Files
thrilltrack-explorer/src/lib/integrationTests/testRunner.ts
gpt-engineer-app[bot] 8feb01f1c3 Implement Phases 5 & 6
2025-11-02 21:11:18 +00:00

207 lines
4.5 KiB
TypeScript

/**
* Integration Test Runner
*
* Core infrastructure for running comprehensive integration tests.
* Tests run against real database functions, edge functions, and API endpoints.
*/
import { moderationTestSuite } from './suites/moderationTests';
import { moderationLockTestSuite } from './suites/moderationLockTests';
import { moderationDependencyTestSuite } from './suites/moderationDependencyTests';
/**
* Registry of all available test suites
*/
export const ALL_TEST_SUITES = [
moderationTestSuite,
moderationLockTestSuite,
moderationDependencyTestSuite
];
export interface TestResult {
id: string;
name: string;
suite: string;
status: 'pass' | 'fail' | 'skip' | 'running';
duration: number; // milliseconds
error?: string;
details?: any;
timestamp: string;
stack?: string;
}
export interface Test {
id: string;
name: string;
description: string;
run: () => Promise<TestResult>;
}
export interface TestSuite {
id: string;
name: string;
description: string;
tests: Test[];
}
export class IntegrationTestRunner {
private results: TestResult[] = [];
private isRunning = false;
private shouldStop = false;
private onProgress?: (result: TestResult) => void;
constructor(onProgress?: (result: TestResult) => void) {
this.onProgress = onProgress;
}
/**
* Run a single test with error handling
*/
async runTest(test: Test, suiteName: string): Promise<TestResult> {
if (this.shouldStop) {
return {
id: test.id,
name: test.name,
suite: suiteName,
status: 'skip',
duration: 0,
timestamp: new Date().toISOString(),
details: { reason: 'Test run stopped by user' }
};
}
// Mark as running
const runningResult: TestResult = {
id: test.id,
name: test.name,
suite: suiteName,
status: 'running',
duration: 0,
timestamp: new Date().toISOString(),
};
if (this.onProgress) {
this.onProgress(runningResult);
}
try {
const result = await test.run();
this.results.push(result);
if (this.onProgress) {
this.onProgress(result);
}
return result;
} catch (error) {
const failResult: TestResult = {
id: test.id,
name: test.name,
suite: suiteName,
status: 'fail',
duration: 0,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString(),
};
this.results.push(failResult);
if (this.onProgress) {
this.onProgress(failResult);
}
return failResult;
}
}
/**
* Run all tests in a suite
*/
async runSuite(suite: TestSuite): Promise<TestResult[]> {
const suiteResults: TestResult[] = [];
for (const test of suite.tests) {
const result = await this.runTest(test, suite.name);
suiteResults.push(result);
if (this.shouldStop) {
break;
}
}
return suiteResults;
}
/**
* Run all suites sequentially
*/
async runAllSuites(suites: TestSuite[]): Promise<TestResult[]> {
this.results = [];
this.isRunning = true;
this.shouldStop = false;
for (const suite of suites) {
await this.runSuite(suite);
if (this.shouldStop) {
break;
}
}
this.isRunning = false;
return this.results;
}
/**
* Stop the test run
*/
stop(): void {
this.shouldStop = true;
}
/**
* Get all results
*/
getResults(): TestResult[] {
return this.results;
}
/**
* Get summary statistics
*/
getSummary(): {
total: number;
passed: number;
failed: number;
skipped: number;
running: number;
totalDuration: number;
} {
const total = this.results.length;
const passed = this.results.filter(r => r.status === 'pass').length;
const failed = this.results.filter(r => r.status === 'fail').length;
const skipped = this.results.filter(r => r.status === 'skip').length;
const running = this.results.filter(r => r.status === 'running').length;
const totalDuration = this.results.reduce((sum, r) => sum + r.duration, 0);
return { total, passed, failed, skipped, running, totalDuration };
}
/**
* Check if runner is currently running
*/
getIsRunning(): boolean {
return this.isRunning;
}
/**
* Reset runner state
*/
reset(): void {
this.results = [];
this.isRunning = false;
this.shouldStop = false;
}
}