const Fastify = require("fastify"); const { AppError } = require("../../../src/utils/AppError"); const travelOrdersRoutes = require("../../../src/routes/travelOrders"); function buildPrisma({ employees = [], travelRequests = [], upsertImpl } = {}) { return { employee: { findMany: vi.fn().mockResolvedValue(employees), }, travelRequest: { findMany: vi.fn().mockResolvedValue(travelRequests), }, travelOrder: { upsert: vi.fn().mockImplementation(upsertImpl ?? (async ({ create }) => ({ id: "order-1", source: create.source, external_order_no: create.external_order_no, employee_id: create.employee_id, department_id: create.department_id, linked_reimbursement_id: create.linked_reimbursement_id, travel_request_id: create.travel_request_id, occurred_at: create.occurred_at, amount: create.amount, currency: create.currency, status: create.status, created_at: new Date("2026-03-10T00:00:00.000Z"), updated_at: new Date("2026-03-10T00:00:00.000Z"), }))), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit-1" }), }, }; } async function buildApp({ prisma }) { const app = Fastify(); app.decorate("prisma", prisma); app.decorate("config", { travelSyncEnabled: true }); app.decorate("authenticate", async (request) => { if (!request.headers.authorization) { throw new AppError({ code: "AUTH_001", message: "未登录", statusCode: 401 }); } request.employee = { id: "actor-1", permissionCodes: ["integration.manage"] }; }); app.decorate("hasPermission", (employee, permissionCode) => (employee?.permissionCodes ?? []).includes(permissionCode)); app.decorate("requirePermission", (permissionCode) => async (request) => { await app.authenticate(request); if (!app.hasPermission(request.employee, permissionCode)) { throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403 }); } }); app.setErrorHandler((error, request, reply) => { if (error instanceof AppError) { return reply.status(error.statusCode).send({ requestId: request.id, error: { code: error.code, message: error.message, details: error.details ?? {}, }, }); } return reply.status(500).send({ requestId: request.id, error: { code: "SYS_001", message: "服务器内部错误", details: {} }, }); }); await app.register(travelOrdersRoutes, { prefix: "/api" }); return app; } describe("travelOrders routes", () => { it("imports rows with legal travelRequestId linkage", async () => { const prisma = buildPrisma({ employees: [{ id: "employee-1", department_id: "dept-1" }], travelRequests: [{ id: "11111111-1111-4111-8111-111111111111", applicant_employee_id: "employee-1" }], }); const app = await buildApp({ prisma }); const response = await app.inject({ method: "POST", url: "/api/travel/orders/import", headers: { authorization: "Bearer test" }, payload: { orders: [ { source: "ctrip", externalOrderNo: "EXT-001", employeeId: "employee-1", travelRequestId: "11111111-1111-4111-8111-111111111111", amount: "1888.00", }, ], }, }); expect(response.statusCode).toBe(200); expect(response.json().data).toEqual( expect.objectContaining({ imported: 1, linkedTravelRequestCount: 1, }), ); expect(response.json().data.items[0].travelRequestId).toBe("11111111-1111-4111-8111-111111111111"); await app.close(); }); it("skips rows when travel request employee mismatches", async () => { const prisma = buildPrisma({ employees: [{ id: "employee-1", department_id: "dept-1" }], travelRequests: [{ id: "11111111-1111-4111-8111-111111111111", applicant_employee_id: "employee-2" }], }); const app = await buildApp({ prisma }); const response = await app.inject({ method: "POST", url: "/api/travel/orders/import", headers: { authorization: "Bearer test" }, payload: { orders: [{ source: "ctrip", externalOrderNo: "EXT-002", employeeId: "employee-1", travelRequestId: "11111111-1111-4111-8111-111111111111" }], }, }); expect(response.statusCode).toBe(200); expect(response.json().data.imported).toBe(0); expect(response.json().data.skipped).toEqual([ expect.objectContaining({ reason: "travel_request_employee_mismatch" }), ]); await app.close(); }); it("skips rows when travel request does not exist", async () => { const prisma = buildPrisma({ employees: [{ id: "employee-1", department_id: "dept-1" }], travelRequests: [], }); const app = await buildApp({ prisma }); const response = await app.inject({ method: "POST", url: "/api/travel/orders/import", headers: { authorization: "Bearer test" }, payload: { orders: [{ source: "ctrip", externalOrderNo: "EXT-003", employeeId: "employee-1", travelRequestId: "11111111-1111-4111-8111-111111111111" }], }, }); expect(response.statusCode).toBe(200); expect(response.json().data.imported).toBe(0); expect(response.json().data.skipped).toEqual([ expect.objectContaining({ reason: "travel_request_not_found" }), ]); await app.close(); }); it("preserves existing travel linkage when new row omits travelRequestId", async () => { const prisma = buildPrisma({ employees: [{ id: "employee-1", department_id: "dept-1" }], travelRequests: [], upsertImpl: async ({ create, update }) => { expect(update.travel_request_id).toBeUndefined(); return { id: "order-2", source: create.source, external_order_no: create.external_order_no, employee_id: create.employee_id, department_id: create.department_id, linked_reimbursement_id: create.linked_reimbursement_id, travel_request_id: "existing-travel-request", occurred_at: create.occurred_at, amount: create.amount, currency: create.currency, status: "imported", created_at: new Date("2026-03-10T00:00:00.000Z"), updated_at: new Date("2026-03-10T00:00:00.000Z"), }; }, }); const app = await buildApp({ prisma }); const response = await app.inject({ method: "POST", url: "/api/travel/orders/import", headers: { authorization: "Bearer test" }, payload: { orders: [{ source: "ctrip", externalOrderNo: "EXT-004", employeeId: "employee-1", amount: "500.00" }], }, }); expect(response.statusCode).toBe(200); expect(response.json().data.items[0].travelRequestId).toBe("existing-travel-request"); await app.close(); }); });