mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 00:11:14 -05:00
feat: Implement is_test_data flag system
This commit is contained in:
181
src/lib/integrationTests/TestDataTracker.ts
Normal file
181
src/lib/integrationTests/TestDataTracker.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import type { Database } from '@/integrations/supabase/types';
|
||||
|
||||
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 });
|
||||
console.warn(`Failed to cleanup ${table}:`, error);
|
||||
}
|
||||
} catch (err) {
|
||||
errors.push({ table, error: err });
|
||||
console.warn(`Exception cleaning up ${table}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear tracking after cleanup attempt
|
||||
this.entities.clear();
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.warn(`Cleanup completed with ${errors.length} errors:`, 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) {
|
||||
console.warn(`Failed to check ${table}:`, error);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count && count > 0) {
|
||||
remaining.push({ table, count });
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Exception checking ${table}:`, 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 {
|
||||
const { error, data } = await supabase
|
||||
.from(table as any)
|
||||
.delete()
|
||||
.eq('is_test_data', true)
|
||||
.select('id');
|
||||
|
||||
if (error) {
|
||||
console.warn(`Failed to bulk delete from ${table}:`, error);
|
||||
totalErrors++;
|
||||
} else if (data) {
|
||||
totalDeleted += data.length;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Exception bulk deleting from ${table}:`, 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user