/** * Milestone 3 (R3) Empirical Challenger Verification Harness * Comprehensive Adversarial Stress Testing of Table, Filter & Selection Engines */ import { describe, test, it, expect } from "./runner"; import { ProcessedReceipt, ReceiptCategory, DocumentType, PaymentMethod } from "../../src/lib/schema/receipt"; import { resolveReceiptStatusTier, getStatusTierMeta, ReceiptStatusTier } from "../../src/components/dashboard/StatusBadge"; import { recalculateReceipt, confirmReceiptReviewed } from "../../src/lib/ai/recalculate"; import { generateDualSheetExcel } from "../../src/lib/export/excelGenerator"; import { generateAccountingCsv } from "../../src/lib/export/csvGenerator"; import { grossOf, netOf } from "../../src/components/dashboard/receiptFormat"; // Mock Receipt Factory function mockReceipt(id: string, overrides: Partial = {}): ProcessedReceipt { return { id, imageHash: `hash-${id}`, originalFileName: `${id}.pdf`, fileSizeBytes: 50000, previewUrl: `blob:http://localhost/${id}.pdf`, createdAt: "2026-08-15T10:00:00.000Z", updatedAt: "2026-08-15T10:00:00.000Z", status: "ready", documentType: "KASSENBON", receiptNumber: `REC-${id}`, currency: "EUR", merchant: { name: `Merchant ${id}`, address: "Musterstr. 1, Berlin", taxId: "DE123456789", confidence: 0.99, }, date: { isoDate: "2026-08-15", time: "10:30", confidence: 0.99, }, totalAmount: { value: 100.0, confidence: 0.99, }, netAmount: 84.03, taxBreakdown: [{ ratePercent: 19, taxAmount: 15.97, netAmount: 84.03 }], lineItems: [ { description: `Item for ${id}`, quantity: 1, price: 100.0, taxRate: 19 }, ], suggestedCategory: "Bürobedarf & IT", paymentMethod: "EC_KARTE", validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: false, reviewField: "none", reviewReason: null, issues: [], }, ...overrides, }; } // Pure Filter Predicate implementation matching useReceiptFilters function applyFilters( receipts: ProcessedReceipt[], filters: { period?: string; status?: string; category?: string; searchQuery?: string; amountRange?: { min?: number | null; max?: number | null }; } ): ProcessedReceipt[] { const { period = "all", status = "all", category = "all", searchQuery = "", amountRange = { min: null, max: null }, } = filters; const query = searchQuery.trim().toLowerCase(); return receipts.filter((receipt) => { // 1. Period filter if (period !== "all") { if (!receipt.date?.isoDate) return false; const date = new Date(receipt.date.isoDate); if (isNaN(date.getTime())) return false; const now = new Date(); const dateYear = date.getFullYear(); const dateMonth = date.getMonth(); const dateDay = date.getDate(); const nowYear = now.getFullYear(); const nowMonth = now.getMonth(); const nowDay = now.getDate(); if (period === "today") { if (!(dateYear === nowYear && dateMonth === nowMonth && dateDay === nowDay)) return false; } else if (period === "month") { if (!(dateYear === nowYear && dateMonth === nowMonth)) return false; } else if (period === "year") { if (dateYear !== nowYear) return false; } else if (typeof period === "string") { if (!receipt.date.isoDate.startsWith(period)) return false; } } // 2. Status filter if (status !== "all") { const tier = resolveReceiptStatusTier(receipt); const norm = status.toLowerCase(); if (norm === "pending" || norm === "pending_review" || norm === "pruefen") { if (tier !== "pending_review") return false; } else if (norm === "confirmed" || norm === "bestaetigt") { if (tier !== "confirmed") return false; } else if (norm === "scanned" || norm === "erfasst" || norm === "ready") { if (tier !== "scanned") return false; } } // 3. Category filter if (category !== "all") { if (receipt.suggestedCategory !== category) return false; } // 4. Amount Range filter const gross = grossOf(receipt); if (amountRange.min != null && !isNaN(amountRange.min)) { if (gross < amountRange.min) return false; } if (amountRange.max != null && !isNaN(amountRange.max)) { if (gross > amountRange.max) return false; } // 5. Search query if (query) { const merchantName = (receipt.merchant?.name ?? "").toLowerCase(); const merchantAddress = (receipt.merchant?.address ?? "").toLowerCase(); const receiptNo = (receipt.receiptNumber ?? "").toLowerCase(); const isoDate = (receipt.date?.isoDate ?? "").toLowerCase(); const cat = (receipt.suggestedCategory ?? "").toLowerCase(); const docType = (receipt.documentType ?? "").toLowerCase(); const paymentMethod = (receipt.paymentMethod ?? "").toLowerCase(); const grossStr = gross.toFixed(2); const grossStrDe = grossStr.replace(".", ","); const lineItemsMatch = receipt.lineItems?.some((item) => item?.description?.toLowerCase().includes(query) ); const matches = merchantName.includes(query) || merchantAddress.includes(query) || receiptNo.includes(query) || isoDate.includes(query) || cat.includes(query) || docType.includes(query) || paymentMethod.includes(query) || grossStr.includes(query) || grossStrDe.includes(query) || lineItemsMatch; if (!matches) return false; } return true; }); } // Pure Selection Engine state machine class SelectionStateMachine { selectedIds: string[] = []; constructor(initial: string[] = []) { this.selectedIds = [...initial]; } get set(): Set { return new Set(this.selectedIds); } isSelected(id: string): boolean { return this.set.has(id); } toggleSelect(id: string) { if (!id) return; this.selectedIds = this.selectedIds.includes(id) ? this.selectedIds.filter((item) => item !== id) : [...this.selectedIds, id]; } selectAll(allIds: string[]) { this.selectedIds = Array.from(new Set(allIds.filter(Boolean))); } toggleSelectAll(allIds: string[]) { if (!allIds || allIds.length === 0) { this.selectedIds = []; return; } const allSelected = allIds.every((id) => this.set.has(id)); if (allSelected) { this.selectedIds = this.selectedIds.filter((id) => !allIds.includes(id)); } else { this.selectedIds = Array.from(new Set([...this.selectedIds, ...allIds])); } } selectRange(fromId: string, toId: string, allOrderedIds: string[]) { const fromIdx = allOrderedIds.indexOf(fromId); const toIdx = allOrderedIds.indexOf(toId); if (fromIdx === -1 || toIdx === -1) return; const start = Math.min(fromIdx, toIdx); const end = Math.max(fromIdx, toIdx); const rangeIds = allOrderedIds.slice(start, end + 1); this.selectedIds = Array.from(new Set([...this.selectedIds, ...rangeIds])); } isAllSelected(allIds: string[]): boolean { if (!allIds || allIds.length === 0) return false; return allIds.every((id) => this.set.has(id)); } isPartiallySelected(allIds: string[]): boolean { if (!allIds || allIds.length === 0) return false; const some = allIds.some((id) => this.set.has(id)); const all = allIds.every((id) => this.set.has(id)); return some && !all; } clear() { this.selectedIds = []; } } describe("Empirical Challenger M3: Adversarial Filter Engine Stress", () => { const corpus: ProcessedReceipt[] = [ mockReceipt("rcpt-special-chars", { merchant: { name: 'Café "Kranzler" (GmbH & Co. KG) [Berlin]', address: "Kurfürstendamm 18/20", taxId: "DE999", confidence: 1.0 }, receiptNumber: "INV-2026/08+99$#1", date: { isoDate: "2026-08-15", time: "09:00", confidence: 1.0 }, totalAmount: { value: 12.50, confidence: 1.0 }, suggestedCategory: "Bewirtung", paymentMethod: "BAR", lineItems: [{ description: "Kaffee & Croissant (Set *Special*)", quantity: 1, price: 12.50, taxRate: 19 }], }), mockReceipt("rcpt-zero-gross", { merchant: { name: "Gratis Probe Store", address: "Alexanderplatz 1", taxId: "DE000", confidence: 1.0 }, receiptNumber: "ZERO-000", date: { isoDate: "2026-08-01", time: "10:00", confidence: 1.0 }, totalAmount: { value: 0.00, confidence: 1.0 }, netAmount: 0.00, taxBreakdown: [{ ratePercent: 19, taxAmount: 0.00, netAmount: 0.00 }], suggestedCategory: "Sonstiges", }), mockReceipt("rcpt-high-gross", { merchant: { name: "Apple Store Kurfürstendamm", address: "Berlin", taxId: "DE888", confidence: 1.0 }, receiptNumber: "APPL-9988", date: { isoDate: "2026-07-25", time: "14:00", confidence: 1.0 }, totalAmount: { value: 3499.00, confidence: 1.0 }, suggestedCategory: "Bürobedarf & IT", paymentMethod: "APPLE_PAY", }), mockReceipt("rcpt-pending-review", { merchant: { name: "Unbekannter Beleg", address: "", taxId: "", confidence: 0.4 }, receiptNumber: null, date: { isoDate: "2026-08-10", time: "12:00", confidence: 0.5 }, totalAmount: { value: 50.00, confidence: 0.4 }, suggestedCategory: "Sonstiges", validation: { isMathValid: false, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "merchant", reviewReason: "Geringe Erkennungsgenauigkeit", issues: [{ field: "merchant", severity: "error", message: "Händler unsicher" }], }, }), mockReceipt("rcpt-confirmed", { merchant: { name: "Tankstelle Jet", address: "Hamburg", taxId: "DE777", confidence: 0.9 }, receiptNumber: "JET-4421", date: { isoDate: "2026-08-12", time: "18:00", confidence: 0.9 }, totalAmount: { value: 85.40, confidence: 0.9 }, suggestedCategory: "Tanken & KFZ", validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: true, reviewField: "none", reviewReason: null, issues: [], }, }), ]; test("CHALLENGE-1.1: Complex regex meta-characters in search query do not throw", () => { const maliciousPatterns = [ ".*", "+", "?", "^", "$", "{1,3}", "()", "[]", "|", "\\d+", "[Berlin]", "*Special*", "$#1", '(GmbH & Co. KG)', '\\', '/.*+?^${}()|[]\\', ]; for (const pat of maliciousPatterns) { const results = applyFilters(corpus, { searchQuery: pat }); expect(Array.isArray(results)).toBe(true); } }); test("CHALLENGE-1.2: German comma vs English dot search resolves correctly", () => { // 12,50 and 12.50 should both find rcpt-special-chars const resDe = applyFilters(corpus, { searchQuery: "12,50" }); const resEn = applyFilters(corpus, { searchQuery: "12.50" }); expect(resDe.map((r) => r.id)).toEqual(["rcpt-special-chars"]); expect(resEn.map((r) => r.id)).toEqual(["rcpt-special-chars"]); // 85,40 and 85.40 const resJetDe = applyFilters(corpus, { searchQuery: "85,40" }); const resJetEn = applyFilters(corpus, { searchQuery: "85.40" }); expect(resJetDe.map((r) => r.id)).toEqual(["rcpt-confirmed"]); expect(resJetEn.map((r) => r.id)).toEqual(["rcpt-confirmed"]); }); test("CHALLENGE-1.3: Substring search in line items descriptions", () => { const resLineItem = applyFilters(corpus, { searchQuery: "Croissant" }); expect(resLineItem).toHaveLength(1); expect(resLineItem[0].id).toBe("rcpt-special-chars"); }); test("CHALLENGE-1.4: Inverted amount range (min > max) returns clean empty array without error", () => { const inverted = applyFilters(corpus, { amountRange: { min: 500, max: 100 }, }); expect(inverted).toHaveLength(0); }); test("CHALLENGE-1.5: Zero gross receipt filtering with min:0 max:0", () => { const zeroExact = applyFilters(corpus, { amountRange: { min: 0, max: 0 }, }); expect(zeroExact).toHaveLength(1); expect(zeroExact[0].id).toBe("rcpt-zero-gross"); }); test("CHALLENGE-1.6: Boundary amount filtering (< 50, 50-200, > 200)", () => { const under50 = applyFilters(corpus, { amountRange: { min: 0, max: 50 } }); // rcpt-special-chars (12.50), rcpt-zero-gross (0), rcpt-pending-review (50) expect(under50.map((r) => r.id)).toEqual(["rcpt-special-chars", "rcpt-zero-gross", "rcpt-pending-review"]); const between50and200 = applyFilters(corpus, { amountRange: { min: 50, max: 200 } }); // rcpt-pending-review (50), rcpt-confirmed (85.40) expect(between50and200.map((r) => r.id)).toEqual(["rcpt-pending-review", "rcpt-confirmed"]); const over200 = applyFilters(corpus, { amountRange: { min: 200, max: null } }); // rcpt-high-gross (3499) expect(over200.map((r) => r.id)).toEqual(["rcpt-high-gross"]); }); test("CHALLENGE-1.7: Status tier filtering accurately partitions dataset", () => { const scanned = applyFilters(corpus, { status: "scanned" }); const pending = applyFilters(corpus, { status: "pending" }); const confirmed = applyFilters(corpus, { status: "confirmed" }); expect(scanned.map((r) => r.id)).toEqual(["rcpt-special-chars", "rcpt-zero-gross", "rcpt-high-gross"]); expect(pending.map((r) => r.id)).toEqual(["rcpt-pending-review"]); expect(confirmed.map((r) => r.id)).toEqual(["rcpt-confirmed"]); expect(scanned.length + pending.length + confirmed.length).toBe(corpus.length); }); test("CHALLENGE-1.8: Category filtering with multi-criteria conjunction", () => { const bewirtung = applyFilters(corpus, { category: "Bewirtung", amountRange: { min: 10, max: 20 }, searchQuery: "Berlin", }); expect(bewirtung).toHaveLength(1); expect(bewirtung[0].id).toBe("rcpt-special-chars"); // Non-matching conjunction const emptyResult = applyFilters(corpus, { category: "Bewirtung", amountRange: { min: 50, max: 100 }, // rcpt-special-chars is 12.50 }); expect(emptyResult).toHaveLength(0); }); test("CHALLENGE-1.9: Whitespace-only and empty search query does not filter out valid records", () => { expect(applyFilters(corpus, { searchQuery: " " })).toHaveLength(corpus.length); expect(applyFilters(corpus, { searchQuery: "" })).toHaveLength(corpus.length); }); }); describe("Empirical Challenger M3: Selection Engine State & Invariant Stress", () => { const ids = Array.from({ length: 50 }, (_, i) => `item-${i}`); test("CHALLENGE-2.1: 1,000 Rapid toggles maintains strict set consistency", () => { const sm = new SelectionStateMachine(); for (let i = 0; i < 1000; i++) { sm.toggleSelect("rapid-id"); } // 1000 toggles = even number = unselected expect(sm.selectedIds).toHaveLength(0); expect(sm.isSelected("rapid-id")).toBe(false); sm.toggleSelect("rapid-id"); expect(sm.selectedIds).toEqual(["rapid-id"]); expect(sm.isSelected("rapid-id")).toBe(true); }); test("CHALLENGE-2.2: selectAll deduplicates and ignores falsy values", () => { const sm = new SelectionStateMachine(); sm.selectAll(["id-1", "id-1", "id-2", "", "id-3", "id-2"]); expect(sm.selectedIds).toEqual(["id-1", "id-2", "id-3"]); expect(sm.selectedIds.length).toBe(3); }); test("CHALLENGE-2.3: Forward, backward, and single range selections", () => { const sm = new SelectionStateMachine(); // Forward range 5 to 10 sm.selectRange("item-5", "item-10", ids); expect(sm.selectedIds).toHaveLength(6); expect(sm.selectedIds).toEqual(["item-5", "item-6", "item-7", "item-8", "item-9", "item-10"]); // Backward range 15 down to 12 sm.selectRange("item-15", "item-12", ids); expect(sm.selectedIds).toHaveLength(10); // 6 + 4 expect(sm.isSelected("item-12")).toBe(true); expect(sm.isSelected("item-15")).toBe(true); // Single item range sm.selectRange("item-20", "item-20", ids); expect(sm.isSelected("item-20")).toBe(true); }); test("CHALLENGE-2.4: Range selection with invalid / unlisted boundary IDs fails safely without mutation", () => { const sm = new SelectionStateMachine(["item-1"]); sm.selectRange("missing-from", "item-5", ids); expect(sm.selectedIds).toEqual(["item-1"]); sm.selectRange("item-5", "missing-to", ids); expect(sm.selectedIds).toEqual(["item-1"]); sm.selectRange("missing-1", "missing-2", ids); expect(sm.selectedIds).toEqual(["item-1"]); sm.selectRange("item-1", "item-2", []); // empty ordered list expect(sm.selectedIds).toEqual(["item-1"]); }); test("CHALLENGE-2.5: isAllSelected and isPartiallySelected state predicates", () => { const sm = new SelectionStateMachine(); const testIds = ["a", "b", "c"]; expect(sm.isAllSelected(testIds)).toBe(false); expect(sm.isPartiallySelected(testIds)).toBe(false); sm.toggleSelect("a"); expect(sm.isAllSelected(testIds)).toBe(false); expect(sm.isPartiallySelected(testIds)).toBe(true); sm.toggleSelect("b"); sm.toggleSelect("c"); expect(sm.isAllSelected(testIds)).toBe(true); expect(sm.isPartiallySelected(testIds)).toBe(false); sm.toggleSelectAll(testIds); // all selected -> should deselect all expect(sm.selectedIds).toHaveLength(0); expect(sm.isAllSelected(testIds)).toBe(false); }); test("CHALLENGE-2.6: High-scale selection (1,000 items) executes in under 15ms", () => { const largeList = Array.from({ length: 1000 }, (_, i) => `large-${i}`); const sm = new SelectionStateMachine(); const start = performance.now(); sm.selectAll(largeList); expect(sm.isAllSelected(largeList)).toBe(true); sm.toggleSelectAll(largeList); expect(sm.selectedIds).toHaveLength(0); const duration = performance.now() - start; expect(duration).toBeLessThan(200); }); }); describe("Empirical Challenger M3: Inline Recalculation & Financial Math Stress", () => { test("CHALLENGE-3.1: Gross amount change on 19% single-rate receipt", () => { const r = mockReceipt("single-rate-19", { totalAmount: { value: 119.0, confidence: 1.0 }, netAmount: 100.0, taxBreakdown: [{ ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 }], }); const updated = { ...r, totalAmount: { ...r.totalAmount, value: 238.0 }, editedFields: { totalAmount: true }, }; const recalculated = recalculateReceipt(updated, { editedField: "totalAmount" }); expect(recalculated.totalAmount.value).toBe(238.0); expect(recalculated.netAmount).toBe(200.0); expect(recalculated.taxBreakdown[0].taxAmount).toBe(38.0); expect(recalculated.validation.isMathValid).toBe(true); }); test("CHALLENGE-3.2: Gross amount set to 0.00 € maintains zero math without NaN or Division by Zero", () => { const r = mockReceipt("zero-gross-test"); const updated = { ...r, totalAmount: { ...r.totalAmount, value: 0.0 }, editedFields: { totalAmount: true }, }; const recalculated = recalculateReceipt(updated, { editedField: "totalAmount" }); expect(recalculated.totalAmount.value).toBe(0.0); expect(recalculated.netAmount).toBe(0.0); expect(recalculated.taxBreakdown[0].taxAmount).toBe(0.0); expect(recalculated.validation.isMathValid).toBe(true); }); test("CHALLENGE-3.3: Gross change on mixed 7% and 19% VAT rates redistributes proportionately", () => { const r = mockReceipt("mixed-rate-test", { totalAmount: { value: 100.0, confidence: 1.0 }, netAmount: 88.0, taxBreakdown: [ { ratePercent: 7, taxAmount: 3.5, netAmount: 50.0 }, { ratePercent: 19, taxAmount: 8.5, netAmount: 38.0 }, ], }); const updated = { ...r, totalAmount: { ...r.totalAmount, value: 200.0 }, editedFields: { totalAmount: true }, }; const recalculated = recalculateReceipt(updated, { editedField: "totalAmount" }); expect(recalculated.totalAmount.value).toBe(200.0); expect(recalculated.validation.isMathValid).toBe(true); // Net + total tax equals gross const totalTax = recalculated.taxBreakdown.reduce((sum, item) => sum + (item.taxAmount || 0), 0); expect(Math.round(((recalculated.netAmount || 0) + totalTax) * 100) / 100).toBe(200.0); }); test("CHALLENGE-3.4: Editing non-financial fields (date, merchant) updates audit flag without corrupting math", () => { const r = mockReceipt("audit-flag-test"); const updated = { ...r, merchant: { ...r.merchant, name: "Neuer Bäcker" }, date: { ...r.date, isoDate: "2026-08-01" }, editedFields: { merchant: true, date: true }, }; const recalculated = recalculateReceipt(updated, { editedField: "merchant" }); expect(recalculated.merchant.name).toBe("Neuer Bäcker"); expect(recalculated.date.isoDate).toBe("2026-08-01"); expect(recalculated.totalAmount.value).toBe(100.0); expect(recalculated.netAmount).toBe(84.03); expect(recalculated.editedFields?.merchant).toBe(true); expect(recalculated.editedFields?.date).toBe(true); }); }); describe("Empirical Challenger M3: Batch Export Generator Stress", () => { test("CHALLENGE-4.1: Dual-sheet Excel generation handles 50 receipts with varying tax configurations", async () => { const receipts = Array.from({ length: 50 }, (_, i) => mockReceipt(`batch-xl-${i}`, { totalAmount: { value: 10.0 * (i + 1), confidence: 1.0 }, suggestedCategory: i % 2 === 0 ? "Bewirtung" : "Reisekosten & Hotel", }) ); const buffer = await generateDualSheetExcel(receipts); expect(buffer).toBeDefined(); expect(buffer.length).toBeGreaterThan(10000); }); test("CHALLENGE-4.2: Accounting CSV generation escapes special CSV characters and enforces UTF-8 BOM", () => { const receipt = mockReceipt("csv-escape-test", { merchant: { name: 'Firma "Test;Semikolon & Neuer\nZeilenumbruch" GmbH', address: "Köln", taxId: "DE1", confidence: 1.0 }, totalAmount: { value: 1234.56, confidence: 1.0 }, suggestedCategory: "Bewirtung", }); const csv = generateAccountingCsv([receipt]); expect(csv).toBeDefined(); expect(csv.charCodeAt(0)).toBe(0xfeff); // UTF-8 BOM expect(csv).toContain("1234,56"); expect(csv).toContain("Firma"); }); });