Files
thrilltrack-explorer/src-old/lib/integrationTests/suites/versioningTests.ts

491 lines
18 KiB
TypeScript

/**
* Versioning & Rollback Test Suite
*
* Tests the complete versioning system end-to-end including automatic
* version creation, attribution, and rollback functionality.
*/
import { supabase } from '@/lib/supabaseClient';
import type { TestSuite, TestResult } from '../testRunner';
import { TestDataTracker } from '../TestDataTracker';
export const versioningTestSuite: TestSuite = {
id: 'versioning',
name: 'Versioning & Rollback',
description: 'Tests version creation, attribution, rollback, and cleanup',
tests: [
{
id: 'version-001',
name: 'Automatic Version Creation on Insert',
description: 'Verifies version 1 is created automatically when entity is created',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
const tracker = new TestDataTracker();
let parkId: string | null = null;
try {
// Create a park
const slug = `test-park-${Date.now()}`;
const { data: park, error: createError } = await supabase
.from('parks')
.insert({
name: 'Version Test Park',
slug,
park_type: 'theme_park',
status: 'operating'
})
.select('id')
.single();
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned from insert');
parkId = park.id;
// Poll for version creation
let v1: any = null;
const pollStart = Date.now();
while (!v1 && Date.now() - pollStart < 5000) {
const { data } = await supabase
.from('park_versions')
.select('version_id')
.eq('park_id', park.id)
.eq('version_number', 1)
.single();
if (data) {
v1 = data;
break;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
// Check version was created
const { data: version, error: versionError } = await supabase
.from('park_versions')
.select('*')
.eq('park_id', park.id)
.eq('version_number', 1)
.single();
if (versionError) throw new Error(`Version query failed: ${versionError.message}`);
if (!version) throw new Error('Version 1 not created');
if (version.name !== 'Version Test Park') throw new Error('Version has incorrect 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: park.id,
versionNumber: version.version_number,
changeType: version.change_type,
isCurrent: version.is_current
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'version-001',
name: 'Automatic Version Creation on Insert',
suite: 'Versioning & Rollback',
status: 'fail',
duration,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
} finally {
// Cleanup
if (parkId) {
await supabase.from('parks').delete().eq('id', parkId);
}
}
}
},
{
id: 'version-002',
name: 'Automatic Version Creation on Update',
description: 'Verifies version 2 is created when entity is updated',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
const tracker = new TestDataTracker();
let parkId: string | null = null;
try {
// Create a park
const slug = `test-park-${Date.now()}`;
const { data: park, error: createError } = await supabase
.from('parks')
.insert({
name: 'Original Name',
slug,
park_type: 'theme_park',
status: 'operating'
})
.select('id')
.single();
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned');
parkId = park.id;
// Wait for version 1
await new Promise(resolve => setTimeout(resolve, 100));
// Update the park
const { error: updateError } = await supabase
.from('parks')
.update({ name: 'Updated Name' })
.eq('id', park.id);
if (updateError) throw new Error(`Park update failed: ${updateError.message}`);
// Wait for version 2
await new Promise(resolve => setTimeout(resolve, 100));
// Check version 2 exists
const { data: v2, error: v2Error } = await supabase
.from('park_versions')
.select('*')
.eq('park_id', park.id)
.eq('version_number', 2)
.single();
if (v2Error) throw new Error(`Version 2 query failed: ${v2Error.message}`);
if (!v2) throw new Error('Version 2 not created');
if (v2.name !== 'Updated Name') throw new Error('Version 2 has incorrect 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');
// Check version 1 is no longer current
const { data: v1, error: v1Error } = await supabase
.from('park_versions')
.select('is_current')
.eq('park_id', park.id)
.eq('version_number', 1)
.single();
if (v1Error) throw new Error(`Version 1 query failed: ${v1Error.message}`);
if (v1?.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: park.id,
v1IsCurrent: v1?.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: 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('version-001 cleanup incomplete:', remaining);
}
}
}
},
{
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();
let parkId: string | null = null;
try {
// Create a park
const slug = `test-park-${Date.now()}`;
const { data: park, error: createError } = await supabase
.from('parks')
.insert({
name: 'Rollback Test Park',
slug,
park_type: 'theme_park',
status: 'operating'
})
.select('id')
.single();
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned');
parkId = park.id;
// Poll for version creation
let v1: any = null;
const pollStart = Date.now();
while (!v1 && Date.now() - pollStart < 5000) {
const { data } = await supabase
.from('park_versions')
.select('version_id')
.eq('park_id', park.id)
.eq('version_number', 1)
.single();
if (data) {
v1 = data;
break;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
if (!v1) throw new Error('Version 1 not created after 5s timeout');
// Check current 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 });
// Try rollback
const { error: rollbackError } = await supabase.rpc('rollback_to_version', {
p_entity_type: 'park',
p_entity_id: park.id,
p_target_version_id: v1.version_id,
p_changed_by: user.id,
p_reason: 'Authorization test'
});
// If user is moderator, rollback should succeed
// If not, rollback should fail with permission error
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: 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('version-002 cleanup incomplete:', remaining);
}
}
}
},
{
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();
let parkId: string | null = null;
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) {
// Skip test if not moderator
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 park
const slug = `test-park-${Date.now()}`;
const { data: park, error: createError } = await supabase
.from('parks')
.insert({
name: 'Original Name',
slug,
park_type: 'theme_park',
status: 'operating',
description: 'Original Description'
})
.select('id')
.single();
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned');
parkId = park.id;
await new Promise(resolve => setTimeout(resolve, 100));
// Get version 1
const { data: v1, error: v1Error } = await supabase
.from('park_versions')
.select('version_id, name, description')
.eq('park_id', park.id)
.eq('version_number', 1)
.single();
if (v1Error || !v1) throw new Error('Version 1 not found');
// Update park
const { error: updateError } = await supabase
.from('parks')
.update({ name: 'Modified Name', description: 'Modified Description' })
.eq('id', park.id);
if (updateError) throw new Error(`Park update failed: ${updateError.message}`);
await new Promise(resolve => setTimeout(resolve, 100));
// Verify version 2
const { data: v2 } = await supabase
.from('park_versions')
.select('version_number, name')
.eq('park_id', park.id)
.eq('version_number', 2)
.single();
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: park.id,
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}`);
await new Promise(resolve => setTimeout(resolve, 200));
// Verify park data restored
const { data: restored, error: restoredError } = await supabase
.from('parks')
.select('name, description')
.eq('id', park.id)
.single();
if (restoredError) throw new Error(`Failed to fetch restored park: ${restoredError.message}`);
if (!restored) throw new Error('Restored park not found');
if (restored.name !== 'Original Name') {
throw new Error(`Rollback failed: expected "Original Name", got "${restored.name}"`);
}
if (restored.description !== 'Original Description') {
throw new Error(`Description not restored: expected "Original Description", got "${restored.description}"`);
}
// Verify version 3 created with change_type = 'restored'
const { data: v3, error: v3Error } = await supabase
.from('park_versions')
.select('*')
.eq('park_id', park.id)
.eq('version_number', 3)
.single();
if (v3Error || !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 !== 'Original 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: park.id,
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: 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('version-003 cleanup incomplete:', remaining);
}
}
}
}
]
};