diff --git a/server/src/__tests__/issue-feedback-routes.test.ts b/server/src/__tests__/issue-feedback-routes.test.ts index 31e625e6fe4..e59fe6d1e91 100644 --- a/server/src/__tests__/issue-feedback-routes.test.ts +++ b/server/src/__tests__/issue-feedback-routes.test.ts @@ -72,61 +72,60 @@ const mockIssueReferenceService = vi.hoisted(() => ({ syncIssue: vi.fn(async () => undefined), })); -function registerModuleMocks() { - vi.doMock("@paperclipai/shared/telemetry", () => ({ - trackAgentTaskCompleted: vi.fn(), - trackErrorHandlerCrash: vi.fn(), - })); +// Keep service factories stable while each request uses the hoisted test doubles. +vi.mock("@paperclipai/shared/telemetry", () => ({ + trackAgentTaskCompleted: vi.fn(), + trackErrorHandlerCrash: vi.fn(), +})); - vi.doMock("../telemetry.js", () => ({ - getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), - })); +vi.mock("../telemetry.js", () => ({ + getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), +})); - vi.doMock("../services/index.js", () => ({ - companyService: () => ({ - getById: vi.fn(async () => ({ id: "company-1" })), - }), - accessService: () => mockAccessService, - agentService: () => mockAgentService, - companySkillService: () => ({ - completeTestRunForIssue: vi.fn(async () => null), - }), - documentAnnotationService: () => ({ remapOpenThreadsForDocument: async () => [] }), - documentService: () => ({}), - executionWorkspaceService: () => mockExecutionWorkspaceService, - goalService: () => ({}), - heartbeatService: () => mockHeartbeatService, - issueApprovalService: () => ({}), - issueReferenceService: () => mockIssueReferenceService, - issueRecoveryActionService: () => ({ - getActiveForIssue: vi.fn(async () => null), - listActiveForIssues: vi.fn(async () => new Map()), - }), - issueService: () => mockIssueService, - issueThreadInteractionService: () => mockIssueThreadInteractionService, - logActivity: mockLogActivity, - projectService: () => ({}), - routineService: () => mockRoutineService, - workProductService: () => ({}), - })); +vi.mock("../services/index.js", () => ({ + companyService: () => ({ + getById: vi.fn(async () => ({ id: "company-1" })), + }), + accessService: () => mockAccessService, + agentService: () => mockAgentService, + companySkillService: () => ({ + completeTestRunForIssue: vi.fn(async () => null), + }), + documentAnnotationService: () => ({ remapOpenThreadsForDocument: async () => [] }), + documentService: () => ({}), + executionWorkspaceService: () => mockExecutionWorkspaceService, + goalService: () => ({}), + heartbeatService: () => mockHeartbeatService, + issueApprovalService: () => ({}), + issueReferenceService: () => mockIssueReferenceService, + issueRecoveryActionService: () => ({ + getActiveForIssue: vi.fn(async () => null), + listActiveForIssues: vi.fn(async () => new Map()), + }), + issueService: () => mockIssueService, + issueThreadInteractionService: () => mockIssueThreadInteractionService, + logActivity: mockLogActivity, + projectService: () => ({}), + routineService: () => mockRoutineService, + workProductService: () => ({}), +})); - vi.doMock("../services/environments.js", () => ({ - environmentService: () => mockEnvironmentService, - })); +vi.mock("../services/environments.js", () => ({ + environmentService: () => mockEnvironmentService, +})); - vi.doMock("../services/execution-workspaces.js", () => ({ - executionWorkspaceService: () => mockExecutionWorkspaceService, - STALE_REOPEN_PENDING_CONSUMPTION_GRACE_MS: 5 * 60 * 1000, - })); +vi.mock("../services/execution-workspaces.js", () => ({ + executionWorkspaceService: () => mockExecutionWorkspaceService, + STALE_REOPEN_PENDING_CONSUMPTION_GRACE_MS: 5 * 60 * 1000, +})); - vi.doMock("../services/feedback.js", () => ({ - feedbackService: () => mockFeedbackService, - })); +vi.mock("../services/feedback.js", () => ({ + feedbackService: () => mockFeedbackService, +})); - vi.doMock("../services/instance-settings.js", () => ({ - instanceSettingsService: () => mockInstanceSettingsService, - })); -} +vi.mock("../services/instance-settings.js", () => ({ + instanceSettingsService: () => mockInstanceSettingsService, +})); async function createApp(actor: Record) { const [{ issueRoutes }, { errorHandler }] = await Promise.all([ @@ -152,17 +151,6 @@ async function createApp(actor: Record) { describe("issue feedback trace routes", () => { beforeEach(() => { - vi.resetModules(); - vi.doUnmock("@paperclipai/shared/telemetry"); - vi.doUnmock("../telemetry.js"); - vi.doUnmock("../services/index.js"); - vi.doUnmock("../services/environments.js"); - vi.doUnmock("../services/execution-workspaces.js"); - vi.doUnmock("../services/feedback.js"); - vi.doUnmock("../services/instance-settings.js"); - vi.doUnmock("../routes/issues.js"); - vi.doUnmock("../middleware/index.js"); - registerModuleMocks(); vi.clearAllMocks(); mockFeedbackExportService.flushPendingFeedbackTraces.mockResolvedValue({ attempted: 1, @@ -260,6 +248,8 @@ describe("issue feedback trace routes", () => { const res = await request(app).get("/api/feedback-traces/trace-1"); expect(res.status, JSON.stringify({ body: res.body, errors: app.locals.routeErrors })).toBe(404); + expect(mockFeedbackService.getFeedbackTraceById).toHaveBeenCalledExactlyOnceWith("trace-1", true); + expect(mockFeedbackService.getFeedbackTraceBundle).not.toHaveBeenCalled(); }); it("returns 404 for bundle fetches when a board user lacks access to the trace company", async () => { @@ -280,5 +270,7 @@ describe("issue feedback trace routes", () => { const res = await request(app).get("/api/feedback-traces/trace-1/bundle"); expect(res.status, JSON.stringify({ body: res.body, errors: app.locals.routeErrors })).toBe(404); + expect(mockFeedbackService.getFeedbackTraceBundle).toHaveBeenCalledExactlyOnceWith("trace-1"); + expect(mockFeedbackService.getFeedbackTraceById).not.toHaveBeenCalled(); }); });