import { describe, it, expect, vi } from "vitest"; import { ARCHIVE_AFTER_DAYS, archiveCutoff, orderArchiveWhere, orderUnarchiveWhere, productArchiveWhere, productUnarchiveWhere, returnArchiveWhere, returnUnarchiveWhere, runAutoArchive, type ArchiveDb, } from "./archive"; const NOW = new Date("2026-09-25T12:00:00Z"); function fakeDb(counts: { archived: number; restored: number }) { const model = () => ({ updateMany: vi .fn() .mockResolvedValueOnce({ count: counts.archived }) .mockResolvedValueOnce({ count: counts.restored }), }); return { order: model(), returnRequest: model(), product: model() }; } describe("archiveCutoff", () => { it("liegt genau einen Monat (30 Tage) vor jetzt", () => { expect(ARCHIVE_AFTER_DAYS).toBe(30); expect(archiveCutoff(NOW).toISOString()).toBe("2026-08-26T12:00:00.000Z"); }); }); describe("orderArchiveWhere", () => { const where = orderArchiveWhere(archiveCutoff(NOW)); it("archiviert nur versendete, abgeholte und stornierte Bestellungen", () => { expect(where.status).toEqual({ in: ["VERSENDET", "ABGEHOLT", "STORNIERT"] }); }); it("verlangt, dass die letzte Bearbeitung mehr als einen Monat zurueckliegt", () => { expect(where.updatedAt).toEqual({ lte: archiveCutoff(NOW) }); expect(where.archivedAt).toBeNull(); }); it("laesst Bestellungen mit offener oder frisch bearbeiteter Retoure aktiv", () => { expect(where.OR).toEqual([ { returnRequest: { is: null } }, { returnRequest: { is: { status: { in: ["ANGENOMMEN", "ABGELEHNT", "ERSTATTET"] }, updatedAt: { lte: archiveCutoff(NOW) } }, }, }, ]); }); }); describe("orderUnarchiveWhere", () => { it("holt nur automatisch archivierte Bestellungen zurueck, wenn Status oder Retoure wieder offen sind", () => { expect(orderUnarchiveWhere()).toEqual({ archivedAt: { not: null }, archivedManually: false, OR: [ { status: { notIn: ["VERSENDET", "ABGEHOLT", "STORNIERT"] } }, { returnRequest: { is: { status: "ANGEFRAGT" } } }, ], }); }); }); describe("returnArchiveWhere / returnUnarchiveWhere", () => { it("archiviert angenommene, abgelehnte und erstattete Retouren nach einem Monat ohne Bearbeitung", () => { expect(returnArchiveWhere(archiveCutoff(NOW))).toEqual({ archivedAt: null, status: { in: ["ANGENOMMEN", "ABGELEHNT", "ERSTATTET"] }, updatedAt: { lte: archiveCutoff(NOW) }, }); }); it("holt eine automatisch archivierte Retoure zurueck, sobald sie wieder offen ist", () => { expect(returnUnarchiveWhere()).toEqual({ archivedAt: { not: null }, archivedManually: false, status: { notIn: ["ANGENOMMEN", "ABGELEHNT", "ERSTATTET"] }, }); }); }); describe("productArchiveWhere / productUnarchiveWhere", () => { it("archiviert Produkte, die seit ueber einem Monat Bestand 0 haben und nicht bearbeitet wurden", () => { const cutoff = archiveCutoff(NOW); expect(productArchiveWhere(cutoff)).toEqual({ archivedAt: null, updatedAt: { lte: cutoff }, OR: [{ inventory: { is: null } }, { inventory: { is: { quantity: { lte: 0 }, updatedAt: { lte: cutoff } } } }], }); }); it("holt ein automatisch archiviertes Produkt zurueck, sobald wieder Bestand da ist", () => { expect(productUnarchiveWhere()).toEqual({ archivedAt: { not: null }, archivedManually: false, inventory: { is: { quantity: { gt: 0 } } }, }); }); }); describe("runAutoArchive", () => { it("archiviert faellige Eintraege und holt wieder offene zurueck, jeweils nur fuer die angefragte Liste", async () => { const db = fakeDb({ archived: 3, restored: 1 }); const result = await runAutoArchive(db as unknown as ArchiveDb, "orders", NOW); expect(result).toEqual({ archived: 3, restored: 1 }); expect(db.order.updateMany).toHaveBeenNthCalledWith(1, { where: orderArchiveWhere(archiveCutoff(NOW)), data: { archivedAt: NOW, archivedManually: false }, }); expect(db.order.updateMany).toHaveBeenNthCalledWith(2, { where: orderUnarchiveWhere(), data: { archivedAt: null }, }); expect(db.product.updateMany).not.toHaveBeenCalled(); expect(db.returnRequest.updateMany).not.toHaveBeenCalled(); }); it("nutzt fuer Retouren und Produkte die jeweils eigenen Regeln", async () => { const db = fakeDb({ archived: 0, restored: 0 }); await runAutoArchive(db as unknown as ArchiveDb, "returns", NOW); await runAutoArchive(db as unknown as ArchiveDb, "products", NOW); expect(db.returnRequest.updateMany.mock.calls[0][0].where).toEqual(returnArchiveWhere(archiveCutoff(NOW))); expect(db.product.updateMany.mock.calls[0][0].where).toEqual(productArchiveWhere(archiveCutoff(NOW))); }); });