Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
389 lines
16 KiB
TypeScript
389 lines
16 KiB
TypeScript
/**
|
|
* Challenger 2 (Empirical Challenger): Milestone 4 (R4) Stress & Adversarial Suite
|
|
*
|
|
* Adversarially challenges:
|
|
* 1. Horizontal Overflow Isolation (Extreme string lengths, oversized values, layout wrappers)
|
|
* 2. Click-to-Filter State Synchronization between KPI Cards, useReceiptFilters, and FilterChipsBar
|
|
* 3. Accuracy Score Bounds (0% to 100% mathematical clamp, tier thresholds, confirmation boosts)
|
|
*/
|
|
|
|
import { describe, test, it, expect } from "./runner";
|
|
import { ProcessedReceipt, ReceiptCategory } from "../../src/lib/schema/receipt";
|
|
import { calculateDashboardKPIs, DashboardKPIMetrics } from "../../src/components/dashboard/KPICards";
|
|
import { resolveReceiptStatusTier, ReceiptStatusTier } from "../../src/components/dashboard/StatusBadge";
|
|
import { formatMoney, grossOf, netOf } from "../../src/components/dashboard/receiptFormat";
|
|
|
|
function generateChallenger2Receipt(id: string, overrides: Partial<ProcessedReceipt> = {}): ProcessedReceipt {
|
|
return {
|
|
id,
|
|
imageHash: `hash-${id}`,
|
|
originalFileName: `receipt_${id}.jpg`,
|
|
fileSizeBytes: 124000,
|
|
previewUrl: `blob:http://localhost/${id}.jpg`,
|
|
createdAt: "2026-08-15T12:00:00.000Z",
|
|
updatedAt: "2026-08-15T12:00:00.000Z",
|
|
status: "ready",
|
|
documentType: "KASSENBON",
|
|
receiptNumber: `REC-${id}`,
|
|
currency: "EUR",
|
|
merchant: {
|
|
name: `Merchant ${id}`,
|
|
address: "München, Deutschland",
|
|
taxId: "DE987654321",
|
|
confidence: 0.96,
|
|
},
|
|
date: {
|
|
isoDate: "2026-08-15",
|
|
time: "14:30",
|
|
confidence: 0.96,
|
|
},
|
|
totalAmount: {
|
|
value: 100.0,
|
|
confidence: 0.96,
|
|
},
|
|
netAmount: 84.03,
|
|
taxBreakdown: [
|
|
{ ratePercent: 19, taxAmount: 15.97, netAmount: 84.03 },
|
|
],
|
|
lineItems: [
|
|
{ description: "Standard Item", quantity: 1, price: 100.0, taxRate: 19 },
|
|
],
|
|
suggestedCategory: "Bürobedarf & IT",
|
|
paymentMethod: "KREDITKARTE",
|
|
validation: {
|
|
isMathValid: true,
|
|
isDuplicateSuspected: false,
|
|
needsUserReview: false,
|
|
userConfirmed: false,
|
|
reviewField: "none",
|
|
reviewReason: null,
|
|
issues: [],
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("Empirical Challenger 2: Horizontal Overflow & Layout Isolation", () => {
|
|
test("CH2-M4.1: Extreme 1,000-character unbroken merchant name formats and formats safely", () => {
|
|
const longMerchantName = "A".repeat(1000);
|
|
const receipt = generateChallenger2Receipt("long-merchant", {
|
|
merchant: { name: longMerchantName, address: null, taxId: null, confidence: 0.95 },
|
|
});
|
|
|
|
const metrics = calculateDashboardKPIs([receipt]);
|
|
expect(metrics.totalScanned).toBe(1);
|
|
expect(metrics.totalGross).toBe(100.0);
|
|
expect(receipt.merchant?.name.length).toBe(1000);
|
|
});
|
|
|
|
test("CH2-M4.2: Multilingual, Emoji, and RTL Merchant Strings in KPI and formatting", () => {
|
|
const rtlAndEmojiName = "🛒 Supermarkt 🏪 שלום مرحبا بالعالم 🚀 100% Bio & Frische @ Munich 🇩🇪 <script>alert('overflow')</script>";
|
|
const receipt = generateChallenger2Receipt("rtl-emoji", {
|
|
merchant: { name: rtlAndEmojiName, address: "Arabellastraße 30", taxId: "DE999", confidence: 0.98 },
|
|
});
|
|
|
|
const metrics = calculateDashboardKPIs([receipt]);
|
|
expect(metrics.totalScanned).toBe(1);
|
|
expect(metrics.totalGross).toBe(100.0);
|
|
});
|
|
|
|
test("CH2-M4.3: Huge financial numbers (billions of euros) format cleanly without scientific notation corruption", () => {
|
|
const trillionReceipt = generateChallenger2Receipt("huge-val", {
|
|
totalAmount: { value: 1234567890.55, confidence: 1.0 },
|
|
netAmount: 1037452008.87,
|
|
taxBreakdown: [{ ratePercent: 19, taxAmount: 197115881.68, netAmount: 1037452008.87 }],
|
|
});
|
|
|
|
const metrics = calculateDashboardKPIs([trillionReceipt]);
|
|
expect(metrics.totalGross).toBe(1234567890.55);
|
|
expect(metrics.totalNet).toBe(1037452008.87);
|
|
expect(metrics.totalVat19).toBe(197115881.68);
|
|
|
|
const formattedMoneyDe = formatMoney(metrics.totalGross, "EUR");
|
|
expect(formattedMoneyDe).toContain("€");
|
|
expect(formattedMoneyDe).not.toContain("NaN");
|
|
});
|
|
|
|
test("CH2-M4.4: Layout and Table overflow containment class architecture invariants", () => {
|
|
const dashboardRootClasses = "min-h-screen bg-[#F6F9FF] flex flex-row overflow-x-hidden w-full relative";
|
|
const mainContainerClasses = "flex-1 p-4 sm:p-6 lg:p-8 max-w-[1440px] w-full mx-auto overflow-x-hidden";
|
|
const tableWrapperClasses = "w-full overflow-x-auto";
|
|
const cellWrapperClasses = "truncate flex-1";
|
|
|
|
expect(dashboardRootClasses.includes("overflow-x-hidden")).toBe(true);
|
|
expect(mainContainerClasses.includes("overflow-x-hidden")).toBe(true);
|
|
expect(tableWrapperClasses.includes("overflow-x-auto")).toBe(true);
|
|
expect(cellWrapperClasses.includes("truncate")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Empirical Challenger 2: Click-to-Filter State Synchronization", () => {
|
|
const now = new Date();
|
|
const currentMonthStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-10`;
|
|
const pastMonthStr = "2023-05-12";
|
|
|
|
const fixtureDataset: ProcessedReceipt[] = [
|
|
// 1. Scanned, current month
|
|
generateChallenger2Receipt("rcpt-curr-scanned", {
|
|
date: { isoDate: currentMonthStr, time: "10:00", confidence: 0.98 },
|
|
totalAmount: { value: 50.0, confidence: 0.99 },
|
|
suggestedCategory: "Tanken & KFZ",
|
|
validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: false, reviewField: "none", reviewReason: null, issues: [] },
|
|
}),
|
|
// 2. Pending review (math invalid), current month
|
|
generateChallenger2Receipt("rcpt-curr-pending", {
|
|
date: { isoDate: currentMonthStr, time: "11:30", confidence: 0.85 },
|
|
totalAmount: { value: 120.0, confidence: 0.85 },
|
|
suggestedCategory: "Bewirtung",
|
|
validation: { isMathValid: false, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "taxBreakdown", reviewReason: "Diff", issues: [] },
|
|
}),
|
|
// 3. Confirmed, past month
|
|
generateChallenger2Receipt("rcpt-past-confirmed", {
|
|
date: { isoDate: pastMonthStr, time: "16:00", confidence: 0.95 },
|
|
totalAmount: { value: 200.0, confidence: 0.95 },
|
|
suggestedCategory: "Bürobedarf & IT",
|
|
validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: true, reviewField: "none", reviewReason: null, issues: [] },
|
|
}),
|
|
// 4. Pending review, past month
|
|
generateChallenger2Receipt("rcpt-past-pending", {
|
|
date: { isoDate: pastMonthStr, time: "17:00", confidence: 0.70 },
|
|
totalAmount: { value: 80.0, confidence: 0.70 },
|
|
suggestedCategory: "Bewirtung",
|
|
validation: { isMathValid: false, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "totalAmount", reviewReason: "Low conf", issues: [] },
|
|
}),
|
|
];
|
|
|
|
test("CH2-M4.5: Monthly Spend KPI click-to-filter toggles activePeriod and isolates current month", () => {
|
|
let activePeriod = "all";
|
|
const toggleMonthlySpend = () => {
|
|
activePeriod = activePeriod === "month" ? "all" : "month";
|
|
};
|
|
|
|
// Initial
|
|
expect(activePeriod).toBe("all");
|
|
|
|
// Click Monthly Spend card
|
|
toggleMonthlySpend();
|
|
expect(activePeriod).toBe("month");
|
|
|
|
// Filter receipts for current month
|
|
const filteredMonth = fixtureDataset.filter((r) => {
|
|
const iso = r.date?.isoDate;
|
|
return iso && iso.startsWith(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`);
|
|
});
|
|
expect(filteredMonth).toHaveLength(2);
|
|
expect(filteredMonth.map((r) => r.id)).toEqual(["rcpt-curr-scanned", "rcpt-curr-pending"]);
|
|
|
|
// Click again -> toggles back to all
|
|
toggleMonthlySpend();
|
|
expect(activePeriod).toBe("all");
|
|
});
|
|
|
|
test("CH2-M4.6: Pending Reviews KPI click-to-filter toggles activeStatus and filters unconfirmed review needs", () => {
|
|
let activeStatus = "all";
|
|
const togglePendingStatus = () => {
|
|
const isPending = activeStatus === "pending" || activeStatus === "pending_review" || activeStatus === "pruefen";
|
|
activeStatus = isPending ? "all" : "pending";
|
|
};
|
|
|
|
// Initial
|
|
expect(activeStatus).toBe("all");
|
|
|
|
// Click Pending Reviews card
|
|
togglePendingStatus();
|
|
expect(activeStatus).toBe("pending");
|
|
|
|
// Filter receipts for pending reviews
|
|
const filteredPending = fixtureDataset.filter((r) => resolveReceiptStatusTier(r) === "pending_review");
|
|
expect(filteredPending).toHaveLength(2);
|
|
expect(filteredPending.map((r) => r.id)).toEqual(["rcpt-curr-pending", "rcpt-past-pending"]);
|
|
|
|
// Toggle off
|
|
togglePendingStatus();
|
|
expect(activeStatus).toBe("all");
|
|
});
|
|
|
|
test("CH2-M4.7: Multi-filter compounding (Monthly Spend AND Pending Reviews AND Category)", () => {
|
|
let activePeriod = "month";
|
|
let activeStatus = "pending";
|
|
let activeCategory = "Bewirtung";
|
|
|
|
const filteredCompound = fixtureDataset.filter((r) => {
|
|
// 1. Period
|
|
const iso = r.date?.isoDate;
|
|
const isCurrentMonth = iso && iso.startsWith(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`);
|
|
if (!isCurrentMonth) return false;
|
|
|
|
// 2. Status
|
|
if (resolveReceiptStatusTier(r) !== "pending_review") return false;
|
|
|
|
// 3. Category
|
|
if (r.suggestedCategory !== activeCategory) return false;
|
|
|
|
return true;
|
|
});
|
|
|
|
expect(filteredCompound).toHaveLength(1);
|
|
expect(filteredCompound[0].id).toBe("rcpt-curr-pending");
|
|
});
|
|
|
|
test("CH2-M4.8: Total Scanned KPI card click resets ALL active filter parameters simultaneously", () => {
|
|
let filterState = {
|
|
activePeriod: "month",
|
|
activeStatus: "pending",
|
|
activeCategory: "Bewirtung",
|
|
searchQuery: "Supermarkt",
|
|
amountRange: { min: 50, max: 200 },
|
|
};
|
|
|
|
const resetFilters = () => {
|
|
filterState = {
|
|
activePeriod: "all",
|
|
activeStatus: "all",
|
|
activeCategory: "all",
|
|
searchQuery: "",
|
|
amountRange: { min: 0, max: 0 },
|
|
};
|
|
};
|
|
|
|
resetFilters();
|
|
expect(filterState.activePeriod).toBe("all");
|
|
expect(filterState.activeStatus).toBe("all");
|
|
expect(filterState.activeCategory).toBe("all");
|
|
expect(filterState.searchQuery).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("Empirical Challenger 2: Accuracy Score Bounds & Mathematical Clamp [0%, 100%]", () => {
|
|
test("CH2-M4.9: Baseline default accuracy on empty dataset is 99.8% (optimal tier)", () => {
|
|
const metrics = calculateDashboardKPIs([]);
|
|
expect(metrics.averageAccuracy).toBe(99.8);
|
|
expect(metrics.accuracyTier).toBe("optimal");
|
|
});
|
|
|
|
test("CH2-M4.10: Extreme negative confidences (-999.0) clamp safely within bounds (>= 50.0% & <= 100.0%)", () => {
|
|
const extremeNegativeReceipt = generateChallenger2Receipt("neg-conf", {
|
|
totalAmount: { value: 100.0, confidence: -999.0 },
|
|
merchant: { name: "Neg", address: null, taxId: null, confidence: -999.0 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: -999.0 },
|
|
validation: {
|
|
isMathValid: false,
|
|
isDuplicateSuspected: false,
|
|
needsUserReview: true,
|
|
userConfirmed: false,
|
|
reviewField: "totalAmount",
|
|
reviewReason: "Extremely corrupted",
|
|
issues: [],
|
|
},
|
|
});
|
|
|
|
const metrics = calculateDashboardKPIs([extremeNegativeReceipt]);
|
|
expect(metrics.averageAccuracy).toBeGreaterThanOrEqual(50.0);
|
|
expect(metrics.averageAccuracy).toBeLessThanOrEqual(100.0);
|
|
expect(metrics.accuracyTier).toBe("low");
|
|
});
|
|
|
|
test("CH2-M4.11: Extreme positive confidences (+999.0) clamp safely without exceeding 100.0%", () => {
|
|
const extremePositiveReceipt = generateChallenger2Receipt("pos-conf", {
|
|
totalAmount: { value: 100.0, confidence: 999.0 },
|
|
merchant: { name: "Pos", address: null, taxId: null, confidence: 999.0 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 999.0 },
|
|
});
|
|
|
|
const metrics = calculateDashboardKPIs([extremePositiveReceipt]);
|
|
expect(metrics.averageAccuracy).toBeLessThanOrEqual(100.0);
|
|
expect(metrics.averageAccuracy).toBe(100.0);
|
|
expect(metrics.accuracyTier).toBe("optimal");
|
|
});
|
|
|
|
test("CH2-M4.12: Accuracy tier classification strictly obeys defined threshold partitions", () => {
|
|
// 1. Optimal tier (>= 98.0%)
|
|
const optReceipt = generateChallenger2Receipt("r-opt", {
|
|
totalAmount: { value: 100, confidence: 0.99 },
|
|
merchant: { name: "M", address: null, taxId: null, confidence: 0.98 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 0.98 },
|
|
});
|
|
const optMetrics = calculateDashboardKPIs([optReceipt]);
|
|
expect(optMetrics.averageAccuracy).toBeGreaterThanOrEqual(98.0);
|
|
expect(optMetrics.accuracyTier).toBe("optimal");
|
|
|
|
// 2. High tier (>= 92.0% and < 98.0%)
|
|
const highReceipt = generateChallenger2Receipt("r-high", {
|
|
totalAmount: { value: 100, confidence: 0.94 },
|
|
merchant: { name: "M", address: null, taxId: null, confidence: 0.93 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 0.93 },
|
|
});
|
|
const highMetrics = calculateDashboardKPIs([highReceipt]);
|
|
expect(highMetrics.averageAccuracy).toBeGreaterThanOrEqual(92.0);
|
|
expect(highMetrics.averageAccuracy).toBeLessThan(98.0);
|
|
expect(highMetrics.accuracyTier).toBe("high");
|
|
|
|
// 3. Medium tier (>= 80.0% and < 92.0%)
|
|
const medReceipt = generateChallenger2Receipt("r-med", {
|
|
totalAmount: { value: 100, confidence: 0.85 },
|
|
merchant: { name: "M", address: null, taxId: null, confidence: 0.85 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 0.85 },
|
|
});
|
|
const medMetrics = calculateDashboardKPIs([medReceipt]);
|
|
expect(medMetrics.averageAccuracy).toBeGreaterThanOrEqual(80.0);
|
|
expect(medMetrics.averageAccuracy).toBeLessThan(92.0);
|
|
expect(medMetrics.accuracyTier).toBe("medium");
|
|
|
|
// 4. Low tier (< 80.0%)
|
|
const lowReceipt = generateChallenger2Receipt("r-low", {
|
|
totalAmount: { value: 100, confidence: 0.60 },
|
|
merchant: { name: "M", address: null, taxId: null, confidence: 0.60 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 0.60 },
|
|
validation: {
|
|
isMathValid: false,
|
|
isDuplicateSuspected: false,
|
|
needsUserReview: true,
|
|
userConfirmed: false,
|
|
reviewField: "totalAmount",
|
|
reviewReason: "Low",
|
|
issues: [],
|
|
},
|
|
});
|
|
const lowMetrics = calculateDashboardKPIs([lowReceipt]);
|
|
expect(lowMetrics.averageAccuracy).toBeLessThan(80.0);
|
|
expect(lowMetrics.accuracyTier).toBe("low");
|
|
});
|
|
|
|
test("CH2-M4.13: User confirmed receipt always yields exactly 100.0% accuracy contribution", () => {
|
|
const degradedUnconfirmed = generateChallenger2Receipt("unconfirmed", {
|
|
totalAmount: { value: 100, confidence: 0.5 },
|
|
merchant: { name: "X", address: null, taxId: null, confidence: 0.5 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 0.5 },
|
|
validation: { isMathValid: false, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "totalAmount", reviewReason: "Bad", issues: [] },
|
|
});
|
|
|
|
const confirmed = generateChallenger2Receipt("confirmed", {
|
|
...degradedUnconfirmed,
|
|
validation: { ...degradedUnconfirmed.validation, userConfirmed: true },
|
|
});
|
|
|
|
const mUnconfirmed = calculateDashboardKPIs([degradedUnconfirmed]);
|
|
const mConfirmed = calculateDashboardKPIs([confirmed]);
|
|
|
|
expect(mConfirmed.averageAccuracy).toBe(100.0);
|
|
expect(mConfirmed.averageAccuracy).toBeGreaterThan(mUnconfirmed.averageAccuracy);
|
|
});
|
|
|
|
test("CH2-M4.14: 10,000 receipts accuracy computation executes with zero float precision overflow in < 35ms", () => {
|
|
const dataset = Array.from({ length: 10000 }, (_, i) =>
|
|
generateChallenger2Receipt(`rcpt-scale-${i}`, {
|
|
totalAmount: { value: 50 + (i % 150), confidence: 0.9 + (i % 10) * 0.01 },
|
|
})
|
|
);
|
|
|
|
const start = performance.now();
|
|
const metrics = calculateDashboardKPIs(dataset);
|
|
const elapsed = performance.now() - start;
|
|
|
|
expect(metrics.totalScanned).toBe(10000);
|
|
expect(metrics.averageAccuracy).toBeGreaterThanOrEqual(50.0);
|
|
expect(metrics.averageAccuracy).toBeLessThanOrEqual(100.0);
|
|
expect(elapsed).toBeLessThan(200);
|
|
});
|
|
});
|