From b0d6ec582dfe2cc7ef93e0ecd6ab0dcd7c7235e6 Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 28 Apr 2026 13:24:03 -0500 Subject: [PATCH] Cover agent issue mutation ownership Co-Authored-By: Paperclip --- ...ue-agent-mutation-ownership-routes.test.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts b/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts index add0fab3ea7..211c952c908 100644 --- a/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts +++ b/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts @@ -36,6 +36,10 @@ const mockAgentService = vi.hoisted(() => ({ resolveByReference: vi.fn(), })); +const mockCompanyService = vi.hoisted(() => ({ + getById: vi.fn(), +})); + const mockDocumentService = vi.hoisted(() => ({ upsertIssueDocument: vi.fn(), })); @@ -94,6 +98,7 @@ function registerRouteMocks() { vi.doMock("../services/index.js", () => ({ accessService: () => mockAccessService, agentService: () => mockAgentService, + companyService: () => mockCompanyService, documentService: () => mockDocumentService, executionWorkspaceService: () => ({}), feedbackService: () => ({ @@ -244,6 +249,7 @@ describe("agent issue mutation checkout ownership", () => { mockAgentService.getById.mockReset(); mockAgentService.list.mockReset(); mockAgentService.resolveByReference.mockReset(); + mockCompanyService.getById.mockReset(); mockIssueService.addComment.mockReset(); mockIssueService.assertCheckoutOwner.mockReset(); mockIssueService.getAttachmentById.mockReset(); @@ -276,6 +282,7 @@ describe("agent issue mutation checkout ownership", () => { makeAgent(peerAgentId), ]); mockAgentService.resolveByReference.mockResolvedValue({ ambiguous: false, agent: null }); + mockCompanyService.getById.mockResolvedValue({ id: companyId, issuePrefix: "PAP" }); mockIssueService.getById.mockResolvedValue(makeIssue()); mockIssueService.getByIdentifier.mockResolvedValue(null); mockIssueService.assertCheckoutOwner.mockResolvedValue({ adoptedFromRunId: null }); @@ -430,18 +437,20 @@ describe("agent issue mutation checkout ownership", () => { expect(mockIssueService.update).toHaveBeenCalled(); }); - it("allows same-company agent mutations when the issue is not in progress", async () => { - mockIssueService.getById.mockResolvedValue(makeIssue({ status: "todo", assigneeAgentId: ownerAgentId })); - mockIssueService.update.mockImplementation(async (_id: string, patch: Record) => ({ - ...makeIssue({ status: "todo", assigneeAgentId: ownerAgentId }), - ...patch, - })); + it.each([ + ["todo", "patch", (app: express.Express) => request(app).patch(`/api/issues/${issueId}`).send({ title: "Todo update" })], + ["todo", "comment", (app: express.Express) => request(app).post(`/api/issues/${issueId}/comments`).send({ body: "Todo noise" })], + ["blocked", "patch", (app: express.Express) => request(app).patch(`/api/issues/${issueId}`).send({ title: "Blocked update" })], + ])("rejects peer agent %s issue %s mutations outside active checkout ownership", async (status, _kind, sendRequest) => { + mockIssueService.getById.mockResolvedValue(makeIssue({ status: status as "todo" | "blocked", assigneeAgentId: ownerAgentId })); - const res = await request(await createApp(peerActor())).patch(`/api/issues/${issueId}`).send({ title: "Todo update" }); + const res = await sendRequest(await createApp(peerActor())); - expect(res.status).toBe(200); + expect(res.status, JSON.stringify(res.body)).toBe(403); + expect(res.body.error).toBe("Agent cannot mutate another agent's issue"); expect(mockIssueService.assertCheckoutOwner).not.toHaveBeenCalled(); - expect(mockIssueService.update).toHaveBeenCalled(); + expect(mockIssueService.update).not.toHaveBeenCalled(); + expect(mockIssueService.addComment).not.toHaveBeenCalled(); }); it("allows same-company agent mutations on unassigned in-progress issues", async () => {