// @vitest-environment node /** * Kontoloeschung mit Patenschaften: nicht, solange ein Abo laeuft (sonst * zoege Stripe weiter ab, ohne dass es ein Konto gibt); beim Loeschen * verschwinden die Urkunden-Dateien der eigenen Patenschaften mit. */ import { mkdtemp, mkdir, writeFile, stat } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; const m = vi.hoisted(() => ({ db: { sponsorshipSubscription: { count: vi.fn() }, sponsorship: { findMany: vi.fn() }, }, })); vi.mock("@/lib/prisma", () => ({ prisma: m.db })); const root = await mkdtemp(path.join(tmpdir(), "wabenhain-certs-")); process.env.CERTIFICATE_DIR = root; process.env.UPLOAD_DIR = path.join(tmpdir(), "wabenhain-uploads-elsewhere"); const { BLOCKING_SUBSCRIPTION_WHERE, hasBlockingSubscription, certificateDirsOfCustomer, removeCertificateDirs } = await import( "./customer-deletion" ); beforeAll(async () => { await mkdir(path.join(root, "2026", "WH-P-2026-00001"), { recursive: true }); await writeFile(path.join(root, "2026", "WH-P-2026-00001", "r1.pdf"), "pdf"); await writeFile(path.join(root, "2026", "WH-P-2026-00001", "r1-textur-2048.webp"), "webp"); await mkdir(path.join(root, "2026", "WH-P-2026-00002"), { recursive: true }); await writeFile(path.join(root, "2026", "WH-P-2026-00002", "r1.pdf"), "andere Person"); }); beforeEach(() => { vi.clearAllMocks(); vi.spyOn(console, "error").mockImplementation(() => {}); vi.spyOn(console, "info").mockImplementation(() => {}); }); describe("hasBlockingSubscription", () => { test("laufende, gekuendigte, im Verzug und bezahlt-aber-noch-offene (SEPA) Abos blockieren", () => { expect(BLOCKING_SUBSCRIPTION_WHERE).toEqual({ OR: [{ status: { in: ["AKTIV", "IM_VERZUG", "GEKUENDIGT"] } }, { status: "AUSSTEHEND", stripeSubscriptionId: { not: null } }], }); }); test("fragt nur die Abos dieses Kontos ab", async () => { m.db.sponsorshipSubscription.count.mockResolvedValue(1); expect(await hasBlockingSubscription("cust_1")).toBe(true); expect(m.db.sponsorshipSubscription.count).toHaveBeenCalledWith({ where: { customerId: "cust_1", ...BLOCKING_SUBSCRIPTION_WHERE } }); m.db.sponsorshipSubscription.count.mockResolvedValue(0); expect(await hasBlockingSubscription("cust_1")).toBe(false); }); }); describe("Urkunden-Dateien", () => { test("sammelt die Ordner der eigenen Patenschaften (Revisionspfade und Nummernordner)", async () => { m.db.sponsorship.findMany.mockResolvedValue([ { number: "WH-P-2026-00001", createdAt: new Date("2026-03-01T00:00:00Z"), revisions: [{ pdfPath: "2026/WH-P-2026-00001/r1.pdf" }] }, { number: "WH-P-2027-00009", createdAt: new Date("2027-01-02T00:00:00Z"), revisions: [] }, ]); expect(await certificateDirsOfCustomer("cust_1")).toEqual(["2026/WH-P-2026-00001", "2027/WH-P-2027-00009"]); // Uebertragene Patenschaften gehoeren dem neuen Konto und bleiben: gefiltert wird nach ownerId. expect(m.db.sponsorship.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: { ownerId: "cust_1" } })); }); test("loescht nur die genannten Ordner, fehlende Ordner und unsichere Pfade sind kein Fehler", async () => { await removeCertificateDirs(["2026/WH-P-2026-00001", "2027/WH-P-2027-00009", "../ausserhalb"]); await expect(stat(path.join(root, "2026", "WH-P-2026-00001"))).rejects.toThrow(); await expect(stat(path.join(root, "2026", "WH-P-2026-00002", "r1.pdf"))).resolves.toBeTruthy(); }); });