diff --git a/apps/server/test/agents-routes.test.ts b/apps/server/test/agents-routes.test.ts index d197d105..8701333d 100644 --- a/apps/server/test/agents-routes.test.ts +++ b/apps/server/test/agents-routes.test.ts @@ -179,6 +179,59 @@ describe("POST /api/v1/agents (create)", () => { expect(res.statusCode).toBe(400); expect(res.json().error).toContain("disabled"); }); + + it("does not apply fullAccess for terminal agents", async () => { + const agent = await createAgent({ + type: "terminal", + fullAccess: true, + }); + expect(agent.type).toBe("terminal"); + expect(agent.fullAccess).toBe(false); + }); + + it("defaults type to codex when omitted", async () => { + const agent = await createAgent({}); + expect(agent.type).toBe("codex"); + }); + + it("applies fullAccess arg for codex type", async () => { + const agent = await createAgent({ + type: "codex", + fullAccess: true, + }); + expect(agent.fullAccess).toBe(true); + }); + + it("trims whitespace-only initialPrompt to undefined", async () => { + const agent = await createAgent({ + initialPrompt: " ", + }); + expect(agent.initialPrompt).toBeFalsy(); + }); +}); + +// --------------------------------------------------------------------------- +// GET /api/v1/agents/:id/repo-icon +// --------------------------------------------------------------------------- +describe("GET /api/v1/agents/:id/repo-icon", () => { + it("returns 404 for non-existent agent", async () => { + const res = await authedInject( + "GET", + "/api/v1/agents/agt_nonexistent/repo-icon" + ); + expect(res.statusCode).toBe(404); + expect(res.json().error).toContain("Agent not found"); + }); + + it("returns 404 when agent has no repo icon", async () => { + const agent = await createAgent(); + const res = await authedInject( + "GET", + `/api/v1/agents/${agent.id}/repo-icon` + ); + expect(res.statusCode).toBe(404); + expect(res.json().error).toContain("No repo icon"); + }); }); // --------------------------------------------------------------------------- diff --git a/apps/server/test/system-routes.test.ts b/apps/server/test/system-routes.test.ts index bf353b86..de4ea86f 100644 --- a/apps/server/test/system-routes.test.ts +++ b/apps/server/test/system-routes.test.ts @@ -629,6 +629,107 @@ describe("POST /api/v1/app/settings/ides", () => { }); }); +describe("GET /api/v1/app/settings/cross-repo-messaging", () => { + it("returns enabled status (defaults to false)", async () => { + const res = await ctx.app.inject({ + method: "GET", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + }); + expect(res.statusCode).toBe(200); + expect(res.json()).toEqual({ enabled: false }); + }); + + it("reflects updated state after POST", async () => { + await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: { enabled: true }, + }); + const res = await ctx.app.inject({ + method: "GET", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + }); + expect(res.statusCode).toBe(200); + expect(res.json()).toEqual({ enabled: true }); + }); +}); + +describe("POST /api/v1/app/settings/cross-repo-messaging", () => { + it("enables cross-repo messaging", async () => { + const res = await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: { enabled: true }, + }); + expect(res.statusCode).toBe(200); + expect(res.json()).toEqual({ enabled: true }); + }); + + it("disables cross-repo messaging", async () => { + await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: { enabled: true }, + }); + const res = await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: { enabled: false }, + }); + expect(res.statusCode).toBe(200); + expect(res.json()).toEqual({ enabled: false }); + }); + + it("rejects non-boolean enabled", async () => { + const res = await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: { enabled: "yes" }, + }); + expect(res.statusCode).toBe(400); + expect(res.json().error).toMatch(/enabled must be a boolean/); + }); + + it("rejects missing enabled field", async () => { + const res = await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: {}, + }); + expect(res.statusCode).toBe(400); + expect(res.json().error).toMatch(/enabled must be a boolean/); + }); + + it("rejects null body", async () => { + const res = await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: null, + }); + expect(res.statusCode).toBe(400); + }); + + it("rejects numeric enabled", async () => { + const res = await ctx.app.inject({ + method: "POST", + url: "/api/v1/app/settings/cross-repo-messaging", + headers: { cookie: sessionCookie }, + payload: { enabled: 1 }, + }); + expect(res.statusCode).toBe(400); + expect(res.json().error).toMatch(/enabled must be a boolean/); + }); +}); + describe("POST /api/v1/energy-report", () => { it("accepts any body and returns 204", async () => { const res = await ctx.app.inject({ diff --git a/apps/web/src/lib/store.test.ts b/apps/web/src/lib/store.test.ts index 35b1374b..df0fe029 100644 --- a/apps/web/src/lib/store.test.ts +++ b/apps/web/src/lib/store.test.ts @@ -6,6 +6,8 @@ import { reconcileMediaSidebarStateStorage, MEDIA_SIDEBAR_STATE_STORAGE_PREFIX, defaultMediaSidebarState, + reconcileDiffViewStateStorage, + DIFF_VIEW_STATE_STORAGE_PREFIX, } from "./store"; describe("reconcileAgentSidebarOrder", () => { @@ -118,3 +120,105 @@ describe("reconcileMediaSidebarStateStorage", () => { ).toBeNull(); }); }); + +describe("reconcileDiffViewStateStorage", () => { + beforeEach(() => { + window.localStorage.clear(); + }); + + afterEach(() => { + window.localStorage.clear(); + }); + + const defaultDiffState = JSON.stringify({ + collapsedFiles: [], + collapsedDirs: [], + scrollTop: 0, + }); + + const storeDiffForAgent = (agentId: string) => { + window.localStorage.setItem( + `${DIFF_VIEW_STATE_STORAGE_PREFIX}${agentId}`, + defaultDiffState + ); + }; + + it("removes keys for agents not in the live set", () => { + storeDiffForAgent("agt_1"); + storeDiffForAgent("agt_2"); + storeDiffForAgent("agt_3"); + + reconcileDiffViewStateStorage(["agt_1", "agt_3"]); + + expect( + window.localStorage.getItem(`${DIFF_VIEW_STATE_STORAGE_PREFIX}agt_1`) + ).not.toBeNull(); + expect( + window.localStorage.getItem(`${DIFF_VIEW_STATE_STORAGE_PREFIX}agt_2`) + ).toBeNull(); + expect( + window.localStorage.getItem(`${DIFF_VIEW_STATE_STORAGE_PREFIX}agt_3`) + ).not.toBeNull(); + }); + + it("does nothing when all stored agents are live", () => { + storeDiffForAgent("agt_1"); + storeDiffForAgent("agt_2"); + + reconcileDiffViewStateStorage(["agt_1", "agt_2"]); + + expect(window.localStorage.length).toBe(2); + }); + + it("removes all diff view keys when live set is empty", () => { + storeDiffForAgent("agt_1"); + storeDiffForAgent("agt_2"); + + reconcileDiffViewStateStorage([]); + + expect(window.localStorage.length).toBe(0); + }); + + it("does nothing when localStorage is empty", () => { + reconcileDiffViewStateStorage(["agt_1"]); + + expect(window.localStorage.length).toBe(0); + }); + + it("does not affect non-diff-view keys", () => { + window.localStorage.setItem("dispatch:leftSidebarOpen", "true"); + window.localStorage.setItem( + `${MEDIA_SIDEBAR_STATE_STORAGE_PREFIX}agt_live`, + "{}" + ); + storeDiffForAgent("agt_dead"); + + reconcileDiffViewStateStorage([]); + + expect(window.localStorage.getItem("dispatch:leftSidebarOpen")).toBe( + "true" + ); + expect( + window.localStorage.getItem( + `${MEDIA_SIDEBAR_STATE_STORAGE_PREFIX}agt_live` + ) + ).toBe("{}"); + expect( + window.localStorage.getItem(`${DIFF_VIEW_STATE_STORAGE_PREFIX}agt_dead`) + ).toBeNull(); + }); + + it("accepts a Set as agentIds", () => { + storeDiffForAgent("agt_1"); + storeDiffForAgent("agt_2"); + + reconcileDiffViewStateStorage(new Set(["agt_1"])); + + expect( + window.localStorage.getItem(`${DIFF_VIEW_STATE_STORAGE_PREFIX}agt_1`) + ).not.toBeNull(); + expect( + window.localStorage.getItem(`${DIFF_VIEW_STATE_STORAGE_PREFIX}agt_2`) + ).toBeNull(); + }); +});