import { describe, test, expect } from "vitest"; import { invoiceRelativePath, resolveInvoicePath, INVOICE_DIR } from "./invoice-storage"; function makeInput(overrides: Partial[0]> = {}) { return { year: 2026, lastName: "Mustermann", firstName: "Erika", invoiceNumber: "RE-2026-A3F9C1B2", ...overrides, }; } describe("invoiceRelativePath", () => { test("sortiert nach Jahr, dann Kunde, dann Rechnungsnummer", () => { // Arrange & Act const relative = invoiceRelativePath(makeInput()); // Assert expect(relative).toBe("2026/mustermann-erika/RE-2026-A3F9C1B2.pdf"); }); test("schreibt Umlaute im Kundenordner aus", () => { // Arrange & Act & Assert: eine blosse Filterung auf a-z machte aus // "Mueller" ein "m-ller" und damit unleserliche Ordner. expect(invoiceRelativePath(makeInput({ lastName: "Müller", firstName: "Jörg" }))).toBe( "2026/mueller-joerg/RE-2026-A3F9C1B2.pdf" ); }); test("legt Rechnungen derselben Person im selben Ordner ab", () => { // Arrange const first = invoiceRelativePath(makeInput({ invoiceNumber: "RE-2026-0001" })); const second = invoiceRelativePath(makeInput({ invoiceNumber: "RE-2026-0002" })); // Act & Assert: genau das ist der Zweck der Ablage — alle Belege einer // Person eines Jahres beieinander. expect(first.split("/").slice(0, 2)).toEqual(second.split("/").slice(0, 2)); expect(first).not.toBe(second); }); test("trennt Jahrgaenge voneinander", () => { // Arrange & Act const older = invoiceRelativePath(makeInput({ year: 2025 })); const newer = invoiceRelativePath(makeInput({ year: 2026 })); // Assert: die Aufbewahrungsfrist laeuft je Jahr ab, ein Jahrgang muss // sich als ganzes Verzeichnis archivieren lassen. expect(older.startsWith("2025/")).toBe(true); expect(newer.startsWith("2026/")).toBe(true); }); test("faellt auf 'unbekannt' zurueck, statt ein leeres Pfadsegment zu erzeugen", () => { // Arrange & Act & Assert: "2026//RE-....pdf" waere ein kaputter Pfad. expect(invoiceRelativePath(makeInput({ lastName: "***", firstName: "###" }))).toBe( "2026/unbekannt/RE-2026-A3F9C1B2.pdf" ); }); }); describe("resolveInvoicePath", () => { test("loest innerhalb des Ablageverzeichnisses auf", () => { // Arrange & Act const resolved = resolveInvoicePath("2026/mustermann-erika/RE-2026-A3F9C1B2.pdf"); // Assert expect(resolved.startsWith(INVOICE_DIR)).toBe(true); }); test("weist Pfade ab, die das Verzeichnis verlassen", () => { // Arrange & Act & Assert: der Pfad kommt zwar aus der eigenen Datenbank, // ist aber irgendwann einmal aus Eingaben entstanden — "../" darf nie // hinausfuehren. expect(() => resolveInvoicePath("../../etc/passwd")).toThrow(); expect(() => resolveInvoicePath("2026/../../../geheim.pdf")).toThrow(); }); });