import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("./db", () => ({ getMaterialReturnFollowupByRequestId: vi.fn(), upsertMaterialReturnFollowup: vi.fn(), })); vi.mock("./mailer", () => ({ sendOperationalEmail: vi.fn(), })); vi.mock("./materialReturnEmail", () => ({ generateMaterialReturnReminderEmail: vi.fn(), })); vi.mock("./materialReturnPdf", () => ({ generateMaterialReturnStatementPdf: vi.fn(), })); vi.mock("./logisticsSettings", () => ({ getLogisticsSettings: vi.fn(), })); import * as db from "./db"; import { getLogisticsSettings } from "./logisticsSettings"; import { scheduleMaterialReturnFollowup } from "./materialReturnWorkflow"; describe("scheduleMaterialReturnFollowup", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(getLogisticsSettings).mockResolvedValue({ materialReturnLeadDays: 4, materialReturnGraceDays: 3, inventory: { tente3x3: 12, chapiteau5x5: 10, podium: 1, autres: null, }, replacementValues: { tente3x3: 0, chapiteau5x5: 0, podium: 0, autres: null, }, }); }); it("keeps the standard expiry window for a fresh follow-up", async () => { vi.mocked(db.getMaterialReturnFollowupByRequestId).mockResolvedValue(undefined); vi.mocked(db.upsertMaterialReturnFollowup).mockResolvedValue(1); const request = { id: 12, associationId: 3, formData: JSON.stringify({ dateRestitution: "2026-06-15" }), } as any; await scheduleMaterialReturnFollowup({ request, serviceLabel: "Service technique", recipientEmails: ["agent@example.com"], }); expect(db.upsertMaterialReturnFollowup).toHaveBeenCalledTimes(1); const payload = vi.mocked(db.upsertMaterialReturnFollowup).mock.calls[0]?.[1]; expect(payload.status).toBe("planifie"); expect(new Date(payload.uploadTokenExpiresAt).toISOString().startsWith("2026-06-18")).toBe(true); }); it("reactivates an expired upload window when the follow-up is reassigned", async () => { const expiredDate = new Date(); expiredDate.setDate(expiredDate.getDate() - 2); expiredDate.setHours(23, 59, 59, 999); vi.mocked(db.getMaterialReturnFollowupByRequestId).mockResolvedValue({ id: 4, requestId: 12, associationId: 3, status: "en_cours", uploadToken: "existing-token", uploadTokenExpiresAt: expiredDate, } as any); vi.mocked(db.upsertMaterialReturnFollowup).mockResolvedValue(4); const request = { id: 12, associationId: 3, formData: JSON.stringify({ dateRestitution: "2026-05-01" }), } as any; await scheduleMaterialReturnFollowup({ request, serviceLabel: "Service technique", recipientEmails: ["agent@example.com"], }); const payload = vi.mocked(db.upsertMaterialReturnFollowup).mock.calls[0]?.[1]; expect(payload.uploadToken).toBe("existing-token"); expect(new Date(payload.uploadTokenExpiresAt).getTime()).toBeGreaterThan(Date.now()); expect(payload.status).toBe("planifie"); }); it("generates a new token and a fresh upload window when reactivation is explicit", async () => { const futureExpiry = new Date("2026-06-18T23:59:59.999Z"); vi.mocked(db.getMaterialReturnFollowupByRequestId).mockResolvedValue({ id: 4, requestId: 12, associationId: 3, status: "en_attente", uploadToken: "existing-token", uploadTokenExpiresAt: futureExpiry, } as any); vi.mocked(db.upsertMaterialReturnFollowup).mockResolvedValue(4); const request = { id: 12, associationId: 3, formData: JSON.stringify({ dateRestitution: "2026-06-15" }), } as any; const reactivatedAt = new Date("2026-06-20T10:30:00.000Z"); await scheduleMaterialReturnFollowup({ request, serviceLabel: "Service technique", recipientEmails: ["agent@example.com"], }, { forceNewToken: true, forceImmediateSendWindow: true, reactivatedByUserId: 9, reactivatedAt, reactivationReason: "Suivi rouvert", }); const payload = vi.mocked(db.upsertMaterialReturnFollowup).mock.calls[0]?.[1]; expect(payload.uploadToken).not.toBe("existing-token"); expect(new Date(payload.uploadTokenExpiresAt).getTime()).toBeGreaterThan(futureExpiry.getTime()); expect(new Date(payload.uploadTokenExpiresAt).getTime()).toBeGreaterThan(reactivatedAt.getTime()); expect(payload.reactivatedByUserId).toBe(9); expect(payload.reactivationReason).toBe("Suivi rouvert"); }); });