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>(); /** * 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 { // 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> { 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 { const summary: Record = {}; this.entities.forEach((ids, table) => { summary[table] = ids.size; }); return summary; } }