Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m57s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 3m7s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / test (push) Canceled after 1m22s
- Oeffentliche Kuendigung: exakte Identifikation, Limits je Vertrag und Adresse, Bestaetigung nur an die Kontoadresse mit Ruecknahme-Link (HMAC) - Vorschau: Body-Grenze beim Lesen, Parallelitaets- und Minutenlimit, Cache - Rabatte gelten nicht fuer Patenschaftspositionen - Abo aktiv erst nach bestaetigter Zahlung, offene Abschluesse zaehlen zum Kontingent und werden nach 24 h aufgeraeumt, Sitzung 58 Minuten - Konto-Loeschung nur ohne laufendes Abo, Urkunden-Dateien werden entfernt - Abo-Lieferungen mit Zahlungszeile (DHL-Marke moeglich) - Shop zeigt freien Bestand nach Abo-Reservierung - Storno/volle Erstattung widerruft die Patenschaft (Status WIDERRUFEN) - Umsatzsteuer der Patenschaft per VAT_RATE_PERCENT_PATENSCHAFT (19 %) - Digitale Bestellungen archivieren sich, Kalendertage in Europe/Berlin, Monatsenden begrenzt, Datenauskunft um Patenschaften ergaenzt Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
78 lines
3.5 KiB
TypeScript
78 lines
3.5 KiB
TypeScript
// @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();
|
|
});
|
|
});
|