/** * Data Integrity & Constraints Test Suite * * Tests database constraints, RLS policies, and data integrity rules. */ import { supabase } from '@/lib/supabaseClient'; import type { TestSuite, TestResult } from '../testRunner'; import { TestDataTracker } from '../TestDataTracker'; export const dataIntegrityTestSuite: TestSuite = { id: 'data-integrity', name: 'Data Integrity & Constraints', description: 'Tests database constraints, RLS policies, and data integrity', tests: [ { id: 'integrity-001', name: 'RLS Policy Enforcement - Public Read', description: 'Validates public read access to entity tables', run: async (): Promise => { const startTime = Date.now(); try { // Test public read access to parks const { data: parks, error: parksError } = await supabase .from('parks') .select('id, name, slug') .limit(5); if (parksError) throw new Error(`Parks read failed: ${parksError.message}`); // Test public read access to rides const { data: rides, error: ridesError } = await supabase .from('rides') .select('id, name, slug') .limit(5); if (ridesError) throw new Error(`Rides read failed: ${ridesError.message}`); // Test public read access to companies const { data: companies, error: companiesError } = await supabase .from('companies') .select('id, name, slug') .limit(5); if (companiesError) throw new Error(`Companies read failed: ${companiesError.message}`); // Test public read access to ride_models const { data: models, error: modelsError } = await supabase .from('ride_models') .select('id, name, slug') .limit(5); if (modelsError) throw new Error(`Ride models read failed: ${modelsError.message}`); const duration = Date.now() - startTime; return { id: 'integrity-001', name: 'RLS Policy Enforcement - Public Read', suite: 'Data Integrity & Constraints', status: 'pass', duration, timestamp: new Date().toISOString(), details: { parksReadable: Array.isArray(parks), ridesReadable: Array.isArray(rides), companiesReadable: Array.isArray(companies), rideModelsReadable: Array.isArray(models) } }; } catch (error) { const duration = Date.now() - startTime; return { id: 'integrity-001', name: 'RLS Policy Enforcement - Public Read', suite: 'Data Integrity & Constraints', status: 'fail', duration, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString() }; } } }, { id: 'integrity-002', name: 'Foreign Key Constraint Enforcement', description: 'Tests foreign key constraints prevent invalid references', run: async (): Promise => { const startTime = Date.now(); try { // Try to create a ride with non-existent park_id const invalidParkId = '00000000-0000-0000-0000-000000000000'; const slug = `test-ride-${Date.now()}`; const { error } = await supabase .from('rides') .insert({ name: 'Invalid Ride', slug, park_id: invalidParkId, category: 'roller_coaster', status: 'operating', is_test_data: true }); // This SHOULD fail with foreign key violation if (!error) { throw new Error('Foreign key constraint not enforced - invalid park_id was accepted'); } // Verify it's a foreign key violation if (!error.message.includes('foreign key') && !error.message.includes('violates')) { throw new Error(`Expected foreign key error, got: ${error.message}`); } const duration = Date.now() - startTime; return { id: 'integrity-002', name: 'Foreign Key Constraint Enforcement', suite: 'Data Integrity & Constraints', status: 'pass', duration, timestamp: new Date().toISOString(), details: { constraintEnforced: true, errorMessage: error.message } }; } catch (error) { const duration = Date.now() - startTime; return { id: 'integrity-002', name: 'Foreign Key Constraint Enforcement', suite: 'Data Integrity & Constraints', status: 'fail', duration, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString() }; } } }, { id: 'integrity-003', name: 'Unique Constraint Enforcement', description: 'Tests unique constraints prevent duplicate slugs', run: async (): Promise => { const startTime = Date.now(); const tracker = new TestDataTracker(); let parkId: string | null = null; try { // Create a park const slug = `unique-test-${Date.now()}`; const { data: park, error: createError } = await supabase .from('parks') .insert({ name: 'Unique Test Park', slug, park_type: 'theme_park', status: 'operating', is_test_data: true }) .select('id') .single(); if (createError) throw new Error(`Park creation failed: ${createError.message}`); if (!park) throw new Error('No park returned'); parkId = park.id; tracker.track('parks', parkId); // Try to create another park with same slug const { error: duplicateError } = await supabase .from('parks') .insert({ name: 'Duplicate Park', slug, // Same slug park_type: 'theme_park', status: 'operating', is_test_data: true }); // This SHOULD fail with unique violation if (!duplicateError) { throw new Error('Unique constraint not enforced - duplicate slug was accepted'); } // Verify it's a unique violation if (!duplicateError.message.includes('unique') && !duplicateError.message.includes('duplicate')) { throw new Error(`Expected unique constraint error, got: ${duplicateError.message}`); } const duration = Date.now() - startTime; return { id: 'integrity-003', name: 'Unique Constraint Enforcement', suite: 'Data Integrity & Constraints', status: 'pass', duration, timestamp: new Date().toISOString(), details: { constraintEnforced: true, errorMessage: duplicateError.message } }; } catch (error) { const duration = Date.now() - startTime; return { id: 'integrity-003', name: 'Unique Constraint Enforcement', suite: 'Data Integrity & Constraints', status: 'fail', duration, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString() }; } finally { await tracker.cleanup(); const remaining = await tracker.verifyCleanup(); if (remaining.length > 0) { console.warn('integrity-003 cleanup incomplete:', remaining); } } } }, { id: 'integrity-004', name: 'No JSONB in Entity Tables', description: 'Validates no JSONB columns exist in entity tables (per requirements)', run: async (): Promise => { const startTime = Date.now(); try { // Sample actual data and check structure (information_schema not accessible via RLS) const { data: parks } = await supabase.from('parks').select('*').limit(1); const { data: rides } = await supabase.from('rides').select('*').limit(1); const { data: companies } = await supabase.from('companies').select('*').limit(1); const { data: models } = await supabase.from('ride_models').select('*').limit(1); // Check if any fields appear to be JSONB objects const hasJsonbFields = [parks, rides, companies, models].some(dataset => { if (!dataset || dataset.length === 0) return false; const record = dataset[0] as any; return Object.keys(record).some(key => { const val = record[key]; // Check if value is a plain object (not Date, not Array, not null) if (val === null || val === undefined) return false; if (typeof val !== 'object') return false; if (Array.isArray(val)) return false; // Check if it's a Date by checking if it has getTime method if (val && typeof val.getTime === 'function') return false; // If we get here, it's likely a JSONB object return true; }); }); if (hasJsonbFields) { throw new Error('Found JSONB-like fields in entity tables'); } const duration = Date.now() - startTime; return { id: 'integrity-004', name: 'No JSONB in Entity Tables', suite: 'Data Integrity & Constraints', status: 'pass', duration, timestamp: new Date().toISOString(), details: { noJsonbColumns: true, validation: 'Entity tables use relational structure only' } }; } catch (error) { const duration = Date.now() - startTime; return { id: 'integrity-004', name: 'No JSONB in Entity Tables', suite: 'Data Integrity & Constraints', status: 'fail', duration, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString() }; } } } ] };