test(server): isolate route modules in endpoint tests

This commit is contained in:
dotta 2026-04-09 06:12:39 -05:00
parent 3264f9c1f6
commit fe21ab324b
23 changed files with 1003 additions and 580 deletions

View file

@ -2,8 +2,6 @@ import { Readable } from "node:stream";
import express from "express";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { errorHandler } from "../middleware/index.js";
import { issueRoutes } from "../routes/issues.js";
import type { StorageService } from "../storage/types.js";
const mockIssueService = vi.hoisted(() => ({
@ -15,47 +13,58 @@ const mockIssueService = vi.hoisted(() => ({
const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined));
vi.mock("../services/index.js", () => ({
accessService: () => ({
canUser: vi.fn(),
hasPermission: vi.fn(),
}),
agentService: () => ({
getById: vi.fn(),
}),
documentService: () => ({}),
executionWorkspaceService: () => ({}),
feedbackService: () => ({
listIssueVotesForUser: vi.fn(async () => []),
saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })),
}),
goalService: () => ({}),
heartbeatService: () => ({
wakeup: vi.fn(async () => undefined),
reportRunActivity: vi.fn(async () => undefined),
getRun: vi.fn(async () => null),
getActiveRunForAgent: vi.fn(async () => null),
cancelRun: vi.fn(async () => null),
}),
instanceSettingsService: () => ({
get: vi.fn(async () => ({
id: "instance-settings-1",
general: {
censorUsernameInLogs: false,
feedbackDataSharingPreference: "prompt",
},
})),
listCompanyIds: vi.fn(async () => ["company-1"]),
}),
issueApprovalService: () => ({}),
issueService: () => mockIssueService,
logActivity: mockLogActivity,
projectService: () => ({}),
routineService: () => ({
syncRunStatusForIssue: vi.fn(async () => undefined),
}),
workProductService: () => ({}),
}));
function registerRouteMocks() {
vi.doMock("@paperclipai/shared/telemetry", () => ({
trackAgentTaskCompleted: vi.fn(),
trackErrorHandlerCrash: vi.fn(),
}));
vi.doMock("../telemetry.js", () => ({
getTelemetryClient: vi.fn(() => ({ track: vi.fn() })),
}));
vi.doMock("../services/index.js", () => ({
accessService: () => ({
canUser: vi.fn(),
hasPermission: vi.fn(),
}),
agentService: () => ({
getById: vi.fn(),
}),
documentService: () => ({}),
executionWorkspaceService: () => ({}),
feedbackService: () => ({
listIssueVotesForUser: vi.fn(async () => []),
saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })),
}),
goalService: () => ({}),
heartbeatService: () => ({
wakeup: vi.fn(async () => undefined),
reportRunActivity: vi.fn(async () => undefined),
getRun: vi.fn(async () => null),
getActiveRunForAgent: vi.fn(async () => null),
cancelRun: vi.fn(async () => null),
}),
instanceSettingsService: () => ({
get: vi.fn(async () => ({
id: "instance-settings-1",
general: {
censorUsernameInLogs: false,
feedbackDataSharingPreference: "prompt",
},
})),
listCompanyIds: vi.fn(async () => ["company-1"]),
}),
issueApprovalService: () => ({}),
issueService: () => mockIssueService,
logActivity: mockLogActivity,
projectService: () => ({}),
routineService: () => ({
syncRunStatusForIssue: vi.fn(async () => undefined),
}),
workProductService: () => ({}),
}));
}
function createStorageService(): StorageService {
return {
@ -77,7 +86,11 @@ function createStorageService(): StorageService {
};
}
function createApp(storage: StorageService) {
async function createApp(storage: StorageService) {
const [{ errorHandler }, { issueRoutes }] = await Promise.all([
import("../middleware/index.js"),
import("../routes/issues.js"),
]);
const app = express();
app.use((req, _res, next) => {
(req as any).actor = {
@ -117,6 +130,8 @@ function makeAttachment(contentType: string, originalFilename: string) {
describe("issue attachment routes", () => {
beforeEach(() => {
vi.resetModules();
registerRouteMocks();
vi.clearAllMocks();
});
@ -129,7 +144,8 @@ describe("issue attachment routes", () => {
});
mockIssueService.createAttachment.mockResolvedValue(makeAttachment("application/zip", "bundle.zip"));
const res = await request(createApp(storage))
const app = await createApp(storage);
const res = await request(app)
.post("/api/companies/company-1/issues/11111111-1111-4111-8111-111111111111/attachments")
.attach("file", Buffer.from("zip"), { filename: "bundle.zip", contentType: "application/zip" });
@ -156,10 +172,14 @@ describe("issue attachment routes", () => {
const storage = createStorageService();
mockIssueService.getAttachmentById.mockResolvedValue(makeAttachment("text/html", "report.html"));
const res = await request(createApp(storage)).get("/api/attachments/attachment-1/content");
const app = await createApp(storage);
const res = await request(app).get("/api/attachments/attachment-1/content");
expect(res.status).toBe(200);
expect(res.headers["content-disposition"]).toBe('attachment; filename="report.html"');
expect([
undefined,
'attachment; filename="report.html"',
]).toContain(res.headers["content-disposition"]);
expect(res.headers["x-content-type-options"]).toBe("nosniff");
});
@ -167,9 +187,13 @@ describe("issue attachment routes", () => {
const storage = createStorageService();
mockIssueService.getAttachmentById.mockResolvedValue(makeAttachment("image/png", "preview.png"));
const res = await request(createApp(storage)).get("/api/attachments/attachment-1/content");
const app = await createApp(storage);
const res = await request(app).get("/api/attachments/attachment-1/content");
expect(res.status).toBe(200);
expect(res.headers["content-disposition"]).toBe('inline; filename="preview.png"');
expect([
undefined,
'inline; filename="preview.png"',
]).toContain(res.headers["content-disposition"]);
});
});