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>
209 lines
7.4 KiB
TypeScript
209 lines
7.4 KiB
TypeScript
/**
|
|
* Milestone 4 (R4): Adversarial Stress & Shell Robustness Tests
|
|
*
|
|
* Verifies:
|
|
* 1. KPI Calculation Resilience on Extreme, Negative, and Malformed Data
|
|
* 2. High-Scale Metric Computation Performance (1,000+ items in < 15ms)
|
|
* 3. Mobile Drawer State Invariants & Rapid Toggle Stress
|
|
* 4. Temporal Date Partitioning under Adversarial ISO Date Strings
|
|
* 5. Zenith Color Palette Mathematical Contrast Invariants
|
|
*/
|
|
|
|
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";
|
|
|
|
function generateStressReceipt(id: string, overrides: Partial<ProcessedReceipt> = {}): ProcessedReceipt {
|
|
return {
|
|
id,
|
|
imageHash: `hash-${id}`,
|
|
originalFileName: `receipt_${id}.jpg`,
|
|
fileSizeBytes: 150000,
|
|
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: "Berlin",
|
|
taxId: "DE123456789",
|
|
confidence: 0.95,
|
|
},
|
|
date: {
|
|
isoDate: "2026-08-15",
|
|
time: "10:00",
|
|
confidence: 0.95,
|
|
},
|
|
totalAmount: {
|
|
value: 100.0,
|
|
confidence: 0.95,
|
|
},
|
|
netAmount: 84.03,
|
|
taxBreakdown: [
|
|
{ ratePercent: 19, taxAmount: 15.97, netAmount: 84.03 },
|
|
],
|
|
lineItems: [
|
|
{ description: "Item 1", 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,
|
|
};
|
|
}
|
|
|
|
describe("Milestone 4 (R4) Adversarial: KPI Calculation & Mathematical Extreme Values", () => {
|
|
test("ADV-M4.1: Calculates KPIs on receipts with 0.00 € totals and missing optional tax arrays", () => {
|
|
const zeroReceipts = [
|
|
generateStressReceipt("zero-1", {
|
|
totalAmount: { value: 0.0, confidence: 1.0 },
|
|
netAmount: 0.0,
|
|
taxBreakdown: [],
|
|
}),
|
|
generateStressReceipt("zero-2", {
|
|
totalAmount: { value: 0.0, confidence: 1.0 },
|
|
netAmount: 0.0,
|
|
taxBreakdown: [],
|
|
}),
|
|
];
|
|
|
|
const metrics = calculateDashboardKPIs(zeroReceipts);
|
|
expect(metrics.totalScanned).toBe(2);
|
|
expect(metrics.totalGross).toBe(0.0);
|
|
expect(metrics.totalNet).toBe(0.0);
|
|
expect(metrics.totalVat19).toBe(0.0);
|
|
expect(metrics.totalVat7).toBe(0.0);
|
|
expect(metrics.averageAccuracy).toBeGreaterThanOrEqual(95.0);
|
|
});
|
|
|
|
test("ADV-M4.2: Handles negative amounts (refunds / credit notes) without NaN or division by zero", () => {
|
|
const refundReceipts = [
|
|
generateStressReceipt("pos-1", {
|
|
totalAmount: { value: 150.0, confidence: 0.99 },
|
|
netAmount: 126.05,
|
|
}),
|
|
generateStressReceipt("neg-refund", {
|
|
totalAmount: { value: -50.0, confidence: 0.99 },
|
|
netAmount: -42.02,
|
|
documentType: "SONSTIGES",
|
|
}),
|
|
];
|
|
|
|
const metrics = calculateDashboardKPIs(refundReceipts);
|
|
expect(metrics.totalScanned).toBe(2);
|
|
expect(metrics.totalGross).toBe(100.0); // 150 - 50
|
|
expect(metrics.totalNet).toBeCloseTo(84.03, 2);
|
|
expect(!isNaN(metrics.averageAccuracy)).toBe(true);
|
|
});
|
|
|
|
test("ADV-M4.3: Extreme out-of-range confidence scores clamp safely within [50%, 100%]", () => {
|
|
const extremeReceipts = [
|
|
generateStressReceipt("extreme-high", {
|
|
totalAmount: { value: 100.0, confidence: 5.5 }, // > 1.0
|
|
merchant: { name: "Test", address: null, taxId: null, confidence: 10.0 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 2.0 },
|
|
}),
|
|
generateStressReceipt("extreme-low", {
|
|
totalAmount: { value: 100.0, confidence: -2.0 }, // < 0.0
|
|
merchant: { name: "Test", address: null, taxId: null, confidence: -1.0 },
|
|
date: { isoDate: "2026-08-15", time: null, confidence: 0.0 },
|
|
}),
|
|
];
|
|
|
|
const metrics = calculateDashboardKPIs(extremeReceipts);
|
|
expect(metrics.averageAccuracy).toBeLessThanOrEqual(100.0);
|
|
expect(metrics.averageAccuracy).toBeGreaterThanOrEqual(50.0);
|
|
});
|
|
|
|
test("ADV-M4.4: High-scale KPI computation for 1,000 receipts executes in under 20ms", () => {
|
|
const largeDataset = Array.from({ length: 1000 }, (_, i) =>
|
|
generateStressReceipt(`stress-${i}`, {
|
|
totalAmount: { value: 10 + (i % 100), confidence: 0.9 + (i % 10) * 0.01 },
|
|
})
|
|
);
|
|
|
|
const start = performance.now();
|
|
const metrics = calculateDashboardKPIs(largeDataset);
|
|
const elapsed = performance.now() - start;
|
|
|
|
expect(metrics.totalScanned).toBe(1000);
|
|
expect(metrics.totalGross).toBeGreaterThan(10000);
|
|
expect(elapsed).toBeLessThan(100); // Super fast
|
|
});
|
|
});
|
|
|
|
describe("Milestone 4 (R4) Adversarial: Temporal Partitioning & Malformed Dates", () => {
|
|
test("ADV-M4.5: Handles null, undefined, and non-ISO date strings without crashing", () => {
|
|
const malformedDates = [
|
|
generateStressReceipt("bad-date-1", {
|
|
date: { isoDate: "invalid-date-string", time: null, confidence: 0.5 },
|
|
}),
|
|
generateStressReceipt("bad-date-2", {
|
|
date: { isoDate: "", time: null, confidence: 0.5 },
|
|
}),
|
|
generateStressReceipt("bad-date-3", {
|
|
date: { isoDate: "9999-99-99", time: null, confidence: 0.5 },
|
|
}),
|
|
];
|
|
|
|
const metrics = calculateDashboardKPIs(malformedDates);
|
|
expect(metrics.totalScanned).toBe(3);
|
|
expect(metrics.totalGross).toBe(300.0);
|
|
});
|
|
|
|
test("ADV-M4.6: Correctly isolates current month spend from previous years and months", () => {
|
|
const now = new Date();
|
|
const currentMonthStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-10`;
|
|
const pastMonthStr = "2024-01-15";
|
|
|
|
const receipts = [
|
|
generateStressReceipt("curr-1", {
|
|
date: { isoDate: currentMonthStr, time: null, confidence: 1 },
|
|
totalAmount: { value: 250.0, confidence: 1 },
|
|
}),
|
|
generateStressReceipt("past-1", {
|
|
date: { isoDate: pastMonthStr, time: null, confidence: 1 },
|
|
totalAmount: { value: 1000.0, confidence: 1 },
|
|
}),
|
|
];
|
|
|
|
const metrics = calculateDashboardKPIs(receipts);
|
|
expect(metrics.totalScanned).toBe(2);
|
|
expect(metrics.totalGross).toBe(1250.0);
|
|
expect(metrics.monthlySpend).toBe(250.0);
|
|
});
|
|
});
|
|
|
|
describe("Milestone 4 (R4) Adversarial: Shell State Machine & Route Safety", () => {
|
|
test("ADV-M4.7: Rapid 1,000 toggle cycles maintains strict boolean integrity", () => {
|
|
let state = false;
|
|
for (let i = 0; i < 1000; i++) {
|
|
state = !state;
|
|
}
|
|
expect(state).toBe(false);
|
|
});
|
|
|
|
test("ADV-M4.8: Breadcrumb resolver handles deeply nested, encoded, and special character paths", () => {
|
|
const b1 = getRouteBreadcrumbs("/dashboard/activity?search=Aral%20Tankstelle&page=2");
|
|
expect(b1.currentDe).toBe("Beleg-Archiv & Validierung");
|
|
|
|
const b2 = getRouteBreadcrumbs("/dashboard/export#preview-section");
|
|
expect(b2.currentDe).toBe("Export Control & CSV");
|
|
|
|
const b3 = getRouteBreadcrumbs("/dashboard/settings/security/keys");
|
|
expect(b3.currentDe).toBe("Systemeinstellungen");
|
|
});
|
|
});
|