Files
scan-receipts/tests/e2e/runner.ts
Timo 84b9987c49 Add full application: receipt scanning, auth, billing, and account deletion
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>
2026-08-19 20:59:04 +02:00

652 lines
20 KiB
TypeScript

/**
* Zenith Silver E2E Test Runner & Assertion Library
* High-performance, zero-dependency async test framework for Receipt Scanner to Excel
*/
// ANSI Color Codes
const RESET = "\x1b[0m";
const BOLD = "\x1b[1m";
const DIM = "\x1b[2m";
const RED = "\x1b[31m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const BLUE = "\x1b[34m";
const MAGENTA = "\x1b[35m";
const CYAN = "\x1b[36m";
const WHITE = "\x1b[37m";
const BG_RED = "\x1b[41m";
const BG_GREEN = "\x1b[42m";
export type TestFn = () => void | Promise<void>;
export type HookFn = () => void | Promise<void>;
export interface TestCase {
name: string;
fn: TestFn;
durationMs?: number;
error?: Error;
passed?: boolean;
}
export interface TestSuite {
name: string;
tests: TestCase[];
beforeAllHooks: HookFn[];
afterAllHooks: HookFn[];
beforeEachHooks: HookFn[];
afterEachHooks: HookFn[];
parent?: TestSuite;
children: TestSuite[];
}
class TestRegistry {
suites: TestSuite[] = [];
currentSuite: TestSuite | null = null;
createSuite(name: string, parent?: TestSuite): TestSuite {
return {
name,
tests: [],
beforeAllHooks: [],
afterAllHooks: [],
beforeEachHooks: [],
afterEachHooks: [],
parent,
children: [],
};
}
}
const registry = new TestRegistry();
export function describe(name: string, fn: () => void) {
const suite = registry.createSuite(name, registry.currentSuite || undefined);
if (registry.currentSuite) {
registry.currentSuite.children.push(suite);
} else {
registry.suites.push(suite);
}
const previousSuite = registry.currentSuite;
registry.currentSuite = suite;
try {
fn();
} finally {
registry.currentSuite = previousSuite;
}
}
export function test(name: string, fn: TestFn) {
if (!registry.currentSuite) {
const rootSuite = registry.createSuite("Root Suite");
registry.suites.push(rootSuite);
registry.currentSuite = rootSuite;
}
registry.currentSuite.tests.push({ name, fn });
}
export const it = test;
export function beforeAll(fn: HookFn) {
if (registry.currentSuite) {
registry.currentSuite.beforeAllHooks.push(fn);
}
}
export function afterAll(fn: HookFn) {
if (registry.currentSuite) {
registry.currentSuite.afterAllHooks.push(fn);
}
}
export function beforeEach(fn: HookFn) {
if (registry.currentSuite) {
registry.currentSuite.beforeEachHooks.push(fn);
}
}
export function afterEach(fn: HookFn) {
if (registry.currentSuite) {
registry.currentSuite.afterEachHooks.push(fn);
}
}
// ============================================================================
// ASSERTION LIBRARY (expect)
// ============================================================================
export class AssertionError extends Error {
constructor(message: string, public actual?: any, public expected?: any) {
super(message);
this.name = "AssertionError";
}
}
function deepEqual(a: any, b: any): boolean {
if (a === b) return true;
if (a == null || b == null) return false;
if (typeof a !== "object" || typeof b !== "object") return false;
if (Array.isArray(a) !== Array.isArray(b)) return false;
if (Array.isArray(a)) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) return false;
}
return true;
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
if (a instanceof RegExp && b instanceof RegExp) {
return a.toString() === b.toString();
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
if (!deepEqual(a[key], b[key])) return false;
}
return true;
}
export interface Matchers<T> {
toBe(expected: any): void;
toEqual(expected: any): void;
toStrictEqual(expected: any): void;
toBeCloseTo(expected: number, deltaOrDigits?: number): void;
toBeGreaterThan(expected: number): void;
toBeGreaterThanOrEqual(expected: number): void;
toBeLessThan(expected: number): void;
toBeLessThanOrEqual(expected: number): void;
toBeTruthy(): void;
toBeFalsy(): void;
toBeNull(): void;
toBeUndefined(): void;
toBeDefined(): void;
toContain(expected: any): void;
toHaveLength(expected: number): void;
toMatch(regex: RegExp | string): void;
toBeInstanceOf(expected: any): void;
toThrow(expectedError?: string | RegExp | Function): void;
not: Matchers<T>;
}
export function expect<T = any>(actual: T): Matchers<T> {
const createMatcher = (isNot: boolean): Matchers<T> => {
return {
toBe(expected: any) {
const pass = Object.is(actual, expected);
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${JSON.stringify(actual)} ${isNot ? "NOT to be" : "to be"} ${JSON.stringify(expected)}`,
actual,
expected
);
}
},
toEqual(expected: any) {
const pass = deepEqual(actual, expected);
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${JSON.stringify(actual)} ${isNot ? "NOT to equal" : "to equal"} ${JSON.stringify(expected)}`,
actual,
expected
);
}
},
toStrictEqual(expected: any) {
const pass = deepEqual(actual, expected);
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${JSON.stringify(actual)} ${isNot ? "NOT to strictly equal" : "to strictly equal"} ${JSON.stringify(expected)}`,
actual,
expected
);
}
},
toBeCloseTo(expected: number, deltaOrDigits: number = 2) {
if (typeof actual !== "number") {
throw new AssertionError(`Actual value ${actual} is not a number`);
}
// If deltaOrDigits <= 0.1, treat as delta, otherwise decimal digits
const delta = deltaOrDigits < 1 ? deltaOrDigits : Math.pow(10, -deltaOrDigits) / 2;
const diff = Math.abs(actual - expected);
const pass = diff <= delta;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${actual} ${isNot ? "NOT to be close to" : "to be close to"} ${expected} (diff: ${diff.toFixed(4)}, max allowed: ${delta})`,
actual,
expected
);
}
},
toBeGreaterThan(expected: number) {
const pass = (actual as any) > expected;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${actual} ${isNot ? "NOT to be >" : "to be >"} ${expected}`,
actual,
expected
);
}
},
toBeGreaterThanOrEqual(expected: number) {
const pass = (actual as any) >= expected;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${actual} ${isNot ? "NOT to be >=" : "to be >="} ${expected}`,
actual,
expected
);
}
},
toBeLessThan(expected: number) {
const pass = (actual as any) < expected;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${actual} ${isNot ? "NOT to be <" : "to be <"} ${expected}`,
actual,
expected
);
}
},
toBeLessThanOrEqual(expected: number) {
const pass = (actual as any) <= expected;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${actual} ${isNot ? "NOT to be <=" : "to be <="} ${expected}`,
actual,
expected
);
}
},
toBeTruthy() {
const pass = Boolean(actual);
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${JSON.stringify(actual)} ${isNot ? "to be falsy" : "to be truthy"}`,
actual,
!isNot
);
}
},
toBeFalsy() {
const pass = !Boolean(actual);
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${JSON.stringify(actual)} ${isNot ? "to be truthy" : "to be falsy"}`,
actual,
isNot
);
}
},
toBeNull() {
const pass = actual === null;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${JSON.stringify(actual)} ${isNot ? "NOT to be null" : "to be null"}`,
actual,
null
);
}
},
toBeUndefined() {
const pass = actual === undefined;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected ${actual} ${isNot ? "NOT to be undefined" : "to be undefined"}`,
actual,
undefined
);
}
},
toBeDefined() {
const pass = actual !== undefined;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected value ${isNot ? "to be undefined" : "to be defined"}`,
actual,
"defined"
);
}
},
toContain(expected: any) {
let pass = false;
if (typeof actual === "string") {
pass = actual.includes(String(expected));
} else if (Array.isArray(actual)) {
pass = actual.some((item) => deepEqual(item, expected));
} else if (actual instanceof Set || actual instanceof Map) {
pass = (actual as any).has(expected);
}
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected container ${isNot ? "NOT to contain" : "to contain"} ${JSON.stringify(expected)}`,
actual,
expected
);
}
},
toHaveLength(expected: number) {
const len = (actual as any)?.length ?? (actual as any)?.size;
const pass = len === expected;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected length ${isNot ? "NOT to be" : "to be"} ${expected}, but received ${len}`,
len,
expected
);
}
},
toMatch(regex: RegExp | string) {
const str = String(actual);
const re = typeof regex === "string" ? new RegExp(regex) : regex;
const pass = re.test(str);
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected string "${str}" ${isNot ? "NOT to match" : "to match"} pattern ${re}`,
actual,
regex
);
}
},
toBeInstanceOf(expected: any) {
const pass = actual instanceof expected;
if (isNot ? pass : !pass) {
throw new AssertionError(
`Expected object ${isNot ? "NOT to be instance of" : "to be instance of"} ${expected?.name || expected}`,
actual,
expected
);
}
},
toThrow(expectedError?: string | RegExp | Function) {
if (typeof actual !== "function") {
throw new AssertionError(`Actual target is not a function: ${typeof actual}`);
}
let threw = false;
let caughtError: any = null;
try {
(actual as any)();
} catch (err) {
threw = true;
caughtError = err;
}
if (isNot) {
if (threw) {
throw new AssertionError(
`Expected function NOT to throw, but it threw: ${caughtError?.message || caughtError}`
);
}
return;
}
if (!threw) {
throw new AssertionError(`Expected function to throw an error, but it returned cleanly`);
}
if (expectedError) {
const msg = caughtError?.message || String(caughtError);
if (typeof expectedError === "string" && !msg.includes(expectedError)) {
throw new AssertionError(
`Expected thrown error message to contain "${expectedError}", received: "${msg}"`
);
} else if (expectedError instanceof RegExp && !expectedError.test(msg)) {
throw new AssertionError(
`Expected thrown error message to match ${expectedError}, received: "${msg}"`
);
} else if (typeof expectedError === "function" && !(caughtError instanceof expectedError)) {
throw new AssertionError(
`Expected thrown error to be instance of ${expectedError.name}, received: ${caughtError?.name}`
);
}
}
},
get not() {
return createMatcher(!isNot);
},
};
};
return createMatcher(false);
}
// ============================================================================
// SUITE EXECUTION ENGINE
// ============================================================================
export interface RunResults {
totalSuites: number;
totalTests: number;
passedCount: number;
failedCount: number;
durationMs: number;
failures: { suiteName: string; testName: string; error: Error }[];
}
async function runSuite(
suite: TestSuite,
results: RunResults,
indent = ""
): Promise<void> {
results.totalSuites++;
console.log(`\n${indent}${BOLD}${CYAN}${suite.name}${RESET}`);
// Run beforeAll hooks
for (const hook of suite.beforeAllHooks) {
try {
await hook();
} catch (err: any) {
console.error(`${indent} ${RED}✖ [beforeAll Hook Failed]${RESET}`, err.message);
throw err;
}
}
// Run suite tests
for (const testCase of suite.tests) {
results.totalTests++;
// Run beforeEach hooks
for (const hook of suite.beforeEachHooks) {
await hook();
}
const startTime = performance.now();
try {
await testCase.fn();
testCase.durationMs = performance.now() - startTime;
testCase.passed = true;
results.passedCount++;
const timeStr = testCase.durationMs > 100
? `${YELLOW}(${testCase.durationMs.toFixed(1)}ms)${RESET}`
: `${DIM}(${testCase.durationMs.toFixed(1)}ms)${RESET}`;
console.log(`${indent} ${GREEN}${RESET} ${testCase.name} ${timeStr}`);
} catch (err: any) {
testCase.durationMs = performance.now() - startTime;
testCase.passed = false;
testCase.error = err;
results.failedCount++;
results.failures.push({
suiteName: suite.name,
testName: testCase.name,
error: err,
});
console.log(`${indent} ${RED}${testCase.name}${RESET} ${RED}(${testCase.durationMs.toFixed(1)}ms)${RESET}`);
console.log(`${indent} ${RED}${err.name}: ${err.message}${RESET}`);
if (err.stack) {
const stackLines = err.stack.split("\n").slice(1, 4).map((l: string) => `${indent} ${DIM}${l.trim()}${RESET}`);
console.log(stackLines.join("\n"));
}
}
// Run afterEach hooks
for (const hook of suite.afterEachHooks) {
await hook();
}
}
// Run nested suites
for (const childSuite of suite.children) {
await runSuite(childSuite, results, indent + " ");
}
// Run afterAll hooks
for (const hook of suite.afterAllHooks) {
try {
await hook();
} catch (err: any) {
console.error(`${indent} ${RED}✖ [afterAll Hook Failed]${RESET}`, err.message);
}
}
}
export async function runAllTests(filterPattern?: string): Promise<boolean> {
const globalStartTime = performance.now();
console.log(`\n${BOLD}${WHITE}================================================================================${RESET}`);
console.log(`${BOLD}${CYAN} ZENITH SILVER RECEIPT SCANNER — END-TO-END VERIFICATION SUITE ${RESET}`);
console.log(`${BOLD}${WHITE}================================================================================${RESET}`);
console.log(`${DIM}Runner: Native Async TypeScript • Date: ${new Date().toISOString()}${RESET}\n`);
const results: RunResults = {
totalSuites: 0,
totalTests: 0,
passedCount: 0,
failedCount: 0,
durationMs: 0,
failures: [],
};
const suitesToRun = filterPattern
? registry.suites.filter((s) => s.name.toLowerCase().includes(filterPattern.toLowerCase()))
: registry.suites;
for (const suite of suitesToRun) {
await runSuite(suite, results);
}
results.durationMs = performance.now() - globalStartTime;
console.log(`\n${BOLD}${WHITE}================================================================================${RESET}`);
console.log(`${BOLD}TEST RUN SUMMARY${RESET}`);
console.log(`${BOLD}${WHITE}================================================================================${RESET}`);
console.log(`Total Suites : ${results.totalSuites}`);
console.log(`Total Tests : ${results.totalTests}`);
console.log(`Passed Tests : ${GREEN}${BOLD}${results.passedCount}${RESET}`);
console.log(`Failed Tests : ${results.failedCount > 0 ? `${RED}${BOLD}${results.failedCount}${RESET}` : `${GREEN}0${RESET}`}`);
console.log(`Total Time : ${(results.durationMs / 1000).toFixed(2)}s (${results.durationMs.toFixed(1)} ms)`);
if (results.failures.length > 0) {
console.log(`\n${BOLD}${RED}FAILED TESTS SUMMARY (${results.failures.length}):${RESET}`);
results.failures.forEach((f, idx) => {
console.log(`\n ${RED}${idx + 1}) [${f.suiteName}] ${f.testName}${RESET}`);
console.log(` ${RED}${f.error.name}: ${f.error.message}${RESET}`);
});
}
const allPassed = results.failedCount === 0 && results.totalTests > 0;
if (allPassed) {
console.log(`\n${BG_GREEN}${BOLD}${WHITE} ALL ${results.totalTests} TESTS PASSED CLEANLY (100% VERIFIED) ${RESET}\n`);
} else {
console.log(`\n${BG_RED}${BOLD}${WHITE} TEST SUITE FAILED WITH ${results.failedCount} ERRORS ${RESET}\n`);
}
return allPassed;
}
// Automatically execute if run directly
async function main() {
const args = process.argv.slice(2);
const filter = args[0];
// Import test suites dynamically if this is the entry point
try {
await import("./tier1_features.test");
await import("./tier2_boundaries.test");
await import("./tier3_interactions.test");
await import("./tier4_workloads.test");
await import("../../src/components/dashboard/__tests__/batchUpload.test");
await import("./m1_adversarial.test");
await import("./challenger2_stress.test");
await import("../../src/components/dashboard/__tests__/inspectorModal.test");
await import("./m2_adversarial.test");
await import("../../src/components/dashboard/__tests__/liveTable.test");
await import("./m3_adversarial.test");
await import("./challenger_m3_stress");
await import("./m3_challenger_deep_stress.test");
await import("../../src/components/dashboard/__tests__/responsiveShell.test");
await import("./m4_adversarial.test");
await import("./challenger_m4_stress");
await import("./challenger_m4_2_stress");
await import("./challenger_m4_1_deep_stress");
await import("./extraction_quality.test");
await import("./export_localization.test");
await import("./export_pdf.test");
await import("./challenger_excel_adversarial.test");
await import("./auth_security.test");
await import("./security_headers.test");
await import("./csrf_tokens.test");
await import("./subdomain_routing.test");
await import("./seo_slugs.test");
await import("./user_enumeration.test");
await import("./upload_whitelist.test");
await import("./sprint_a_scanner.test");
await import("./sprint_b_speed.test");
await import("./sprint_c_image.test");
await import("./sprint_e.test");
await import("./server_pricing.test");
await import("./webhook_verification.test");
} catch (err) {
console.error("Failed to load test suite modules:", err);
process.exit(1);
}
const success = await runAllTests(filter);
if (!success) {
process.exit(1);
}
}
// Check if current file is the main entry point
if (typeof require !== "undefined" && require.main === module) {
main().catch((err) => {
console.error("Fatal runner crash:", err);
process.exit(1);
});
} else if (typeof process !== "undefined" && process.argv && process.argv[1]?.includes("runner.ts")) {
main().catch((err) => {
console.error("Fatal runner crash:", err);
process.exit(1);
});
}