mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 02:11:12 -05:00
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:
@@ -1,71 +1,95 @@
|
||||
/**
|
||||
* Entity Submission & Validation Integration Tests
|
||||
* Submission Pipeline Validation Tests
|
||||
*
|
||||
* Tests for submission validation, schema validation, and entity creation.
|
||||
* Tests submission creation, validation, and the full approval flow.
|
||||
* All tests follow the sacred pipeline architecture.
|
||||
*/
|
||||
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import type { TestSuite, TestResult } from '../testRunner';
|
||||
import { TestDataTracker } from '../TestDataTracker';
|
||||
import {
|
||||
generateUniqueParkData,
|
||||
generateUniqueRideData,
|
||||
generateUniqueCompanyData,
|
||||
generateUniqueRideModelData,
|
||||
createTestParkSubmission,
|
||||
createTestRideSubmission,
|
||||
createTestCompanySubmission,
|
||||
createTestRideModelSubmission,
|
||||
approveSubmission,
|
||||
pollForEntity,
|
||||
getAuthToken,
|
||||
getCurrentUserId,
|
||||
} from '../helpers/approvalTestHelpers';
|
||||
|
||||
export const submissionTestSuite: TestSuite = {
|
||||
id: 'submission',
|
||||
name: 'Entity Submission & Validation',
|
||||
description: 'Tests for entity submission workflows and validation schemas',
|
||||
description: 'Tests submission creation, validation, and approval pipeline',
|
||||
tests: [
|
||||
{
|
||||
id: 'submission-001',
|
||||
name: 'Park Creation Validation',
|
||||
description: 'Validates park submission and creation',
|
||||
description: 'Validates park submission and approval creates entity',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
const tracker = new TestDataTracker();
|
||||
let parkId: string | null = null;
|
||||
|
||||
try {
|
||||
const parkSlug = `test-park-submit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const userId = await getCurrentUserId();
|
||||
const authToken = await getAuthToken();
|
||||
const parkData = generateUniqueParkData('submission-001');
|
||||
|
||||
// Create park with valid data
|
||||
const { data: park, error: createError } = await supabase
|
||||
.from('parks')
|
||||
.insert({
|
||||
name: 'Test Park Submission',
|
||||
slug: parkSlug,
|
||||
park_type: 'theme_park',
|
||||
status: 'operating',
|
||||
description: 'Test park for submission validation'
|
||||
})
|
||||
.select('id, name, slug, park_type, status')
|
||||
// Create submission
|
||||
const { submissionId, itemId } = await createTestParkSubmission(parkData, userId, tracker);
|
||||
|
||||
// Verify submission was created
|
||||
const { data: submission } = await supabase
|
||||
.from('content_submissions')
|
||||
.select('status, submission_type')
|
||||
.eq('id', submissionId)
|
||||
.single();
|
||||
|
||||
if (createError) throw new Error(`Park creation failed: ${createError.message}`);
|
||||
if (!park) throw new Error('Park not returned after creation');
|
||||
|
||||
parkId = park.id;
|
||||
|
||||
// Validate created park has correct data
|
||||
if (park.name !== 'Test Park Submission') {
|
||||
throw new Error(`Expected name "Test Park Submission", got "${park.name}"`);
|
||||
if (!submission) throw new Error('Submission not found');
|
||||
if (submission.status !== 'pending') {
|
||||
throw new Error(`Expected status "pending", got "${submission.status}"`);
|
||||
}
|
||||
if (park.slug !== parkSlug) {
|
||||
throw new Error(`Expected slug "${parkSlug}", got "${park.slug}"`);
|
||||
}
|
||||
if (park.park_type !== 'theme_park') {
|
||||
throw new Error(`Expected park_type "theme_park", got "${park.park_type}"`);
|
||||
if (submission.submission_type !== 'park') {
|
||||
throw new Error(`Expected type "park", got "${submission.submission_type}"`);
|
||||
}
|
||||
|
||||
// Test slug uniqueness constraint
|
||||
const { error: duplicateError } = await supabase
|
||||
.from('parks')
|
||||
.insert({
|
||||
name: 'Duplicate Slug Park',
|
||||
slug: parkSlug, // Same slug
|
||||
park_type: 'theme_park',
|
||||
status: 'operating'
|
||||
});
|
||||
// Approve submission
|
||||
const approval = await approveSubmission(submissionId, [itemId], authToken);
|
||||
if (!approval.success) {
|
||||
throw new Error(`Approval failed: ${approval.error}`);
|
||||
}
|
||||
|
||||
if (!duplicateError) {
|
||||
throw new Error('Duplicate slug was allowed (uniqueness constraint failed)');
|
||||
// Verify entity was created
|
||||
const { data: item } = await supabase
|
||||
.from('submission_items')
|
||||
.select('approved_entity_id, status')
|
||||
.eq('id', itemId)
|
||||
.single();
|
||||
|
||||
if (!item?.approved_entity_id) {
|
||||
throw new Error('No entity created after approval');
|
||||
}
|
||||
if (item.status !== 'approved') {
|
||||
throw new Error(`Expected item status "approved", got "${item.status}"`);
|
||||
}
|
||||
|
||||
tracker.track('parks', item.approved_entity_id);
|
||||
|
||||
// Verify park data
|
||||
const park = await pollForEntity('parks', item.approved_entity_id);
|
||||
if (!park) throw new Error('Park entity not found');
|
||||
|
||||
if (park.name !== parkData.name) {
|
||||
throw new Error(`Expected name "${parkData.name}", got "${park.name}"`);
|
||||
}
|
||||
if (park.slug !== parkData.slug) {
|
||||
throw new Error(`Expected slug "${parkData.slug}", got "${park.slug}"`);
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
@@ -78,9 +102,9 @@ export const submissionTestSuite: TestSuite = {
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
parkId,
|
||||
parkSlug,
|
||||
validationsPassed: ['name', 'slug', 'park_type', 'uniqueness_constraint']
|
||||
submissionId,
|
||||
parkId: item.approved_entity_id,
|
||||
validationsPassed: ['submission_created', 'approval_succeeded', 'entity_created']
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,76 +120,67 @@ export const submissionTestSuite: TestSuite = {
|
||||
};
|
||||
} finally {
|
||||
await tracker.cleanup();
|
||||
const remaining = await tracker.verifyCleanup();
|
||||
if (remaining.length > 0) {
|
||||
console.warn('submission-001 cleanup incomplete:', remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'submission-002',
|
||||
name: 'Ride Creation with Dependencies',
|
||||
description: 'Validates ride submission requires valid park_id',
|
||||
description: 'Validates ride submission requires valid park and creates correctly',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
const tracker = new TestDataTracker();
|
||||
let parkId: string | null = null;
|
||||
let rideId: string | null = null;
|
||||
|
||||
try {
|
||||
// First create a park
|
||||
const parkSlug = `test-park-ride-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const { data: park, error: parkError } = await supabase
|
||||
.from('parks')
|
||||
.insert({
|
||||
name: 'Test Park for Ride',
|
||||
slug: parkSlug,
|
||||
park_type: 'theme_park',
|
||||
status: 'operating',
|
||||
is_test_data: true
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (parkError) throw new Error(`Park creation failed: ${parkError.message}`);
|
||||
parkId = park.id;
|
||||
|
||||
// Try to create ride with invalid park_id (should fail)
|
||||
const invalidParkId = '00000000-0000-0000-0000-000000000000';
|
||||
const { error: invalidError } = await supabase
|
||||
.from('rides')
|
||||
.insert({
|
||||
name: 'Test Ride Invalid Park',
|
||||
slug: `test-ride-invalid-${Date.now()}`,
|
||||
park_id: invalidParkId,
|
||||
category: 'roller_coaster',
|
||||
status: 'operating'
|
||||
});
|
||||
|
||||
if (!invalidError) {
|
||||
throw new Error('Ride with invalid park_id was allowed (foreign key constraint failed)');
|
||||
const userId = await getCurrentUserId();
|
||||
const authToken = await getAuthToken();
|
||||
|
||||
// First create and approve a park
|
||||
const parkData = generateUniqueParkData('submission-002-park');
|
||||
const { submissionId: parkSubId, itemId: parkItemId } = await createTestParkSubmission(parkData, userId, tracker);
|
||||
|
||||
const parkApproval = await approveSubmission(parkSubId, [parkItemId], authToken);
|
||||
if (!parkApproval.success) {
|
||||
throw new Error(`Park approval failed: ${parkApproval.error}`);
|
||||
}
|
||||
|
||||
// Create ride with valid park_id (should succeed)
|
||||
const rideSlug = `test-ride-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const { data: ride, error: rideError } = await supabase
|
||||
.from('rides')
|
||||
.insert({
|
||||
name: 'Test Ride Valid Park',
|
||||
slug: rideSlug,
|
||||
park_id: parkId,
|
||||
category: 'roller_coaster',
|
||||
status: 'operating'
|
||||
})
|
||||
.select('id, name, park_id')
|
||||
const { data: parkItem } = await supabase
|
||||
.from('submission_items')
|
||||
.select('approved_entity_id')
|
||||
.eq('id', parkItemId)
|
||||
.single();
|
||||
|
||||
if (rideError) throw new Error(`Ride creation failed: ${rideError.message}`);
|
||||
if (!ride) throw new Error('Ride not returned after creation');
|
||||
const parkId = parkItem?.approved_entity_id;
|
||||
if (!parkId) throw new Error('Park not created');
|
||||
|
||||
tracker.track('parks', parkId);
|
||||
|
||||
rideId = ride.id;
|
||||
// Now create ride submission
|
||||
const rideData = generateUniqueRideData(parkId, 'submission-002');
|
||||
const { submissionId: rideSubId, itemId: rideItemId } = await createTestRideSubmission(rideData, userId, tracker);
|
||||
|
||||
// Approve ride
|
||||
const rideApproval = await approveSubmission(rideSubId, [rideItemId], authToken);
|
||||
if (!rideApproval.success) {
|
||||
throw new Error(`Ride approval failed: ${rideApproval.error}`);
|
||||
}
|
||||
|
||||
// Verify ride created
|
||||
const { data: rideItem } = await supabase
|
||||
.from('submission_items')
|
||||
.select('approved_entity_id')
|
||||
.eq('id', rideItemId)
|
||||
.single();
|
||||
|
||||
const rideId = rideItem?.approved_entity_id;
|
||||
if (!rideId) throw new Error('Ride not created after approval');
|
||||
|
||||
tracker.track('rides', rideId);
|
||||
|
||||
// Verify ride data
|
||||
const ride = await pollForEntity('rides', rideId);
|
||||
if (!ride) throw new Error('Ride entity not found');
|
||||
|
||||
if (ride.park_id !== parkId) {
|
||||
throw new Error(`Expected park_id "${parkId}", got "${ride.park_id}"`);
|
||||
}
|
||||
@@ -182,7 +197,7 @@ export const submissionTestSuite: TestSuite = {
|
||||
details: {
|
||||
parkId,
|
||||
rideId,
|
||||
validationsPassed: ['foreign_key_constraint', 'valid_dependency']
|
||||
validationsPassed: ['park_created', 'ride_created', 'dependency_valid']
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,52 +213,63 @@ export const submissionTestSuite: TestSuite = {
|
||||
};
|
||||
} finally {
|
||||
await tracker.cleanup();
|
||||
const remaining = await tracker.verifyCleanup();
|
||||
if (remaining.length > 0) {
|
||||
console.warn('submission-002 cleanup incomplete:', remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'submission-003',
|
||||
name: 'Company Creation All Types',
|
||||
description: 'Validates company creation for all company types',
|
||||
description: 'Validates company submission for all company types',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
const tracker = new TestDataTracker();
|
||||
const companyIds: string[] = [];
|
||||
|
||||
try {
|
||||
const userId = await getCurrentUserId();
|
||||
const authToken = await getAuthToken();
|
||||
const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner'] as const;
|
||||
const createdCompanies: Array<{ type: string; id: string }> = [];
|
||||
|
||||
for (const companyType of companyTypes) {
|
||||
const slug = `test-company-${companyType}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const companyData = generateUniqueCompanyData(companyType, `submission-003-${companyType}`);
|
||||
|
||||
const { data: company, error: createError } = await supabase
|
||||
.from('companies')
|
||||
.insert({
|
||||
name: `Test ${companyType} Company`,
|
||||
slug,
|
||||
company_type: companyType,
|
||||
description: `Test company of type ${companyType}`
|
||||
})
|
||||
.select('id, company_type')
|
||||
// Create submission
|
||||
const { submissionId, itemId } = await createTestCompanySubmission(
|
||||
companyType,
|
||||
companyData,
|
||||
userId,
|
||||
tracker
|
||||
);
|
||||
|
||||
// Approve submission
|
||||
const approval = await approveSubmission(submissionId, [itemId], authToken);
|
||||
if (!approval.success) {
|
||||
throw new Error(`${companyType} approval failed: ${approval.error}`);
|
||||
}
|
||||
|
||||
// Verify entity created
|
||||
const { data: item } = await supabase
|
||||
.from('submission_items')
|
||||
.select('approved_entity_id')
|
||||
.eq('id', itemId)
|
||||
.single();
|
||||
|
||||
if (createError) {
|
||||
throw new Error(`${companyType} creation failed: ${createError.message}`);
|
||||
}
|
||||
if (!company) {
|
||||
throw new Error(`${companyType} not returned after creation`);
|
||||
const companyId = item?.approved_entity_id;
|
||||
if (!companyId) {
|
||||
throw new Error(`${companyType} not created after approval`);
|
||||
}
|
||||
|
||||
companyIds.push(company.id);
|
||||
tracker.track('companies', company.id);
|
||||
tracker.track('companies', companyId);
|
||||
|
||||
// Verify company type
|
||||
const company = await pollForEntity('companies', companyId);
|
||||
if (!company) throw new Error(`${companyType} entity not found`);
|
||||
|
||||
if (company.company_type !== companyType) {
|
||||
throw new Error(`Expected company_type "${companyType}", got "${company.company_type}"`);
|
||||
}
|
||||
|
||||
createdCompanies.push({ type: companyType, id: companyId });
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
@@ -256,9 +282,9 @@ export const submissionTestSuite: TestSuite = {
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
companiesCreated: companyIds.length,
|
||||
companiesCreated: createdCompanies.length,
|
||||
companyTypes: companyTypes,
|
||||
companyIds
|
||||
companies: createdCompanies
|
||||
}
|
||||
};
|
||||
|
||||
@@ -274,100 +300,85 @@ export const submissionTestSuite: TestSuite = {
|
||||
};
|
||||
} finally {
|
||||
await tracker.cleanup();
|
||||
const remaining = await tracker.verifyCleanup();
|
||||
if (remaining.length > 0) {
|
||||
console.warn('submission-003 cleanup incomplete:', remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'submission-004',
|
||||
name: 'Ride Model with Images',
|
||||
description: 'Validates ride model creation with image fields',
|
||||
description: 'Validates ride model submission with image fields',
|
||||
run: async (): Promise<TestResult> => {
|
||||
const startTime = Date.now();
|
||||
let manufacturerId: string | null = null;
|
||||
let modelId: string | null = null;
|
||||
const tracker = new TestDataTracker();
|
||||
|
||||
try {
|
||||
// Create manufacturer first
|
||||
const mfgSlug = `test-mfg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const { data: manufacturer, error: mfgError } = await supabase
|
||||
.from('companies')
|
||||
.insert({
|
||||
name: 'Test Manufacturer',
|
||||
slug: mfgSlug,
|
||||
company_type: 'manufacturer'
|
||||
})
|
||||
.select('id')
|
||||
const userId = await getCurrentUserId();
|
||||
const authToken = await getAuthToken();
|
||||
|
||||
// Create and approve manufacturer
|
||||
const mfgData = generateUniqueCompanyData('manufacturer', 'submission-004-mfg');
|
||||
const { submissionId: mfgSubId, itemId: mfgItemId } = await createTestCompanySubmission(
|
||||
'manufacturer',
|
||||
mfgData,
|
||||
userId,
|
||||
tracker
|
||||
);
|
||||
|
||||
const mfgApproval = await approveSubmission(mfgSubId, [mfgItemId], authToken);
|
||||
if (!mfgApproval.success) {
|
||||
throw new Error(`Manufacturer approval failed: ${mfgApproval.error}`);
|
||||
}
|
||||
|
||||
const { data: mfgItem } = await supabase
|
||||
.from('submission_items')
|
||||
.select('approved_entity_id')
|
||||
.eq('id', mfgItemId)
|
||||
.single();
|
||||
|
||||
if (mfgError) throw new Error(`Manufacturer creation failed: ${mfgError.message}`);
|
||||
manufacturerId = manufacturer.id;
|
||||
const manufacturerId = mfgItem?.approved_entity_id;
|
||||
if (!manufacturerId) throw new Error('Manufacturer not created');
|
||||
|
||||
tracker.track('companies', manufacturerId);
|
||||
|
||||
// Create ride model with images
|
||||
const modelSlug = `test-model-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const testImageUrl = 'https://imagedelivery.net/test-account/test-image-id/public';
|
||||
const testImageId = 'test-image-id';
|
||||
// Create ride model submission
|
||||
const modelData = generateUniqueRideModelData(manufacturerId, 'submission-004');
|
||||
const { submissionId, itemId } = await createTestRideModelSubmission(modelData, userId, tracker);
|
||||
|
||||
const { data: model, error: modelError } = await supabase
|
||||
.from('ride_models')
|
||||
.insert({
|
||||
name: 'Test Ride Model',
|
||||
slug: modelSlug,
|
||||
manufacturer_id: manufacturerId,
|
||||
category: 'roller_coaster',
|
||||
ride_type: 'steel_coaster',
|
||||
banner_image_url: testImageUrl,
|
||||
banner_image_id: testImageId,
|
||||
card_image_url: testImageUrl,
|
||||
card_image_id: testImageId
|
||||
})
|
||||
.select('id, banner_image_url, banner_image_id, card_image_url, card_image_id')
|
||||
// Approve ride model
|
||||
const approval = await approveSubmission(submissionId, [itemId], authToken);
|
||||
if (!approval.success) {
|
||||
throw new Error(`Ride model approval failed: ${approval.error}`);
|
||||
}
|
||||
|
||||
// Verify entity created
|
||||
const { data: item } = await supabase
|
||||
.from('submission_items')
|
||||
.select('approved_entity_id')
|
||||
.eq('id', itemId)
|
||||
.single();
|
||||
|
||||
if (modelError) throw new Error(`Ride model creation failed: ${modelError.message}`);
|
||||
if (!model) throw new Error('Ride model not returned after creation');
|
||||
const modelId = item?.approved_entity_id;
|
||||
if (!modelId) throw new Error('Ride model not created after approval');
|
||||
|
||||
modelId = model.id;
|
||||
tracker.track('ride_models', modelId);
|
||||
|
||||
// Validate image fields
|
||||
if (model.banner_image_url !== testImageUrl) {
|
||||
throw new Error(`banner_image_url mismatch: expected "${testImageUrl}", got "${model.banner_image_url}"`);
|
||||
}
|
||||
if (model.banner_image_id !== testImageId) {
|
||||
throw new Error(`banner_image_id mismatch: expected "${testImageId}", got "${model.banner_image_id}"`);
|
||||
}
|
||||
if (model.card_image_url !== testImageUrl) {
|
||||
throw new Error(`card_image_url mismatch`);
|
||||
}
|
||||
if (model.card_image_id !== testImageId) {
|
||||
throw new Error(`card_image_id mismatch`);
|
||||
// Verify model data
|
||||
const model = await pollForEntity('ride_models', modelId);
|
||||
if (!model) throw new Error('Ride model entity not found');
|
||||
|
||||
if (model.manufacturer_id !== manufacturerId) {
|
||||
throw new Error(`Expected manufacturer_id "${manufacturerId}", got "${model.manufacturer_id}"`);
|
||||
}
|
||||
|
||||
// Verify version was created with images
|
||||
let version: any = null;
|
||||
const pollStart = Date.now();
|
||||
while (!version && Date.now() - pollStart < 5000) {
|
||||
const { data } = await supabase
|
||||
.from('ride_model_versions')
|
||||
.select('banner_image_url, banner_image_id, card_image_url, card_image_id')
|
||||
.eq('ride_model_id', modelId)
|
||||
.eq('version_number', 1)
|
||||
.single();
|
||||
|
||||
if (data) {
|
||||
version = data;
|
||||
break;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
// Verify version created
|
||||
const { data: version } = await supabase
|
||||
.from('ride_model_versions')
|
||||
.select('version_number')
|
||||
.eq('ride_model_id', modelId)
|
||||
.eq('version_number', 1)
|
||||
.single();
|
||||
|
||||
if (!version) throw new Error('Version not created after 5s timeout');
|
||||
if (version.banner_image_url !== testImageUrl) {
|
||||
throw new Error('Version missing banner_image_url');
|
||||
}
|
||||
if (!version) throw new Error('Version not created for ride model');
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
@@ -381,8 +392,8 @@ export const submissionTestSuite: TestSuite = {
|
||||
details: {
|
||||
modelId,
|
||||
manufacturerId,
|
||||
imageFieldsValidated: ['banner_image_url', 'banner_image_id', 'card_image_url', 'card_image_id'],
|
||||
versionCreated: true
|
||||
versionCreated: true,
|
||||
followedPipeline: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -397,12 +408,7 @@ export const submissionTestSuite: TestSuite = {
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
} finally {
|
||||
if (modelId) {
|
||||
await supabase.from('ride_models').delete().eq('id', modelId);
|
||||
}
|
||||
if (manufacturerId) {
|
||||
await supabase.from('companies').delete().eq('id', manufacturerId);
|
||||
}
|
||||
await tracker.cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user