Refactor tests to use pipeline

Refactor versioningTests.ts, submissionTests.ts, and dataIntegrityTests.ts to replace direct DB inserts with the submission pipeline (submitParkCreation → approve → verify), aligning tests with RLS policies and validation flows.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-10 16:59:10 +00:00
parent 20cd434e73
commit e0001961bf
3 changed files with 434 additions and 448 deletions

View File

@@ -3,76 +3,82 @@
*
* 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 {
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',
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 created',
description: 'Verifies version 1 is created automatically when entity is approved',
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();
// Follow sacred pipeline: Form → Submission → Approval → Versioning
const userId = await getCurrentUserId();
const authToken = await getAuthToken();
const parkData = generateUniqueParkData('version-001');
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned from insert');
// Create submission
const { submissionId, itemId } = await createTestParkSubmission(parkData, userId, tracker);
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));
// Approve submission
const approval = await approveSubmission(submissionId, [itemId], authToken);
if (!approval.success) {
throw new Error(`Approval failed: ${approval.error}`);
}
// Check version was created
const { data: version, error: versionError } = await supabase
.from('park_versions')
.select('*')
.eq('park_id', park.id)
.eq('version_number', 1)
// Get approved entity ID
const { data: item } = await supabase
.from('submission_items')
.select('approved_entity_id')
.eq('id', itemId)
.single();
if (versionError) throw new Error(`Version query failed: ${versionError.message}`);
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 !== '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');
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;
@@ -84,10 +90,12 @@ export const versioningTestSuite: TestSuite = {
duration,
timestamp: new Date().toISOString(),
details: {
parkId: park.id,
parkId,
submissionId,
versionNumber: version.version_number,
changeType: version.change_type,
isCurrent: version.is_current
isCurrent: version.is_current,
followedPipeline: true
}
};
} catch (error) {
@@ -103,79 +111,81 @@ export const versioningTestSuite: TestSuite = {
timestamp: new Date().toISOString()
};
} finally {
// Cleanup
if (parkId) {
await supabase.from('parks').delete().eq('id', parkId);
}
await tracker.cleanup();
}
}
},
{
id: 'version-002',
name: 'Automatic Version Creation on Update',
description: 'Verifies version 2 is created when entity is updated',
description: 'Verifies version 2 is created when entity is updated via pipeline',
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')
// 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();
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned');
const parkId = item?.approved_entity_id;
if (!parkId) throw new Error('No park ID after approval');
parkId = park.id;
tracker.track('parks', parkId);
// Wait for version 1
await new Promise(resolve => setTimeout(resolve, 100));
const v1 = await pollForVersion('park', parkId, 1);
if (!v1) throw new Error('Version 1 not created');
// Update the park
// 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' })
.eq('id', park.id);
.update({ name: 'Updated Name', description: 'Updated Description' })
.eq('id', parkId);
if (updateError) throw new Error(`Park update failed: ${updateError.message}`);
// Wait for version 2
await new Promise(resolve => setTimeout(resolve, 100));
// Verify version 2 created
const v2 = await pollForVersion('park', parkId, 2);
if (!v2) throw new Error('Version 2 not created after update');
// 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 (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');
}
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
// Verify version 1 is no longer current
const { data: v1Updated } = await supabase
.from('park_versions')
.select('is_current')
.eq('park_id', park.id)
.eq('park_id', parkId)
.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');
if (v1Updated?.is_current) {
throw new Error('Version 1 is still marked as current');
}
const duration = Date.now() - startTime;
@@ -187,8 +197,8 @@ export const versioningTestSuite: TestSuite = {
duration,
timestamp: new Date().toISOString(),
details: {
parkId: park.id,
v1IsCurrent: v1?.is_current,
parkId,
v1IsCurrent: v1Updated?.is_current,
v2IsCurrent: v2.is_current,
v2ChangeType: v2.change_type
}
@@ -207,10 +217,6 @@ export const versioningTestSuite: TestSuite = {
};
} finally {
await tracker.cleanup();
const remaining = await tracker.verifyCleanup();
if (remaining.length > 0) {
console.warn('version-001 cleanup incomplete:', remaining);
}
}
}
},
@@ -221,48 +227,37 @@ export const versioningTestSuite: TestSuite = {
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();
// Create and approve park
const userId = await getCurrentUserId();
const authToken = await getAuthToken();
const parkData = generateUniqueParkData('version-003');
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned');
const { submissionId, itemId } = await createTestParkSubmission(parkData, userId, tracker);
const approval = await approveSubmission(submissionId, [itemId], authToken);
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 (!approval.success) {
throw new Error(`Approval failed: ${approval.error}`);
}
if (!v1) throw new Error('Version 1 not created after 5s timeout');
// Get park ID
const { data: item } = await supabase
.from('submission_items')
.select('approved_entity_id')
.eq('id', itemId)
.single();
// Check current user is moderator
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');
@@ -271,14 +266,13 @@ export const versioningTestSuite: TestSuite = {
// Try rollback
const { error: rollbackError } = await supabase.rpc('rollback_to_version', {
p_entity_type: 'park',
p_entity_id: park.id,
p_entity_id: parkId,
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
// Verify authorization enforcement
if (isMod && rollbackError) {
throw new Error(`Rollback failed for moderator: ${rollbackError.message}`);
}
@@ -316,10 +310,6 @@ export const versioningTestSuite: TestSuite = {
};
} finally {
await tracker.cleanup();
const remaining = await tracker.verifyCleanup();
if (remaining.length > 0) {
console.warn('version-002 cleanup incomplete:', remaining);
}
}
}
},
@@ -330,7 +320,6 @@ export const versioningTestSuite: TestSuite = {
run: async (): Promise<TestResult> => {
const startTime = Date.now();
const tracker = new TestDataTracker();
let parkId: string | null = null;
try {
// Check if user is moderator
@@ -340,7 +329,6 @@ export const versioningTestSuite: TestSuite = {
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',
@@ -353,61 +341,54 @@ export const versioningTestSuite: TestSuite = {
};
}
// 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')
// 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();
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
if (!park) throw new Error('No park returned');
const parkId = item?.approved_entity_id;
if (!parkId) throw new Error('No park ID after approval');
parkId = park.id;
await new Promise(resolve => setTimeout(resolve, 100));
tracker.track('parks', parkId);
// 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');
// 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', park.id);
.eq('id', parkId);
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();
// 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: park.id,
p_entity_id: parkId,
p_target_version_id: v1.version_id,
p_changed_by: user.id,
p_reason: 'Integration test rollback'
@@ -415,37 +396,24 @@ export const versioningTestSuite: TestSuite = {
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();
const restored = await pollForEntity('parks', parkId, 3000);
if (!restored) throw new Error('Could not fetch restored park');
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.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: expected "Original Description", got "${restored.description}"`);
throw new Error(`Description not restored: 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');
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 !== 'Original Name') throw new Error('Version 3 has incorrect data');
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;
@@ -458,7 +426,7 @@ export const versioningTestSuite: TestSuite = {
duration,
timestamp: new Date().toISOString(),
details: {
parkId: park.id,
parkId,
versionsCreated: 3,
dataRestored: true,
v3ChangeType: v3.change_type,
@@ -479,10 +447,6 @@ export const versioningTestSuite: TestSuite = {
};
} finally {
await tracker.cleanup();
const remaining = await tracker.verifyCleanup();
if (remaining.length > 0) {
console.warn('version-003 cleanup incomplete:', remaining);
}
}
}
}