mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 08:31:13 -05:00
Apply quick wins and pipeline fixes for integration tests: - Remove is_test_data flag from location inserts - Increase test delay from 2.5s to 5s and add delays between suites to curb rate limiting - Replace [object Object] error formatting with formatTestError across 10 test suites (31 edits) and add import - Refactor unit/conversion tests and performance tests to use the approval pipeline - Extend edge function handling by ensuring item_ids are included in idempotency key inserts (edge function fix) - Update auth, data integrity, edgeFunction, moderation, performance, submission, unitConversion, and versioning test files accordingly
318 lines
12 KiB
TypeScript
318 lines
12 KiB
TypeScript
/**
|
|
* 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';
|
|
import { formatTestError } from '../formatTestError';
|
|
|
|
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<TestResult> => {
|
|
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: formatTestError(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<TestResult> => {
|
|
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: formatTestError(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 via approval pipeline',
|
|
run: async (): Promise<TestResult> => {
|
|
const startTime = Date.now();
|
|
const tracker = new TestDataTracker();
|
|
|
|
try {
|
|
// Import necessary helpers
|
|
const {
|
|
getCurrentUserId,
|
|
getAuthToken,
|
|
generateUniqueParkData,
|
|
createTestParkSubmission,
|
|
approveSubmission
|
|
} = await import('../helpers/approvalTestHelpers');
|
|
|
|
const userId = await getCurrentUserId();
|
|
const authToken = await getAuthToken();
|
|
|
|
// Create first park with unique slug
|
|
const baseSlug = `unique-test-${Date.now()}`;
|
|
const parkData1 = {
|
|
...generateUniqueParkData('integrity-003-1'),
|
|
slug: baseSlug // Override with our controlled slug
|
|
};
|
|
|
|
// Create and approve first submission
|
|
const { submissionId: sub1Id, itemId: item1Id } = await createTestParkSubmission(parkData1, userId, tracker);
|
|
|
|
const approval1 = await approveSubmission(sub1Id, [item1Id], authToken);
|
|
if (!approval1.success) {
|
|
throw new Error(`First park approval failed: ${approval1.error}`);
|
|
}
|
|
|
|
// Get first park ID
|
|
const { data: item1 } = await supabase
|
|
.from('submission_items')
|
|
.select('approved_entity_id')
|
|
.eq('id', item1Id)
|
|
.single();
|
|
|
|
if (!item1?.approved_entity_id) throw new Error('First park not created');
|
|
tracker.track('parks', item1.approved_entity_id);
|
|
|
|
// Create second submission with SAME slug
|
|
const parkData2 = {
|
|
...generateUniqueParkData('integrity-003-2'),
|
|
slug: baseSlug // Same slug - should fail on approval
|
|
};
|
|
|
|
const { submissionId: sub2Id, itemId: item2Id } = await createTestParkSubmission(parkData2, userId, tracker);
|
|
|
|
// Try to approve second submission (should fail due to unique constraint)
|
|
const approval2 = await approveSubmission(sub2Id, [item2Id], authToken);
|
|
|
|
// Approval should fail
|
|
if (approval2.success) {
|
|
throw new Error('Second approval succeeded when it should have failed (duplicate slug)');
|
|
}
|
|
|
|
// Verify the error mentions unique constraint or duplicate
|
|
const errorMsg = approval2.error?.toLowerCase() || '';
|
|
if (!errorMsg.includes('unique') && !errorMsg.includes('duplicate') && !errorMsg.includes('already exists')) {
|
|
throw new Error(`Expected unique constraint error, got: ${approval2.error}`);
|
|
}
|
|
|
|
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,
|
|
firstParkCreated: true,
|
|
secondParkBlocked: true,
|
|
errorMessage: approval2.error,
|
|
followedPipeline: true
|
|
}
|
|
};
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
return {
|
|
id: 'integrity-003',
|
|
name: 'Unique Constraint Enforcement',
|
|
suite: 'Data Integrity & Constraints',
|
|
status: 'fail',
|
|
duration,
|
|
error: formatTestError(error),
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
} finally {
|
|
await tracker.cleanup();
|
|
}
|
|
}
|
|
},
|
|
{
|
|
id: 'integrity-004',
|
|
name: 'No JSONB in Entity Tables',
|
|
description: 'Validates no JSONB columns exist in entity tables (per requirements)',
|
|
run: async (): Promise<TestResult> => {
|
|
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: formatTestError(error),
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
}
|
|
}
|
|
]
|
|
};
|