const Fastify = require("fastify"); const employeesRoutes = require("../../../src/routes/employees"); const { AppError } = require("../../../src/utils/AppError"); const EMPLOYEE_ID = "11111111-1111-4111-8111-111111111111"; const DEPARTMENT_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; function buildFeatureFlagRecord() { return { id: "flag-1", flag_key: "employee_lifecycle_v1", override_mode: "force_on", rollout_type: "global", department_ids: [], note: "", version: 1, created_at: new Date("2026-03-28T00:00:00.000Z"), created_by: null, updated_at: new Date("2026-03-28T00:00:00.000Z"), updated_by: null, }; } function buildCertificateRow(overrides = {}) { return { id: "44444444-4444-4444-8444-444444444444", employee_id: EMPLOYEE_ID, name: "PMP", issuer: "PMI", obtained_date: new Date("2025-01-01T00:00:00.000Z"), expiry_date: null, certificate_no: "CERT-001", attachment_path: "certificates/pmp.pdf", created_at: new Date("2025-01-01T00:00:00.000Z"), created_by: "actor-1", updated_at: new Date("2025-01-01T00:00:00.000Z"), updated_by: "actor-1", ...overrides, }; } async function buildApp({ actor = { id: "actor-1", department_id: DEPARTMENT_ID, permissionCodes: ["system.users"] } } = {}) { const app = Fastify(); const state = { certificate: null, }; const certificateCreate = vi.fn(async ({ data }) => { state.certificate = buildCertificateRow({ name: data.name, issuer: data.issuer, obtained_date: data.obtained_date, expiry_date: data.expiry_date, certificate_no: data.certificate_no, attachment_path: data.attachment_path, created_by: data.created_by, updated_by: data.updated_by, }); return state.certificate; }); const certificateUpdate = vi.fn(async ({ data }) => { state.certificate = { ...state.certificate, ...(data.name !== undefined ? { name: data.name } : {}), ...(data.issuer !== undefined ? { issuer: data.issuer } : {}), ...(data.obtained_date !== undefined ? { obtained_date: data.obtained_date } : {}), ...(data.expiry_date !== undefined ? { expiry_date: data.expiry_date } : {}), ...(data.certificate_no !== undefined ? { certificate_no: data.certificate_no } : {}), ...(data.attachment_path !== undefined ? { attachment_path: data.attachment_path } : {}), ...(data.updated_by !== undefined ? { updated_by: data.updated_by } : {}), }; return state.certificate; }); app.decorate("config", { appTimezone: "Asia/Shanghai", employeeLifecycleEnabled: true, storageDir: "/tmp", }); 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 AppError({ code: "PERM_001", message: "无权限", statusCode: 403 }); } }); app.decorate("hasPermission", (employeeRecord, permissionCode) => { const permissionCodes = employeeRecord.permissionCodes ?? []; return permissionCodes.includes("*") || permissionCodes.includes(permissionCode); }); app.decorate("prisma", { featureFlag: { findUnique: vi.fn().mockResolvedValue(buildFeatureFlagRecord()), }, employee: { findUnique: vi.fn(async ({ where }) => (where.id === EMPLOYEE_ID ? { id: EMPLOYEE_ID } : null)), findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), update: vi.fn(), }, employeeContract: { findFirst: vi.fn().mockResolvedValue(null), findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), update: vi.fn(), delete: vi.fn(), create: vi.fn(), }, employeeTraining: { findMany: vi.fn().mockResolvedValue([]), findFirst: vi.fn().mockResolvedValue(null), create: vi.fn(), update: vi.fn(), }, employeeCertificate: { findMany: vi.fn(async () => (state.certificate ? [state.certificate] : [])), findFirst: vi.fn(async ({ where }) => (state.certificate?.id === where.id && state.certificate?.employee_id === where.employee_id ? state.certificate : null)), create: certificateCreate, update: certificateUpdate, groupBy: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, employeeChecklistTask: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, employeeLifecycleEvent: { findMany: vi.fn().mockResolvedValue([]), }, assetCard: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, workflowTask: { count: vi.fn().mockResolvedValue(0), }, auditLog: { create: vi.fn().mockResolvedValue({}), }, }); app.setErrorHandler((error, request, reply) => { reply.status(error.statusCode || 500).send({ requestId: request.id, error: { code: error.code || "SYS_001", message: error.message, details: error.details ?? null, }, }); }); await app.register(employeesRoutes, { prefix: "/api" }); return { app, certificateCreate, certificateUpdate, state }; } describe("employee certificate routes", () => { it("allows system.users to create, update and list certificates with derived status", async () => { const { app, certificateCreate, certificateUpdate, state } = await buildApp(); const createResponse = await app.inject({ method: "POST", url: `/api/employees/${EMPLOYEE_ID}/certificates`, payload: { name: "PMP", issuer: "PMI", obtainedDate: "2025-01-01", expiryDate: null, certificateNo: "CERT-001", attachmentPath: "certificates/pmp.pdf", }, }); expect(createResponse.statusCode).toBe(200); expect(createResponse.json().data).toMatchObject({ name: "PMP", issuer: "PMI", status: "valid", attachmentPath: "certificates/pmp.pdf", }); expect(certificateCreate).toHaveBeenCalledTimes(1); const patchResponse = await app.inject({ method: "PATCH", url: `/api/employees/${EMPLOYEE_ID}/certificates/${state.certificate.id}`, payload: { expiryDate: "2025-02-01", }, }); expect(patchResponse.statusCode).toBe(200); expect(patchResponse.json().data).toMatchObject({ expiryDate: "2025-02-01", status: "expired", }); expect(certificateUpdate).toHaveBeenCalledTimes(1); const listResponse = await app.inject({ method: "GET", url: `/api/employees/${EMPLOYEE_ID}/certificates`, }); expect(listResponse.statusCode).toBe(200); expect(listResponse.json().data.items).toEqual([ expect.objectContaining({ id: state.certificate.id, status: "expired", }), ]); await app.close(); }); it("rejects non-admin users accessing certificates", async () => { const { app } = await buildApp({ actor: { id: "actor-2", department_id: DEPARTMENT_ID, permissionCodes: ["employee"] }, }); const response = await app.inject({ method: "GET", url: `/api/employees/${EMPLOYEE_ID}/certificates`, }); expect(response.statusCode).toBe(403); await app.close(); }); });