/** * 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; } 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 { 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 { 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 { 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; } }