Files
thrilltrack-explorer/src/lib/integrationTests/suites/versioningTests.ts
gpt-engineer-app[bot] c5d40d07df Connect to Lovable Cloud
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
2025-11-10 18:20:22 +00:00

456 lines
17 KiB
TypeScript

/**
* Versioning & Rollback Test Suite
*
* Tests the complete versioning system end-to-end including automatic
* version creation, attribution, and rollback functionality.
*
* All tests follow the sacred pipeline: submitParkCreation → approve → verify versioning
*/
import { supabase } from '@/lib/supabaseClient';
import type { TestSuite, TestResult } from '../testRunner';
import { TestDataTracker } from '../TestDataTracker';
import { formatTestError } from '../formatTestError';
import {
generateUniqueParkData,
createTestParkSubmission,
approveSubmission,
pollForEntity,
pollForVersion,
getAuthToken,
getCurrentUserId,
} from '../helpers/approvalTestHelpers';
export const versioningTestSuite: TestSuite = {
id: 'versioning',
name: 'Versioning & Rollback',
description: 'Tests version creation, attribution, rollback, and cleanup via sacred pipeline',
tests: [
{
id: 'version-001',
name: 'Automatic Version Creation on Insert',
description: 'Verifies version 1 is created automatically when entity is approved',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
const tracker = new TestDataTracker();
try {
// Follow sacred pipeline: Form → Submission → Approval → Versioning
const userId = await getCurrentUserId();
const authToken = await getAuthToken();
const parkData = generateUniqueParkData('version-001');
// Create submission
const { submissionId, itemId } = await createTestParkSubmission(parkData, userId, tracker);
// Approve submission
const approval = await approveSubmission(submissionId, [itemId], authToken);
if (!approval.success) {
throw new Error(`Approval failed: ${approval.error}`);
}
// Get approved entity ID
const { data: item } = await supabase
.from('submission_items')
.select('approved_entity_id')
.eq('id', itemId)
.single();
if (!item?.approved_entity_id) {
throw new Error('No entity ID returned after approval');
}
const parkId = item.approved_entity_id;
tracker.track('parks', parkId);
// Poll for park entity
const park = await pollForEntity('parks', parkId);
if (!park) throw new Error('Park not created after approval');
// Verify version 1 was created automatically
const version = await pollForVersion('park', parkId, 1);
if (!version) throw new Error('Version 1 not created');
if (version.name !== parkData.name) {
throw new Error(`Version has incorrect name: expected "${parkData.name}", got "${version.name}"`);
}
if (version.change_type !== 'created') {
throw new Error(`Expected change_type "created", got "${version.change_type}"`);
}
if (!version.is_current) {
throw new Error('Version is not marked as current');
}
const duration = Date.now() - startTime;
return {
id: 'version-001',
name: 'Automatic Version Creation on Insert',
suite: 'Versioning & Rollback',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
parkId,
submissionId,
versionNumber: version.version_number,
changeType: version.change_type,
isCurrent: version.is_current,
followedPipeline: true
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'version-001',
name: 'Automatic Version Creation on Insert',
suite: 'Versioning & Rollback',
status: 'fail',
duration,
error: formatTestError(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
} finally {
await tracker.cleanup();
}
}
},
{
id: 'version-002',
name: 'Automatic Version Creation on Update',
description: 'Verifies version 2 is created when entity is updated via pipeline',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
const tracker = new TestDataTracker();
try {
// Create and approve initial park
const userId = await getCurrentUserId();
const authToken = await getAuthToken();
const parkData = generateUniqueParkData('version-002');
const { submissionId, itemId } = await createTestParkSubmission(parkData, userId, tracker);
const approval = await approveSubmission(submissionId, [itemId], authToken);
if (!approval.success) {
throw new Error(`Initial approval failed: ${approval.error}`);
}
// Get park ID
const { data: item } = await supabase
.from('submission_items')
.select('approved_entity_id')
.eq('id', itemId)
.single();
const parkId = item?.approved_entity_id;
if (!parkId) throw new Error('No park ID after approval');
tracker.track('parks', parkId);
// Wait for version 1
const v1 = await pollForVersion('park', parkId, 1);
if (!v1) throw new Error('Version 1 not created');
// Update park directly (simulating approved edit)
// In production, this would go through edit submission pipeline
const { error: updateError } = await supabase
.from('parks')
.update({ name: 'Updated Name', description: 'Updated Description' })
.eq('id', parkId);
if (updateError) throw new Error(`Park update failed: ${updateError.message}`);
// Verify version 2 created
const v2 = await pollForVersion('park', parkId, 2);
if (!v2) throw new Error('Version 2 not created after update');
if (v2.name !== 'Updated Name') {
throw new Error(`Version 2 has incorrect name: expected "Updated Name", got "${v2.name}"`);
}
if (v2.change_type !== 'updated') {
throw new Error(`Expected change_type "updated", got "${v2.change_type}"`);
}
if (!v2.is_current) {
throw new Error('Version 2 is not marked as current');
}
// Verify version 1 is no longer current
const { data: v1Updated } = await supabase
.from('park_versions')
.select('is_current')
.eq('park_id', parkId)
.eq('version_number', 1)
.single();
if (v1Updated?.is_current) {
throw new Error('Version 1 is still marked as current');
}
const duration = Date.now() - startTime;
return {
id: 'version-002',
name: 'Automatic Version Creation on Update',
suite: 'Versioning & Rollback',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
parkId,
v1IsCurrent: v1Updated?.is_current,
v2IsCurrent: v2.is_current,
v2ChangeType: v2.change_type
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'version-002',
name: 'Automatic Version Creation on Update',
suite: 'Versioning & Rollback',
status: 'fail',
duration,
error: formatTestError(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
} finally {
await tracker.cleanup();
}
}
},
{
id: 'version-003',
name: 'Rollback Authorization Check',
description: 'Tests that rollback_to_version requires moderator role',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
const tracker = new TestDataTracker();
try {
// Create and approve park
const userId = await getCurrentUserId();
const authToken = await getAuthToken();
const parkData = generateUniqueParkData('version-003');
const { submissionId, itemId } = await createTestParkSubmission(parkData, userId, tracker);
const approval = await approveSubmission(submissionId, [itemId], authToken);
if (!approval.success) {
throw new Error(`Approval failed: ${approval.error}`);
}
// Get park ID
const { data: item } = await supabase
.from('submission_items')
.select('approved_entity_id')
.eq('id', itemId)
.single();
const parkId = item?.approved_entity_id;
if (!parkId) throw new Error('No park ID after approval');
tracker.track('parks', parkId);
// Wait for version 1
const v1 = await pollForVersion('park', parkId, 1);
if (!v1) throw new Error('Version 1 not created');
// Check current user role
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('No authenticated user');
const { data: isMod } = await supabase.rpc('is_moderator', { _user_id: user.id });
// Try rollback
const { error: rollbackError } = await supabase.rpc('rollback_to_version', {
p_entity_type: 'park',
p_entity_id: parkId,
p_target_version_id: v1.version_id,
p_changed_by: user.id,
p_reason: 'Authorization test'
});
// Verify authorization enforcement
if (isMod && rollbackError) {
throw new Error(`Rollback failed for moderator: ${rollbackError.message}`);
}
if (!isMod && !rollbackError) {
throw new Error('Rollback succeeded for non-moderator (should have failed)');
}
const duration = Date.now() - startTime;
return {
id: 'version-003',
name: 'Rollback Authorization Check',
suite: 'Versioning & Rollback',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
userIsModerator: isMod,
rollbackBlocked: !isMod && !!rollbackError,
authorizationEnforced: true
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'version-003',
name: 'Rollback Authorization Check',
suite: 'Versioning & Rollback',
status: 'fail',
duration,
error: formatTestError(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
} finally {
await tracker.cleanup();
}
}
},
{
id: 'version-004',
name: 'Complete Rollback Flow',
description: 'Tests end-to-end rollback with version 3 creation',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
const tracker = new TestDataTracker();
try {
// Check if user is moderator
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('No authenticated user');
const { data: isMod } = await supabase.rpc('is_moderator', { _user_id: user.id });
if (!isMod) {
const duration = Date.now() - startTime;
return {
id: 'version-004',
name: 'Complete Rollback Flow',
suite: 'Versioning & Rollback',
status: 'skip',
duration,
timestamp: new Date().toISOString(),
details: { reason: 'User is not a moderator, test requires moderator role' }
};
}
// Create and approve park
const userId = await getCurrentUserId();
const authToken = await getAuthToken();
const parkData = {
...generateUniqueParkData('version-004'),
description: 'Original Description'
};
const { submissionId, itemId } = await createTestParkSubmission(parkData, userId, tracker);
const approval = await approveSubmission(submissionId, [itemId], authToken);
if (!approval.success) {
throw new Error(`Approval failed: ${approval.error}`);
}
// Get park ID
const { data: item } = await supabase
.from('submission_items')
.select('approved_entity_id')
.eq('id', itemId)
.single();
const parkId = item?.approved_entity_id;
if (!parkId) throw new Error('No park ID after approval');
tracker.track('parks', parkId);
// Wait for version 1
const v1 = await pollForVersion('park', parkId, 1);
if (!v1) throw new Error('Version 1 not created');
// Update park
const { error: updateError } = await supabase
.from('parks')
.update({ name: 'Modified Name', description: 'Modified Description' })
.eq('id', parkId);
if (updateError) throw new Error(`Park update failed: ${updateError.message}`);
// Wait for version 2
const v2 = await pollForVersion('park', parkId, 2);
if (!v2) throw new Error('Version 2 not created');
if (v2.name !== 'Modified Name') throw new Error('Version 2 has incorrect data');
// Rollback to version 1
const { error: rollbackError } = await supabase.rpc('rollback_to_version', {
p_entity_type: 'park',
p_entity_id: parkId,
p_target_version_id: v1.version_id,
p_changed_by: user.id,
p_reason: 'Integration test rollback'
});
if (rollbackError) throw new Error(`Rollback failed: ${rollbackError.message}`);
// Verify park data restored
const restored = await pollForEntity('parks', parkId, 3000);
if (!restored) throw new Error('Could not fetch restored park');
if (restored.name !== parkData.name) {
throw new Error(`Rollback failed: expected "${parkData.name}", got "${restored.name}"`);
}
if (restored.description !== 'Original Description') {
throw new Error(`Description not restored: got "${restored.description}"`);
}
// Verify version 3 created with change_type = 'restored'
const v3 = await pollForVersion('park', parkId, 3, 3000);
if (!v3) throw new Error('Version 3 (restored) not created');
if (v3.change_type !== 'restored') {
throw new Error(`Expected change_type "restored", got "${v3.change_type}"`);
}
if (v3.name !== parkData.name) throw new Error('Version 3 has incorrect data');
if (!v3.is_current) throw new Error('Version 3 is not marked as current');
const duration = Date.now() - startTime;
return {
id: 'version-004',
name: 'Complete Rollback Flow',
suite: 'Versioning & Rollback',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
parkId,
versionsCreated: 3,
dataRestored: true,
v3ChangeType: v3.change_type,
v3IsCurrent: v3.is_current
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'version-004',
name: 'Complete Rollback Flow',
suite: 'Versioning & Rollback',
status: 'fail',
duration,
error: formatTestError(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
} finally {
await tracker.cleanup();
}
}
}
]
};