Files
thrilltrack-explorer/src-old/lib/integrationTests/TestDataTracker.ts

189 lines
5.4 KiB
TypeScript

import { supabase } from '@/lib/supabaseClient';
import type { Database } from '@/integrations/supabase/types';
import { logger } from '@/lib/logger';
type TableName = keyof Database['public']['Tables'];
/**
* TestDataTracker - Manages test data lifecycle for integration tests
*
* Tracks all created test entities and ensures proper cleanup in dependency order.
* All tracked entities are marked with is_test_data=true for easy identification.
*/
export class TestDataTracker {
private entities = new Map<string, Set<string>>();
/**
* Track an entity for cleanup
* @param table - Database table name
* @param id - Entity ID
*/
track(table: string, id: string): void {
if (!this.entities.has(table)) {
this.entities.set(table, new Set());
}
this.entities.get(table)!.add(id);
}
/**
* Track multiple entities at once
* @param table - Database table name
* @param ids - Array of entity IDs
*/
trackMany(table: string, ids: string[]): void {
ids.forEach(id => this.track(table, id));
}
/**
* Get all tracked entity IDs for a specific table
* @param table - Database table name
* @returns Array of tracked IDs
*/
getTracked(table: string): string[] {
return Array.from(this.entities.get(table) || []);
}
/**
* Cleanup all tracked test data in proper dependency order
* Deletes children first, then parents to avoid foreign key violations
*/
async cleanup(): Promise<void> {
// Define deletion order (children first, parents last)
const deletionOrder: TableName[] = [
'reviews',
'photos',
'submission_items',
'content_submissions',
'ride_versions',
'park_versions',
'company_versions',
'ride_model_versions',
'rides',
'parks',
'ride_models',
'companies',
'test_data_registry'
];
const errors: Array<{ table: string; error: any }> = [];
for (const table of deletionOrder) {
const ids = this.getTracked(table);
if (ids.length === 0) continue;
try {
const { error } = await supabase
.from(table as any)
.delete()
.in('id', ids);
if (error) {
errors.push({ table, error });
logger.warn('Failed to cleanup test data table', { table, error });
}
} catch (err) {
errors.push({ table, error: err });
logger.warn('Exception cleaning up test data table', { table, error: err });
}
}
// Clear tracking after cleanup attempt
this.entities.clear();
if (errors.length > 0) {
logger.warn('Cleanup completed with errors', { errorCount: errors.length, errors });
}
}
/**
* Verify that all tracked test data has been cleaned up
* @returns Array of remaining test data items
*/
async verifyCleanup(): Promise<Array<{ table: string; count: number }>> {
const tables: TableName[] = [
'parks', 'rides', 'companies', 'ride_models',
'content_submissions', 'submission_items',
'park_versions', 'ride_versions', 'company_versions', 'ride_model_versions',
'photos', 'reviews', 'test_data_registry'
];
const remaining: Array<{ table: string; count: number }> = [];
for (const table of tables) {
try {
const { count, error } = await supabase
.from(table as any)
.select('*', { count: 'exact', head: true })
.eq('is_test_data', true);
if (error) {
logger.warn('Failed to check test data table', { table, error });
continue;
}
if (count && count > 0) {
remaining.push({ table, count });
}
} catch (err) {
logger.warn('Exception checking test data table', { table, error: err });
}
}
return remaining;
}
/**
* Bulk delete all test data from the database (emergency cleanup)
* WARNING: This deletes ALL data marked with is_test_data=true
*/
static async bulkCleanupAllTestData(): Promise<{ deleted: number; errors: number }> {
const tables: TableName[] = [
'reviews', 'photos', 'submission_items', 'content_submissions',
'ride_versions', 'park_versions', 'company_versions', 'ride_model_versions',
'rides', 'parks', 'ride_models', 'companies', 'test_data_registry'
];
let totalDeleted = 0;
let totalErrors = 0;
for (const table of tables) {
try {
// First count how many will be deleted
const { count: countToDelete } = await supabase
.from(table as any)
.select('*', { count: 'exact', head: true })
.eq('is_test_data', true);
// Then delete without selecting (avoids needing SELECT permission on deleted rows)
const { error } = await supabase
.from(table as any)
.delete()
.eq('is_test_data', true);
if (error) {
logger.warn('Failed to bulk delete test data', { table, error });
totalErrors++;
} else if (countToDelete) {
totalDeleted += countToDelete;
}
} catch (err) {
logger.warn('Exception bulk deleting test data', { table, error: err });
totalErrors++;
}
}
return { deleted: totalDeleted, errors: totalErrors };
}
/**
* Get summary of tracked entities
*/
getSummary(): Record<string, number> {
const summary: Record<string, number> = {};
this.entities.forEach((ids, table) => {
summary[table] = ids.size;
});
return summary;
}
}