const Fastify = require("fastify"); const exportsRoutes = require("../../../src/routes/exports"); const ACTOR_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; const PROJECT_ID = "11111111-1111-4111-8111-111111111111"; const OWNER_ID = "22222222-2222-4222-8222-222222222222"; const DEPARTMENT_ID = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"; function buildFeatureFlagRecord(flagKey) { return { id: `flag-${flagKey}`, flag_key: flagKey, override_mode: "force_on", rollout_type: "global", department_ids: [], note: "unit-test", version: 1, created_at: new Date("2026-04-04T08:00:00.000Z"), created_by: null, updated_at: new Date("2026-04-04T08:00:00.000Z"), updated_by: null, }; } function buildContractRow(overrides = {}) { return { id: "contract-1", contract_no: "HT-2026-001", project_id: PROJECT_ID, contract_type: "service", counterparty_name: "测试客户", summary: "合同摘要", amount: "88000.00", currency: "CNY", signed_date: new Date("2026-03-01T00:00:00.000Z"), effective_date: new Date("2026-03-05T00:00:00.000Z"), expiry_date: new Date("2026-03-31T00:00:00.000Z"), confidentiality_level: "internal", archive_location: "/archive/contracts/001", offline_archive_no: "OFF-001", owner_employee_id: OWNER_ID, status: "active", created_at: new Date("2026-03-01T08:00:00.000Z"), updated_at: new Date("2026-04-01T08:00:00.000Z"), owner: { name: "Owner", }, project: { project_no: "PRJ-001", name: "试点项目", }, ...overrides, }; } async function buildApp(options = {}) { const app = Fastify(); const actor = options.actor ?? { id: ACTOR_ID, department_id: DEPARTMENT_ID, permissionCodes: ["project.manage"], }; const count = options.count ?? vi.fn().mockResolvedValue(1); const findMany = options.findMany ?? vi.fn().mockImplementation(async ({ where }) => { if (where?.OR) { return []; } return [buildContractRow()]; }); const auditCreate = options.auditCreate ?? vi.fn().mockResolvedValue({}); app.decorate("authenticate", async (request) => { request.employee = actor; }); app.decorate("requirePermission", (permissionCode) => async (request) => { request.employee = actor; const permissionCodes = actor.permissionCodes ?? []; if (!permissionCodes.includes("*") && !permissionCodes.includes(permissionCode)) { throw new Error(`unexpected permission check: ${permissionCode}`); } }); app.decorate("hasPermission", (employee, permissionCode) => { const permissionCodes = employee.permissionCodes ?? []; return permissionCodes.includes("*") || permissionCodes.includes(permissionCode); }); app.decorate("config", { appTimezone: "Asia/Shanghai", projectLinkingEnabled: true, }); app.decorate("prisma", { featureFlag: { findUnique: vi.fn().mockImplementation(async ({ where }) => buildFeatureFlagRecord(where.flag_key)), }, auditLog: { create: auditCreate, }, contractRegistry: { count, findMany, }, }); await app.register(exportsRoutes, { prefix: "/api" }); return { app, count, findMany, auditCreate }; } describe("contracts registry export route", () => { it("exports filtered contracts registry as csv", async () => { const { app, count, findMany, auditCreate } = await buildApp(); const response = await app.inject({ method: "POST", url: "/api/exports/contracts-registry", payload: { format: "csv", status: "expired", contractType: "service", counterpartyName: "测试", expiryDateFrom: "2026-03-01", expiryDateTo: "2026-03-31", }, }); expect(response.statusCode).toBe(200); expect(response.headers["content-type"]).toContain("text/csv"); expect(response.headers["content-disposition"]).toContain("contracts-registry-"); expect(response.body).toContain("合同编号"); expect(response.body).toContain("服务合同"); expect(response.body).toContain("已过期"); expect(count).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ AND: expect.arrayContaining([ expect.objectContaining({ contract_type: "service" }), expect.objectContaining({ counterparty_name: expect.objectContaining({ contains: "测试", mode: "insensitive" }), }), ]), }), }), ); expect(findMany).toHaveBeenCalled(); expect(auditCreate).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: "EXPORT_CONTRACTS_REGISTRY", extra: expect.objectContaining({ format: "csv", status: "expired", contractType: "service", counterpartyName: "测试", matchedTotal: 1, }), }), }), ); await app.close(); }); it("exports contracts registry as xlsx", async () => { const { app, auditCreate } = await buildApp({ count: vi.fn().mockResolvedValue(1), findMany: vi.fn().mockResolvedValue([ buildContractRow({ id: "contract-2", contract_no: "HT-2026-002", contract_type: "purchase", status: "archived", expiry_date: new Date("2026-04-15T00:00:00.000Z"), }), ]), }); const response = await app.inject({ method: "POST", url: "/api/exports/contracts-registry", payload: { format: "xlsx", }, }); expect(response.statusCode).toBe(200); expect(response.headers["content-type"]).toContain( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ); expect(response.headers["content-disposition"]).toContain("contracts-registry-"); expect(Buffer.from(response.rawPayload).length).toBeGreaterThan(0); expect(auditCreate).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: "EXPORT_CONTRACTS_REGISTRY", extra: expect.objectContaining({ format: "xlsx", matchedTotal: 1, }), }), }), ); await app.close(); }); });