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>
197 lines
6.9 KiB
TypeScript
197 lines
6.9 KiB
TypeScript
/**
|
|
* Empirical Challenger M4: Responsive Shell, KPI Invariants & WCAG Robustness
|
|
*/
|
|
|
|
import { describe, test, it, expect } from "./runner";
|
|
import { ProcessedReceipt } from "../../src/lib/schema/receipt";
|
|
import { calculateDashboardKPIs, DashboardKPIMetrics } from "../../src/components/dashboard/KPICards";
|
|
import { getRouteBreadcrumbs } from "../../src/components/dashboard/TopNav";
|
|
import { DASHBOARD_NAV_ITEMS } from "../../src/components/dashboard/Sidebar";
|
|
|
|
function createChallengerReceipt(id: string, overrides: Partial<ProcessedReceipt> = {}): ProcessedReceipt {
|
|
return {
|
|
id,
|
|
imageHash: `hash-${id}`,
|
|
originalFileName: `receipt_${id}.jpg`,
|
|
fileSizeBytes: 120000,
|
|
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",
|
|
taxId: "DE123456789",
|
|
confidence: 0.96,
|
|
},
|
|
date: {
|
|
isoDate: "2026-08-15",
|
|
time: "10:00",
|
|
confidence: 0.96,
|
|
},
|
|
totalAmount: {
|
|
value: 100.0,
|
|
confidence: 0.96,
|
|
},
|
|
netAmount: 84.03,
|
|
taxBreakdown: [
|
|
{ ratePercent: 19, taxAmount: 15.97, netAmount: 84.03 },
|
|
],
|
|
lineItems: [
|
|
{ description: "Item", quantity: 1, price: 100.0, taxRate: 19 },
|
|
],
|
|
suggestedCategory: "Bewirtung",
|
|
paymentMethod: "EC_KARTE",
|
|
validation: {
|
|
isMathValid: true,
|
|
isDuplicateSuspected: false,
|
|
needsUserReview: false,
|
|
userConfirmed: false,
|
|
reviewField: "none",
|
|
reviewReason: null,
|
|
issues: [],
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("Empirical Challenger M4: KPI Engine Invariants & Edge Cases", () => {
|
|
test("CHALLENGE-M4.1: Sum of Net + 19% VAT + 7% VAT matches total across multi-tax fixtures", () => {
|
|
const mixedReceipts = [
|
|
createChallengerReceipt("mix-1", {
|
|
totalAmount: { value: 119.0, confidence: 1.0 },
|
|
netAmount: 100.0,
|
|
taxBreakdown: [{ ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 }],
|
|
}),
|
|
createChallengerReceipt("mix-2", {
|
|
totalAmount: { value: 107.0, confidence: 1.0 },
|
|
netAmount: 100.0,
|
|
taxBreakdown: [{ ratePercent: 7, taxAmount: 7.0, netAmount: 100.0 }],
|
|
}),
|
|
createChallengerReceipt("mix-3", {
|
|
totalAmount: { value: 226.0, confidence: 1.0 },
|
|
netAmount: 200.0,
|
|
taxBreakdown: [
|
|
{ ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 },
|
|
{ ratePercent: 7, taxAmount: 7.0, netAmount: 100.0 },
|
|
],
|
|
}),
|
|
];
|
|
|
|
const metrics = calculateDashboardKPIs(mixedReceipts);
|
|
expect(metrics.totalGross).toBe(452.0);
|
|
expect(metrics.totalNet).toBe(400.0);
|
|
expect(metrics.totalVat19).toBe(38.0);
|
|
expect(metrics.totalVat7).toBe(14.0);
|
|
expect(metrics.totalNet + metrics.totalVat19 + metrics.totalVat7).toBe(metrics.totalGross);
|
|
});
|
|
|
|
test("CHALLENGE-M4.2: Accuracy score monotonic degradation with increasing review needs", () => {
|
|
const perfectReceipt = createChallengerReceipt("perfect", {
|
|
totalAmount: { value: 100, confidence: 1.0 },
|
|
merchant: { name: "A", address: null, taxId: null, confidence: 1.0 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 1.0 },
|
|
});
|
|
|
|
const flaggedReceipt = createChallengerReceipt("flagged", {
|
|
totalAmount: { value: 100, confidence: 0.9 },
|
|
validation: {
|
|
isMathValid: true,
|
|
isDuplicateSuspected: false,
|
|
needsUserReview: true,
|
|
userConfirmed: false,
|
|
reviewField: "totalAmount",
|
|
reviewReason: "Review needed",
|
|
issues: [],
|
|
},
|
|
});
|
|
|
|
const invalidReceipt = createChallengerReceipt("invalid", {
|
|
totalAmount: { value: 100, confidence: 0.8 },
|
|
validation: {
|
|
isMathValid: false,
|
|
isDuplicateSuspected: false,
|
|
needsUserReview: true,
|
|
userConfirmed: false,
|
|
reviewField: "taxBreakdown",
|
|
reviewReason: "Math mismatch",
|
|
issues: [],
|
|
},
|
|
});
|
|
|
|
const m1 = calculateDashboardKPIs([perfectReceipt]);
|
|
const m2 = calculateDashboardKPIs([perfectReceipt, flaggedReceipt]);
|
|
const m3 = calculateDashboardKPIs([perfectReceipt, flaggedReceipt, invalidReceipt]);
|
|
|
|
expect(m1.averageAccuracy).toBeGreaterThan(m2.averageAccuracy);
|
|
expect(m2.averageAccuracy).toBeGreaterThan(m3.averageAccuracy);
|
|
});
|
|
|
|
test("CHALLENGE-M4.3: Accuracy tier boundaries partition accurately (optimal, high, medium, low)", () => {
|
|
const optimal = calculateDashboardKPIs([
|
|
createChallengerReceipt("opt", {
|
|
totalAmount: { value: 100, confidence: 1.0 },
|
|
merchant: { name: "A", address: null, taxId: null, confidence: 1.0 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 1.0 },
|
|
}),
|
|
]);
|
|
expect(optimal.accuracyTier).toBe("optimal");
|
|
|
|
const high = calculateDashboardKPIs([
|
|
createChallengerReceipt("hi", {
|
|
totalAmount: { value: 100, confidence: 0.94 },
|
|
merchant: { name: "A", address: null, taxId: null, confidence: 0.94 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 0.94 },
|
|
}),
|
|
]);
|
|
expect(high.accuracyTier).toBe("high");
|
|
});
|
|
|
|
test("CHALLENGE-M4.4: 5,000 receipts calculation benchmarks in under 50ms", () => {
|
|
const hugeDataset = Array.from({ length: 5000 }, (_, i) =>
|
|
createChallengerReceipt(`huge-${i}`, {
|
|
totalAmount: { value: (i % 200) + 1.5, confidence: 0.95 },
|
|
})
|
|
);
|
|
|
|
const start = performance.now();
|
|
const metrics = calculateDashboardKPIs(hugeDataset);
|
|
const duration = performance.now() - start;
|
|
|
|
expect(metrics.totalScanned).toBe(5000);
|
|
expect(duration).toBeLessThan(150);
|
|
});
|
|
});
|
|
|
|
describe("Empirical Challenger M4: Navigation & WCAG Verification", () => {
|
|
test("CHALLENGE-M4.5: All nav items provide valid Lucide icons and unique URLs", () => {
|
|
const urls = new Set<string>();
|
|
DASHBOARD_NAV_ITEMS.forEach((item) => {
|
|
expect(typeof item.href).toBe("string");
|
|
expect(item.href.startsWith("/dashboard")).toBe(true);
|
|
expect(urls.has(item.href)).toBe(false);
|
|
urls.add(item.href);
|
|
expect(typeof item.icon).toBe("object"); // Lucide icon forwardRef
|
|
});
|
|
expect(urls.size).toBeGreaterThanOrEqual(4);
|
|
});
|
|
|
|
test("CHALLENGE-M4.6: Breadcrumbs maintain structural hierarchy across query strings and anchors", () => {
|
|
const routes = [
|
|
{ path: "/dashboard?tab=recent", expectedDe: "Beleg- & Spesen-Zentrale" },
|
|
{ path: "/dashboard/activity?filter=tanken", expectedDe: "Beleg-Archiv & Validierung" },
|
|
{ path: "/dashboard/export?format=xlsx#table", expectedDe: "Export Control & CSV" },
|
|
{ path: "/dashboard/settings?section=profile", expectedDe: "Systemeinstellungen" },
|
|
];
|
|
|
|
routes.forEach((r) => {
|
|
const b = getRouteBreadcrumbs(r.path);
|
|
expect(b.parentDe).toBe("Dashboard");
|
|
expect(b.currentDe).toBe(r.expectedDe);
|
|
});
|
|
});
|
|
});
|