const Fastify = require("fastify"); const riskFlagsRoutes = require("../../../src/routes/riskFlags"); const ACTOR_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; const RISK_FLAG_ID = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"; function buildRiskFlag(overrides = {}) { return { id: RISK_FLAG_ID, entity_type: "reimbursement", entity_id: "cccccccc-cccc-4ccc-8ccc-cccccccccccc", risk_code: "budget_exceeded", level: "high", title: "预算已超支", detail: "部门预算已超预算阈值", source: "budget_control", status: "open", payload_json: null, created_at: new Date("2026-04-06T08:00:00.000Z"), created_by: null, resolved_at: null, resolved_by: null, ...overrides, }; } async function buildApp(options = {}) { const app = Fastify(); const actor = options.actor ?? { id: ACTOR_ID, permissionCodes: ["risk.review.manage"], }; const currentRecord = options.currentRecord ?? buildRiskFlag(); const findUnique = options.findUnique ?? vi.fn().mockResolvedValue(currentRecord); const update = options.update ?? vi.fn().mockImplementation(async ({ data }) => ({ ...currentRecord, status: data.status, resolved_at: data.resolved_at, resolved_by: data.resolved_by, })); const auditCreate = options.auditCreate ?? vi.fn().mockResolvedValue({}); app.decorate("authenticate", async (request) => { request.employee = actor; }); app.decorate("hasPermission", (employee, permissionCode) => { const permissionCodes = employee.permissionCodes ?? []; return permissionCodes.includes("*") || permissionCodes.includes(permissionCode); }); app.decorate("prisma", { entityRiskFlag: { findMany: vi.fn().mockResolvedValue([]), findUnique, update, }, auditLog: { create: auditCreate, }, }); await app.register(riskFlagsRoutes, { prefix: "/api" }); return { app, findUnique, update, auditCreate }; } describe("risk flags routes", () => { it("writes resolve note into audit extra while keeping response shape", async () => { const { app, update, auditCreate } = await buildApp(); const response = await app.inject({ method: "POST", url: `/api/risk-flags/${RISK_FLAG_ID}/resolve`, payload: { note: "已人工核查,无需继续挂起", }, }); expect(response.statusCode).toBe(200); expect(update).toHaveBeenCalledWith( expect.objectContaining({ where: { id: RISK_FLAG_ID }, data: expect.objectContaining({ status: "resolved", resolved_by: ACTOR_ID, }), }), ); expect(auditCreate).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: "ENTITY_RISK_FLAG_RESOLVED", extra: expect.objectContaining({ riskCode: "budget_exceeded", note: "已人工核查,无需继续挂起", }), }), }), ); const payload = response.json(); expect(payload.data.item.status).toBe("resolved"); expect(payload.data.item.resolvedBy).toBe(ACTOR_ID); await app.close(); }); it("rejects unexpected fields for resolve action body", async () => { const { app, update, auditCreate } = await buildApp(); const response = await app.inject({ method: "POST", url: `/api/risk-flags/${RISK_FLAG_ID}/resolve`, payload: { note: "ok", extra: "not-allowed", }, }); expect(response.statusCode).toBe(400); expect(update).not.toHaveBeenCalled(); expect(auditCreate).not.toHaveBeenCalled(); await app.close(); }); });