From b6d6cfd8d90d269f47c1b3073492e370585c6c80 Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sat, 14 Feb 2026 19:33:35 -0800 Subject: [PATCH] test (gateway/config): cover config.patch agents.list merge-by-id --- src/config/merge-patch.test.ts | 83 +++++++++++++++++++++ src/gateway/server.config-patch.e2e.test.ts | 48 ++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/config/merge-patch.test.ts diff --git a/src/config/merge-patch.test.ts b/src/config/merge-patch.test.ts new file mode 100644 index 00000000000..750d743d684 --- /dev/null +++ b/src/config/merge-patch.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import { applyMergePatch } from "./merge-patch.js"; + +describe("applyMergePatch", () => { + it("replaces arrays by default", () => { + const base = { + agents: { + list: [ + { id: "primary", workspace: "/tmp/one" }, + { id: "secondary", workspace: "/tmp/two" }, + ], + }, + }; + const patch = { + agents: { + list: [{ id: "primary", memorySearch: { extraPaths: ["/tmp/memory.md"] } }], + }, + }; + + const merged = applyMergePatch(base, patch) as { + agents?: { list?: Array<{ id?: string; workspace?: string }> }; + }; + expect(merged.agents?.list).toEqual([ + { id: "primary", memorySearch: { extraPaths: ["/tmp/memory.md"] } }, + ]); + }); + + it("merges object arrays by id when enabled", () => { + const base = { + agents: { + list: [ + { id: "primary", workspace: "/tmp/one" }, + { id: "secondary", workspace: "/tmp/two" }, + ], + }, + }; + const patch = { + agents: { + list: [{ id: "primary", memorySearch: { extraPaths: ["/tmp/memory.md"] } }], + }, + }; + + const merged = applyMergePatch(base, patch, { + mergeObjectArraysById: true, + }) as { + agents?: { + list?: Array<{ + id?: string; + workspace?: string; + memorySearch?: { extraPaths?: string[] }; + }>; + }; + }; + expect(merged.agents?.list).toHaveLength(2); + const primary = merged.agents?.list?.find((entry) => entry.id === "primary"); + const secondary = merged.agents?.list?.find((entry) => entry.id === "secondary"); + expect(primary?.workspace).toBe("/tmp/one"); + expect(primary?.memorySearch?.extraPaths).toEqual(["/tmp/memory.md"]); + expect(secondary?.workspace).toBe("/tmp/two"); + }); + + it("falls back to replacement for non-id arrays even when enabled", () => { + const base = { + channels: { + telegram: { allowFrom: ["111", "222"] }, + }, + }; + const patch = { + channels: { + telegram: { allowFrom: ["333"] }, + }, + }; + + const merged = applyMergePatch(base, patch, { + mergeObjectArraysById: true, + }) as { + channels?: { + telegram?: { allowFrom?: string[] }; + }; + }; + expect(merged.channels?.telegram?.allowFrom).toEqual(["333"]); + }); +}); diff --git a/src/gateway/server.config-patch.e2e.test.ts b/src/gateway/server.config-patch.e2e.test.ts index d2e57223bef..4ea6f08810e 100644 --- a/src/gateway/server.config-patch.e2e.test.ts +++ b/src/gateway/server.config-patch.e2e.test.ts @@ -43,6 +43,54 @@ describe("gateway config methods", () => { expect(res.ok).toBe(false); expect(res.error?.message ?? "").toContain("raw must be an object"); }); + + it("merges agents.list entries by id instead of replacing the full array", async () => { + const setRes = await rpcReq<{ ok?: boolean }>(ws, "config.set", { + raw: JSON.stringify({ + agents: { + list: [ + { id: "primary", default: true, workspace: "/tmp/primary" }, + { id: "secondary", workspace: "/tmp/secondary" }, + ], + }, + }), + }); + expect(setRes.ok).toBe(true); + const snapshotRes = await rpcReq<{ hash?: string }>(ws, "config.get", {}); + expect(snapshotRes.ok).toBe(true); + expect(typeof snapshotRes.payload?.hash).toBe("string"); + + const patchRes = await rpcReq<{ + config?: { + agents?: { + list?: Array<{ + id?: string; + workspace?: string; + }>; + }; + }; + }>(ws, "config.patch", { + baseHash: snapshotRes.payload?.hash, + raw: JSON.stringify({ + agents: { + list: [ + { + id: "primary", + workspace: "/tmp/primary-updated", + }, + ], + }, + }), + }); + expect(patchRes.ok).toBe(true); + + const list = patchRes.payload?.config?.agents?.list ?? []; + expect(list).toHaveLength(2); + const primary = list.find((entry) => entry.id === "primary"); + const secondary = list.find((entry) => entry.id === "secondary"); + expect(primary?.workspace).toBe("/tmp/primary-updated"); + expect(secondary?.workspace).toBe("/tmp/secondary"); + }); }); describe("gateway server sessions", () => {