Files
thrilltrack-explorer/src/lib/integrationTests/testRunner.ts
gpt-engineer-app[bot] 496ff48e34 Connect to Lovable Cloud
Implemented end-to-end fixes:
- Added INSERT RLS policies for park_submissions, ride_submissions, company_submissions, ride_model_submissions, and photo_submissions (own submissions and moderator access)
- Fixed process_approval_transaction to replace ride_type with category and updated references accordingly
- Enhanced rate limiting in testRunner.ts with a 6s base delay and 12s adaptive delays after submission-heavy suites
2025-11-10 18:34:14 +00:00

301 lines
7.7 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';
import { approvalPipelineTestSuite } from './suites/approvalPipelineTests';
/**
* Registry of all available test suites
*/
export const ALL_TEST_SUITES = [
moderationTestSuite,
moderationLockTestSuite,
moderationDependencyTestSuite,
approvalPipelineTestSuite,
];
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;
private delayBetweenTests: number;
constructor(onProgress?: (result: TestResult) => void, delayBetweenTests: number = 6000) {
this.onProgress = onProgress;
this.delayBetweenTests = delayBetweenTests; // Default 6 seconds to prevent rate limiting
}
/**
* Wait for specified milliseconds (for rate limiting prevention)
*/
private async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 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 (let i = 0; i < suite.tests.length; i++) {
const test = suite.tests[i];
const result = await this.runTest(test, suite.name);
suiteResults.push(result);
if (this.shouldStop) {
break;
}
// Add delay between tests to prevent rate limiting (except after the last test)
if (i < suite.tests.length - 1 && this.delayBetweenTests > 0) {
// Report delay status with countdown
const delaySeconds = this.delayBetweenTests / 1000;
const delayResult: TestResult = {
id: `delay-${Date.now()}`,
name: `⏳ Rate limit delay: ${delaySeconds}s`,
suite: suite.name,
status: 'running',
duration: 0,
timestamp: new Date().toISOString(),
details: {
reason: 'Pausing to prevent rate limiting',
delayMs: this.delayBetweenTests
}
};
if (this.onProgress) {
this.onProgress(delayResult);
}
await this.delay(this.delayBetweenTests);
// Mark delay as complete
const delayCompleteResult: TestResult = {
...delayResult,
status: 'skip',
duration: this.delayBetweenTests,
details: { reason: 'Rate limit delay completed' }
};
if (this.onProgress) {
this.onProgress(delayCompleteResult);
}
}
}
return suiteResults;
}
/**
* Run all suites sequentially
*/
async runAllSuites(suites: TestSuite[]): Promise<TestResult[]> {
this.results = [];
this.isRunning = true;
this.shouldStop = false;
// Track submission-heavy suites for adaptive delays
const submissionHeavySuites = [
'Entity Submission & Validation',
'Approval Pipeline',
'Unit Conversion Tests',
'Performance & Scalability'
];
for (let i = 0; i < suites.length; i++) {
await this.runSuite(suites[i]);
if (this.shouldStop) {
break;
}
// Add delay between suites with adaptive timing
if (i < suites.length - 1 && this.delayBetweenTests > 0) {
// Longer delay after submission-heavy suites
const isHeavySuite = submissionHeavySuites.includes(suites[i].name);
const delayMs = isHeavySuite
? this.delayBetweenTests * 2 // 12s delay after heavy suites
: this.delayBetweenTests; // 6s delay after others
const delaySeconds = delayMs / 1000;
const delayResult: TestResult = {
id: `suite-delay-${Date.now()}`,
name: `⏳ Suite completion delay: ${delaySeconds}s${isHeavySuite ? ' (submission-heavy)' : ''}`,
suite: 'System',
status: 'running',
duration: 0,
timestamp: new Date().toISOString(),
details: {
reason: 'Pausing between suites to prevent rate limiting',
isSubmissionHeavy: isHeavySuite
}
};
if (this.onProgress) {
this.onProgress(delayResult);
}
await this.delay(delayMs);
if (this.onProgress) {
this.onProgress({
...delayResult,
status: 'skip',
duration: delayMs,
details: { reason: 'Suite delay completed' }
});
}
}
}
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;
}
}