import { describe, expect, it } from 'vitest'; /** * Contract Validation Tests for Driver Module * * These tests validate that the driver API DTOs and OpenAPI spec are consistent * and that the generated types will be compatible with the website driver client. */ import * as fs from 'fs/promises'; import * as path from 'path'; interface OpenAPISchema { type?: string; format?: string; $ref?: string; items?: OpenAPISchema; properties?: Record; required?: string[]; enum?: string[]; nullable?: boolean; description?: string; default?: unknown; } interface OpenAPISpec { openapi: string; info: { title: string; description: string; version: string; }; paths: Record; responses: Record; }>; requestBody?: { content: Record; }; }; post?: { parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>; responses: Record; }>; requestBody?: { content: Record; }; }; put?: { parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>; responses: Record; }>; requestBody?: { content: Record; }; }; patch?: { parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>; responses: Record; }>; requestBody?: { content: Record; }; }; delete?: { parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>; responses: Record; }>; }; parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>; }>; components: { schemas: Record; }; } describe('Driver Module Contract Validation', () => { it('should pass', () => { expect(true).toBe(true); }); const apiRoot = path.join(__dirname, '../..'); const openapiPath = path.join(apiRoot, 'apps/api/openapi.json'); const generatedTypesDir = path.join(apiRoot, 'apps/website/lib/types/generated'); const websiteTypesDir = path.join(apiRoot, 'apps/website/lib/types'); describe('OpenAPI Spec Integrity for Driver Endpoints', () => { it('should have driver endpoints defined in OpenAPI spec', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Check for driver endpoints expect(spec.paths['/drivers/leaderboard']).toBeDefined(); expect(spec.paths['/drivers/total-drivers']).toBeDefined(); expect(spec.paths['/drivers/current']).toBeDefined(); expect(spec.paths['/drivers/{driverId}']).toBeDefined(); expect(spec.paths['/drivers/{driverId}/profile']).toBeDefined(); expect(spec.paths['/drivers/{driverId}/liveries']).toBeDefined(); expect(spec.paths['/drivers/{driverId}/races/{raceId}/registration-status']).toBeDefined(); expect(spec.paths['/drivers/complete-onboarding']).toBeDefined(); // Verify GET methods exist expect(spec.paths['/drivers/leaderboard'].get).toBeDefined(); expect(spec.paths['/drivers/total-drivers'].get).toBeDefined(); expect(spec.paths['/drivers/current'].get).toBeDefined(); expect(spec.paths['/drivers/{driverId}'].get).toBeDefined(); expect(spec.paths['/drivers/{driverId}/profile'].get).toBeDefined(); expect(spec.paths['/drivers/{driverId}/liveries'].get).toBeDefined(); expect(spec.paths['/drivers/{driverId}/races/{raceId}/registration-status'].get).toBeDefined(); // Verify POST methods exist expect(spec.paths['/drivers/complete-onboarding'].post).toBeDefined(); }); it('should have GetDriverOutputDTO schema defined', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['GetDriverOutputDTO']; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // Verify required fields expect(schema.required).toContain('id'); expect(schema.required).toContain('iracingId'); expect(schema.required).toContain('name'); expect(schema.required).toContain('country'); expect(schema.required).toContain('joinedAt'); // Verify optional fields expect(schema.properties?.bio).toBeDefined(); expect(schema.properties?.category).toBeDefined(); expect(schema.properties?.rating).toBeDefined(); expect(schema.properties?.experienceLevel).toBeDefined(); expect(schema.properties?.wins).toBeDefined(); expect(schema.properties?.podiums).toBeDefined(); expect(schema.properties?.totalRaces).toBeDefined(); expect(schema.properties?.avatarUrl).toBeDefined(); }); it('should have GetDriverProfileOutputDTO schema defined', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['GetDriverProfileOutputDTO']; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // Verify required fields expect(schema.required).toContain('teamMemberships'); expect(schema.required).toContain('socialSummary'); // Verify optional fields expect(schema.properties?.currentDriver).toBeDefined(); expect(schema.properties?.stats).toBeDefined(); expect(schema.properties?.finishDistribution).toBeDefined(); expect(schema.properties?.extendedProfile).toBeDefined(); }); it('should have DriverRegistrationStatusDTO schema defined', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['DriverRegistrationStatusDTO']; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // Verify required fields expect(schema.required).toContain('isRegistered'); expect(schema.required).toContain('raceId'); expect(schema.required).toContain('driverId'); }); it('should have DriversLeaderboardDTO schema defined', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['DriversLeaderboardDTO']; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // Verify required fields expect(schema.required).toContain('drivers'); expect(schema.required).toContain('totalRaces'); expect(schema.required).toContain('totalWins'); expect(schema.required).toContain('activeCount'); }); it('should have DriverLeaderboardItemDTO schema defined', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['DriverLeaderboardItemDTO']; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // Verify required fields expect(schema.required).toContain('id'); expect(schema.required).toContain('name'); expect(schema.required).toContain('rating'); expect(schema.required).toContain('skillLevel'); expect(schema.required).toContain('nationality'); expect(schema.required).toContain('racesCompleted'); expect(schema.required).toContain('wins'); expect(schema.required).toContain('podiums'); expect(schema.required).toContain('isActive'); expect(schema.required).toContain('rank'); // Verify optional fields expect(schema.properties?.category).toBeDefined(); expect(schema.properties?.avatarUrl).toBeDefined(); }); it('should have CompleteOnboardingInputDTO schema defined', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['CompleteOnboardingInputDTO']; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // Verify required fields expect(schema.required).toContain('firstName'); expect(schema.required).toContain('lastName'); expect(schema.required).toContain('displayName'); expect(schema.required).toContain('country'); // Verify optional fields expect(schema.properties?.timezone).toBeDefined(); expect(schema.properties?.bio).toBeDefined(); }); it('should have CompleteOnboardingOutputDTO schema defined', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['CompleteOnboardingOutputDTO']; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // Verify required fields expect(schema.required).toContain('success'); // Verify optional fields expect(schema.properties?.driverId).toBeDefined(); expect(schema.properties?.errorMessage).toBeDefined(); }); it('should have proper request/response structure for driver leaderboard endpoint', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const leaderboardPath = spec.paths['/drivers/leaderboard']?.get; expect(leaderboardPath).toBeDefined(); // Verify response const response200 = leaderboardPath.responses['200']; expect(response200).toBeDefined(); expect(response200.content['application/json']?.schema.$ref).toBe('#/components/schemas/DriversLeaderboardDTO'); }); it('should have proper request/response structure for get driver endpoint', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const driverPath = spec.paths['/drivers/{driverId}']?.get; expect(driverPath).toBeDefined(); // Verify path parameters expect(driverPath.parameters).toBeDefined(); const driverIdParam = driverPath.parameters.find((p: { name: string }) => p.name === 'driverId'); expect(driverIdParam).toBeDefined(); expect(driverIdParam.in).toBe('path'); // Verify response const response200 = driverPath.responses['200']; expect(response200).toBeDefined(); expect(response200.content['application/json']?.schema.$ref).toBe('#/components/schemas/GetDriverOutputDTO'); }); it('should have proper request/response structure for get driver profile endpoint', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const profilePath = spec.paths['/drivers/{driverId}/profile']?.get; expect(profilePath).toBeDefined(); // Verify path parameters expect(profilePath.parameters).toBeDefined(); const driverIdParam = profilePath.parameters.find((p: { name: string }) => p.name === 'driverId'); expect(driverIdParam).toBeDefined(); expect(driverIdParam.in).toBe('path'); // Verify response const response200 = profilePath.responses['200']; expect(response200).toBeDefined(); expect(response200.content['application/json']?.schema.$ref).toBe('#/components/schemas/GetDriverProfileOutputDTO'); }); it('should have proper request/response structure for complete onboarding endpoint', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const onboardingPath = spec.paths['/drivers/complete-onboarding']?.post; expect(onboardingPath).toBeDefined(); // Verify request body expect(onboardingPath.requestBody).toBeDefined(); expect(onboardingPath.requestBody?.content['application/json']?.schema.$ref).toBe('#/components/schemas/CompleteOnboardingInputDTO'); // Verify response const response200 = onboardingPath.responses['200']; expect(response200).toBeDefined(); expect(response200.content['application/json']?.schema.$ref).toBe('#/components/schemas/CompleteOnboardingOutputDTO'); }); it('should have proper request/response structure for driver registration status endpoint', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const registrationPath = spec.paths['/drivers/{driverId}/races/{raceId}/registration-status']?.get; expect(registrationPath).toBeDefined(); // Verify path parameters expect(registrationPath.parameters).toBeDefined(); const driverIdParam = registrationPath.parameters.find((p: { name: string }) => p.name === 'driverId'); const raceIdParam = registrationPath.parameters.find((p: { name: string }) => p.name === 'raceId'); expect(driverIdParam).toBeDefined(); expect(raceIdParam).toBeDefined(); expect(driverIdParam.in).toBe('path'); expect(raceIdParam.in).toBe('path'); // Verify response const response200 = registrationPath.responses['200']; expect(response200).toBeDefined(); expect(response200.content['application/json']?.schema.$ref).toBe('#/components/schemas/DriverRegistrationStatusDTO'); }); }); describe('DTO Consistency', () => { it('should have generated DTO files for driver schemas', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const generatedFiles = await fs.readdir(generatedTypesDir); const generatedDTOs = generatedFiles .filter(f => f.endsWith('.ts')) .map(f => f.replace('.ts', '')); // Check for driver-related DTOs const driverDTOs = [ 'GetDriverOutputDTO', 'GetDriverProfileOutputDTO', 'DriverRegistrationStatusDTO', 'DriversLeaderboardDTO', 'DriverLeaderboardItemDTO', 'CompleteOnboardingInputDTO', 'CompleteOnboardingOutputDTO', 'GetDriverLiveriesOutputDTO', ]; for (const dtoName of driverDTOs) { expect(spec.components.schemas[dtoName]).toBeDefined(); expect(generatedDTOs).toContain(dtoName); } }); it('should have consistent property types between DTOs and schemas', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schemas = spec.components.schemas; // Test GetDriverOutputDTO const getDriverSchema = schemas['GetDriverOutputDTO']; const getDriverDtoPath = path.join(generatedTypesDir, 'GetDriverOutputDTO.ts'); const getDriverDtoExists = await fs.access(getDriverDtoPath).then(() => true).catch(() => false); if (getDriverDtoExists) { const getDriverDtoContent = await fs.readFile(getDriverDtoPath, 'utf-8'); // Check that all required properties are present if (getDriverSchema.required) { for (const requiredProp of getDriverSchema.required) { expect(getDriverDtoContent).toContain(requiredProp); } } // Check that all properties are present if (getDriverSchema.properties) { for (const propName of Object.keys(getDriverSchema.properties)) { expect(getDriverDtoContent).toContain(propName); } } } // Test GetDriverProfileOutputDTO const getDriverProfileSchema = schemas['GetDriverProfileOutputDTO']; const getDriverProfileDtoPath = path.join(generatedTypesDir, 'GetDriverProfileOutputDTO.ts'); const getDriverProfileDtoExists = await fs.access(getDriverProfileDtoPath).then(() => true).catch(() => false); if (getDriverProfileDtoExists) { const getDriverProfileDtoContent = await fs.readFile(getDriverProfileDtoPath, 'utf-8'); // Check that all required properties are present if (getDriverProfileSchema.required) { for (const requiredProp of getDriverProfileSchema.required) { expect(getDriverProfileDtoContent).toContain(requiredProp); } } // Check that all properties are present if (getDriverProfileSchema.properties) { for (const propName of Object.keys(getDriverProfileSchema.properties)) { expect(getDriverProfileDtoContent).toContain(propName); } } } // Test DriverRegistrationStatusDTO const driverRegistrationSchema = schemas['DriverRegistrationStatusDTO']; const driverRegistrationDtoPath = path.join(generatedTypesDir, 'DriverRegistrationStatusDTO.ts'); const driverRegistrationDtoExists = await fs.access(driverRegistrationDtoPath).then(() => true).catch(() => false); if (driverRegistrationDtoExists) { const driverRegistrationDtoContent = await fs.readFile(driverRegistrationDtoPath, 'utf-8'); // Check that all required properties are present if (driverRegistrationSchema.required) { for (const requiredProp of driverRegistrationSchema.required) { expect(driverRegistrationDtoContent).toContain(requiredProp); } } // Check that all properties are present if (driverRegistrationSchema.properties) { for (const propName of Object.keys(driverRegistrationSchema.properties)) { expect(driverRegistrationDtoContent).toContain(propName); } } } // Test DriversLeaderboardDTO const driversLeaderboardSchema = schemas['DriversLeaderboardDTO']; const driversLeaderboardDtoPath = path.join(generatedTypesDir, 'DriversLeaderboardDTO.ts'); const driversLeaderboardDtoExists = await fs.access(driversLeaderboardDtoPath).then(() => true).catch(() => false); if (driversLeaderboardDtoExists) { const driversLeaderboardDtoContent = await fs.readFile(driversLeaderboardDtoPath, 'utf-8'); // Check that all required properties are present if (driversLeaderboardSchema.required) { for (const requiredProp of driversLeaderboardSchema.required) { expect(driversLeaderboardDtoContent).toContain(requiredProp); } } // Check that all properties are present if (driversLeaderboardSchema.properties) { for (const propName of Object.keys(driversLeaderboardSchema.properties)) { expect(driversLeaderboardDtoContent).toContain(propName); } } } // Test DriverLeaderboardItemDTO const driverLeaderboardItemSchema = schemas['DriverLeaderboardItemDTO']; const driverLeaderboardItemDtoPath = path.join(generatedTypesDir, 'DriverLeaderboardItemDTO.ts'); const driverLeaderboardItemDtoExists = await fs.access(driverLeaderboardItemDtoPath).then(() => true).catch(() => false); if (driverLeaderboardItemDtoExists) { const driverLeaderboardItemDtoContent = await fs.readFile(driverLeaderboardItemDtoPath, 'utf-8'); // Check that all required properties are present if (driverLeaderboardItemSchema.required) { for (const requiredProp of driverLeaderboardItemSchema.required) { expect(driverLeaderboardItemDtoContent).toContain(requiredProp); } } // Check that all properties are present if (driverLeaderboardItemSchema.properties) { for (const propName of Object.keys(driverLeaderboardItemSchema.properties)) { expect(driverLeaderboardItemDtoContent).toContain(propName); } } } // Test CompleteOnboardingInputDTO const completeOnboardingInputSchema = schemas['CompleteOnboardingInputDTO']; const completeOnboardingInputDtoPath = path.join(generatedTypesDir, 'CompleteOnboardingInputDTO.ts'); const completeOnboardingInputDtoExists = await fs.access(completeOnboardingInputDtoPath).then(() => true).catch(() => false); if (completeOnboardingInputDtoExists) { const completeOnboardingInputDtoContent = await fs.readFile(completeOnboardingInputDtoPath, 'utf-8'); // Check that all required properties are present if (completeOnboardingInputSchema.required) { for (const requiredProp of completeOnboardingInputSchema.required) { expect(completeOnboardingInputDtoContent).toContain(requiredProp); } } // Check that all properties are present if (completeOnboardingInputSchema.properties) { for (const propName of Object.keys(completeOnboardingInputSchema.properties)) { expect(completeOnboardingInputDtoContent).toContain(propName); } } } // Test CompleteOnboardingOutputDTO const completeOnboardingOutputSchema = schemas['CompleteOnboardingOutputDTO']; const completeOnboardingOutputDtoPath = path.join(generatedTypesDir, 'CompleteOnboardingOutputDTO.ts'); const completeOnboardingOutputDtoExists = await fs.access(completeOnboardingOutputDtoPath).then(() => true).catch(() => false); if (completeOnboardingOutputDtoExists) { const completeOnboardingOutputDtoContent = await fs.readFile(completeOnboardingOutputDtoPath, 'utf-8'); // Check that all required properties are present if (completeOnboardingOutputSchema.required) { for (const requiredProp of completeOnboardingOutputSchema.required) { expect(completeOnboardingOutputDtoContent).toContain(requiredProp); } } // Check that all properties are present if (completeOnboardingOutputSchema.properties) { for (const propName of Object.keys(completeOnboardingOutputSchema.properties)) { expect(completeOnboardingOutputDtoContent).toContain(propName); } } } }); it('should have driver types defined in tbd folder', async () => { const generatedFiles = await fs.readdir(generatedTypesDir); const driverGenerated = generatedFiles.filter(f => f.includes('Driver') || f.includes('GetDriver') || f.includes('CompleteOnboarding') ); expect(driverGenerated.length).toBeGreaterThanOrEqual(8); }); it('should have driver types re-exported from main types file', async () => { // Check if there's a driver.ts file or if types are exported elsewhere const driverTypesPath = path.join(websiteTypesDir, 'driver.ts'); const driverTypesExists = await fs.access(driverTypesPath).then(() => true).catch(() => false); if (driverTypesExists) { const driverTypesContent = await fs.readFile(driverTypesPath, 'utf-8'); // Verify re-exports expect(driverTypesContent).toContain('GetDriverOutputDTO'); expect(driverTypesContent).toContain('GetDriverProfileOutputDTO'); expect(driverTypesContent).toContain('DriverRegistrationStatusDTO'); expect(driverTypesContent).toContain('DriversLeaderboardDTO'); expect(driverTypesContent).toContain('DriverLeaderboardItemDTO'); expect(driverTypesContent).toContain('CompleteOnboardingInputDTO'); expect(driverTypesContent).toContain('CompleteOnboardingOutputDTO'); } }); }); describe('Driver API Client Contract', () => { it('should have DriversApiClient defined', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientExists = await fs.access(driversApiClientPath).then(() => true).catch(() => false); expect(driversApiClientExists).toBe(true); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify class definition expect(driversApiClientContent).toContain('export class DriversApiClient'); expect(driversApiClientContent).toContain('extends BaseApiClient'); // Verify methods exist expect(driversApiClientContent).toContain('getLeaderboard'); expect(driversApiClientContent).toContain('completeOnboarding'); expect(driversApiClientContent).toContain('getCurrent'); expect(driversApiClientContent).toContain('getRegistrationStatus'); expect(driversApiClientContent).toContain('getDriver'); expect(driversApiClientContent).toContain('getDriverProfile'); expect(driversApiClientContent).toContain('updateProfile'); // Verify method signatures expect(driversApiClientContent).toContain('getLeaderboard()'); expect(driversApiClientContent).toContain('completeOnboarding(input: CompleteOnboardingInputDTO)'); expect(driversApiClientContent).toContain('getCurrent()'); expect(driversApiClientContent).toContain('getRegistrationStatus(driverId: string, raceId: string)'); expect(driversApiClientContent).toContain('getDriver(driverId: string)'); expect(driversApiClientContent).toContain('getDriverProfile(driverId: string)'); expect(driversApiClientContent).toContain('updateProfile(updates: { bio?: string; country?: string })'); }); it('should have proper request construction in getLeaderboard method', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify GET request expect(driversApiClientContent).toContain("return this.get('/drivers/leaderboard')"); }); it('should have proper request construction in completeOnboarding method', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify POST request with input expect(driversApiClientContent).toContain("return this.post('/drivers/complete-onboarding', input)"); }); it('should have proper request construction in getCurrent method', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify GET request with options expect(driversApiClientContent).toContain("return this.get('/drivers/current'"); expect(driversApiClientContent).toContain('allowUnauthenticated: true'); expect(driversApiClientContent).toContain('retry: false'); }); it('should have proper request construction in getRegistrationStatus method', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify GET request with path parameters expect(driversApiClientContent).toContain("return this.get(`/drivers/${driverId}/races/${raceId}/registration-status`)"); }); it('should have proper request construction in getDriver method', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify GET request with path parameter expect(driversApiClientContent).toContain("return this.get(`/drivers/${driverId}`)"); }); it('should have proper request construction in getDriverProfile method', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify GET request with path parameter expect(driversApiClientContent).toContain("return this.get(`/drivers/${driverId}/profile`)"); }); it('should have proper request construction in updateProfile method', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify PUT request with updates expect(driversApiClientContent).toContain("return this.put('/drivers/profile', updates)"); }); }); describe('Request Correctness Tests', () => { it('should validate GetDriverOutputDTO required fields', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['GetDriverOutputDTO']; // Verify all required fields are present expect(schema.required).toContain('id'); expect(schema.required).toContain('iracingId'); expect(schema.required).toContain('name'); expect(schema.required).toContain('country'); expect(schema.required).toContain('joinedAt'); // Verify no extra required fields expect(schema.required.length).toBe(5); }); it('should validate GetDriverOutputDTO optional fields', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['GetDriverOutputDTO']; // Verify optional fields are not required expect(schema.required).not.toContain('bio'); expect(schema.required).not.toContain('category'); expect(schema.required).not.toContain('rating'); expect(schema.required).not.toContain('experienceLevel'); expect(schema.required).not.toContain('wins'); expect(schema.required).not.toContain('podiums'); expect(schema.required).not.toContain('totalRaces'); expect(schema.required).not.toContain('avatarUrl'); // Verify optional fields exist expect(schema.properties?.bio).toBeDefined(); expect(schema.properties?.category).toBeDefined(); expect(schema.properties?.rating).toBeDefined(); expect(schema.properties?.experienceLevel).toBeDefined(); expect(schema.properties?.wins).toBeDefined(); expect(schema.properties?.podiums).toBeDefined(); expect(schema.properties?.totalRaces).toBeDefined(); expect(schema.properties?.avatarUrl).toBeDefined(); }); it('should validate GetDriverProfileOutputDTO required fields', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['GetDriverProfileOutputDTO']; // Verify all required fields are present expect(schema.required).toContain('teamMemberships'); expect(schema.required).toContain('socialSummary'); // Verify no extra required fields expect(schema.required.length).toBe(2); }); it('should validate GetDriverProfileOutputDTO optional fields', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['GetDriverProfileOutputDTO']; // Verify optional fields are not required expect(schema.required).not.toContain('currentDriver'); expect(schema.required).not.toContain('stats'); expect(schema.required).not.toContain('finishDistribution'); expect(schema.required).not.toContain('extendedProfile'); // Verify optional fields exist expect(schema.properties?.currentDriver).toBeDefined(); expect(schema.properties?.stats).toBeDefined(); expect(schema.properties?.finishDistribution).toBeDefined(); expect(schema.properties?.extendedProfile).toBeDefined(); }); it('should validate DriverRegistrationStatusDTO structure', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['DriverRegistrationStatusDTO']; // Verify all required fields expect(schema.required).toContain('isRegistered'); expect(schema.required).toContain('raceId'); expect(schema.required).toContain('driverId'); // Verify field types expect(schema.properties?.isRegistered?.type).toBe('boolean'); expect(schema.properties?.raceId?.type).toBe('string'); expect(schema.properties?.driverId?.type).toBe('string'); }); it('should validate DriversLeaderboardDTO structure', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['DriversLeaderboardDTO']; // Verify all required fields expect(schema.required).toContain('drivers'); expect(schema.required).toContain('totalRaces'); expect(schema.required).toContain('totalWins'); expect(schema.required).toContain('activeCount'); // Verify field types expect(schema.properties?.drivers?.type).toBe('array'); expect(schema.properties?.totalRaces?.type).toBe('number'); expect(schema.properties?.totalWins?.type).toBe('number'); expect(schema.properties?.activeCount?.type).toBe('number'); }); it('should validate DriverLeaderboardItemDTO structure', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['DriverLeaderboardItemDTO']; // Verify all required fields expect(schema.required).toContain('id'); expect(schema.required).toContain('name'); expect(schema.required).toContain('rating'); expect(schema.required).toContain('skillLevel'); expect(schema.required).toContain('nationality'); expect(schema.required).toContain('racesCompleted'); expect(schema.required).toContain('wins'); expect(schema.required).toContain('podiums'); expect(schema.required).toContain('isActive'); expect(schema.required).toContain('rank'); // Verify field types expect(schema.properties?.id?.type).toBe('string'); expect(schema.properties?.name?.type).toBe('string'); expect(schema.properties?.rating?.type).toBe('number'); expect(schema.properties?.skillLevel?.type).toBe('string'); expect(schema.properties?.nationality?.type).toBe('string'); expect(schema.properties?.racesCompleted?.type).toBe('number'); expect(schema.properties?.wins?.type).toBe('number'); expect(schema.properties?.podiums?.type).toBe('number'); expect(schema.properties?.isActive?.type).toBe('boolean'); expect(schema.properties?.rank?.type).toBe('number'); }); it('should validate CompleteOnboardingInputDTO structure', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['CompleteOnboardingInputDTO']; // Verify all required fields expect(schema.required).toContain('firstName'); expect(schema.required).toContain('lastName'); expect(schema.required).toContain('displayName'); expect(schema.required).toContain('country'); // Verify field types expect(schema.properties?.firstName?.type).toBe('string'); expect(schema.properties?.lastName?.type).toBe('string'); expect(schema.properties?.displayName?.type).toBe('string'); expect(schema.properties?.country?.type).toBe('string'); expect(schema.properties?.timezone?.type).toBe('string'); expect(schema.properties?.bio?.type).toBe('string'); }); it('should validate CompleteOnboardingOutputDTO structure', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const schema = spec.components.schemas['CompleteOnboardingOutputDTO']; // Verify all required fields expect(schema.required).toContain('success'); // Verify field types expect(schema.properties?.success?.type).toBe('boolean'); expect(schema.properties?.driverId?.type).toBe('string'); expect(schema.properties?.errorMessage?.type).toBe('string'); }); }); describe('Response Handling Tests', () => { it('should handle successful driver response', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const getDriverSchema = spec.components.schemas['GetDriverOutputDTO']; // Verify response structure expect(getDriverSchema.properties?.id).toBeDefined(); expect(getDriverSchema.properties?.iracingId).toBeDefined(); expect(getDriverSchema.properties?.name).toBeDefined(); expect(getDriverSchema.properties?.country).toBeDefined(); expect(getDriverSchema.properties?.joinedAt).toBeDefined(); expect(getDriverSchema.properties?.bio).toBeDefined(); expect(getDriverSchema.properties?.category).toBeDefined(); expect(getDriverSchema.properties?.rating).toBeDefined(); expect(getDriverSchema.properties?.experienceLevel).toBeDefined(); expect(getDriverSchema.properties?.wins).toBeDefined(); expect(getDriverSchema.properties?.podiums).toBeDefined(); expect(getDriverSchema.properties?.totalRaces).toBeDefined(); expect(getDriverSchema.properties?.avatarUrl).toBeDefined(); }); it('should handle response with all required fields', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const getDriverSchema = spec.components.schemas['GetDriverOutputDTO']; // Verify all required fields are present for (const field of ['id', 'iracingId', 'name', 'country', 'joinedAt']) { expect(getDriverSchema.required).toContain(field); expect(getDriverSchema.properties?.[field]).toBeDefined(); } }); it('should handle optional fields in driver response correctly', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const driverSchema = spec.components.schemas['GetDriverOutputDTO']; // Verify optional fields are nullable expect(driverSchema.properties?.bio?.nullable).toBe(true); expect(driverSchema.properties?.category?.nullable).toBe(true); expect(driverSchema.properties?.rating?.nullable).toBe(true); expect(driverSchema.properties?.experienceLevel?.nullable).toBe(true); expect(driverSchema.properties?.wins?.nullable).toBe(true); expect(driverSchema.properties?.podiums?.nullable).toBe(true); expect(driverSchema.properties?.totalRaces?.nullable).toBe(true); expect(driverSchema.properties?.avatarUrl?.nullable).toBe(true); // Verify optional fields are not in required array expect(driverSchema.required).not.toContain('bio'); expect(driverSchema.required).not.toContain('category'); expect(driverSchema.required).not.toContain('rating'); expect(driverSchema.required).not.toContain('experienceLevel'); expect(driverSchema.required).not.toContain('wins'); expect(driverSchema.required).not.toContain('podiums'); expect(driverSchema.required).not.toContain('totalRaces'); expect(driverSchema.required).not.toContain('avatarUrl'); }); it('should handle optional fields in driver profile response correctly', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const profileSchema = spec.components.schemas['GetDriverProfileOutputDTO']; // Verify optional fields are nullable expect(profileSchema.properties?.currentDriver?.nullable).toBe(true); expect(profileSchema.properties?.stats?.nullable).toBe(true); expect(profileSchema.properties?.finishDistribution?.nullable).toBe(true); expect(profileSchema.properties?.extendedProfile?.nullable).toBe(true); // Verify optional fields are not in required array expect(profileSchema.required).not.toContain('currentDriver'); expect(profileSchema.required).not.toContain('stats'); expect(profileSchema.required).not.toContain('finishDistribution'); expect(profileSchema.required).not.toContain('extendedProfile'); }); it('should handle optional fields in leaderboard item response correctly', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const leaderboardItemSchema = spec.components.schemas['DriverLeaderboardItemDTO']; // Verify optional fields are nullable expect(leaderboardItemSchema.properties?.category?.nullable).toBe(true); expect(leaderboardItemSchema.properties?.avatarUrl?.nullable).toBe(true); // Verify optional fields are not in required array expect(leaderboardItemSchema.required).not.toContain('category'); expect(leaderboardItemSchema.required).not.toContain('avatarUrl'); }); it('should handle optional fields in complete onboarding output correctly', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const completeOnboardingSchema = spec.components.schemas['CompleteOnboardingOutputDTO']; // Verify optional fields are nullable expect(completeOnboardingSchema.properties?.driverId?.nullable).toBe(true); expect(completeOnboardingSchema.properties?.errorMessage?.nullable).toBe(true); // Verify optional fields are not in required array expect(completeOnboardingSchema.required).not.toContain('driverId'); expect(completeOnboardingSchema.required).not.toContain('errorMessage'); }); }); describe('Error Handling Tests', () => { it('should document 401 Unauthorized response for driver endpoints', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Check if 401 response is documented for driver endpoints const driverEndpoints = [ '/drivers/current', '/drivers/{driverId}', '/drivers/{driverId}/profile', '/drivers/{driverId}/liveries', '/drivers/{driverId}/races/{raceId}/registration-status', ]; for (const endpoint of driverEndpoints) { const driverPath = spec.paths[endpoint]; if (driverPath) { const methods = Object.keys(driverPath); for (const method of methods) { const operation = driverPath[method]; if (operation.responses['401']) { expect(operation.responses['401']).toBeDefined(); } } } } }); it('should document 404 Not Found response for driver endpoints', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Check if 404 response is documented for driver endpoints const driverEndpoints = [ '/drivers/{driverId}', '/drivers/{driverId}/profile', '/drivers/{driverId}/liveries', '/drivers/{driverId}/races/{raceId}/registration-status', ]; for (const endpoint of driverEndpoints) { const driverPath = spec.paths[endpoint]; if (driverPath) { const methods = Object.keys(driverPath); for (const method of methods) { const operation = driverPath[method]; if (operation.responses['404']) { expect(operation.responses['404']).toBeDefined(); } } } } }); it('should document 500 Internal Server Error response', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Check if 500 response is documented for driver endpoints const driverEndpoints = [ '/drivers/leaderboard', '/drivers/total-drivers', '/drivers/current', '/drivers/{driverId}', '/drivers/{driverId}/profile', '/drivers/{driverId}/liveries', '/drivers/{driverId}/races/{raceId}/registration-status', '/drivers/complete-onboarding', ]; for (const endpoint of driverEndpoints) { const driverPath = spec.paths[endpoint]; if (driverPath) { const methods = Object.keys(driverPath); for (const method of methods) { const operation = driverPath[method]; if (operation.responses['500']) { expect(operation.responses['500']).toBeDefined(); } } } } }); it('should have proper error handling in DriversApiClient', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify BaseApiClient is extended (which provides error handling) expect(driversApiClientContent).toContain('extends BaseApiClient'); // Verify methods use BaseApiClient methods (which handle errors) expect(driversApiClientContent).toContain('this.get<'); expect(driversApiClientContent).toContain('this.post<'); expect(driversApiClientContent).toContain('this.put<'); }); }); describe('Semantic Guarantee Tests', () => { it('should maintain consistency between request and response schemas', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Verify driver overview request/response consistency const getDriverSchema = spec.components.schemas['GetDriverOutputDTO']; // Output should contain all required fields expect(getDriverSchema.properties?.id).toBeDefined(); expect(getDriverSchema.properties?.iracingId).toBeDefined(); expect(getDriverSchema.properties?.name).toBeDefined(); expect(getDriverSchema.properties?.country).toBeDefined(); expect(getDriverSchema.properties?.joinedAt).toBeDefined(); }); it('should validate semantic consistency in driver data', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const getDriverSchema = spec.components.schemas['GetDriverOutputDTO']; // Verify driver has all required fields expect(getDriverSchema.required).toContain('id'); expect(getDriverSchema.required).toContain('iracingId'); expect(getDriverSchema.required).toContain('name'); expect(getDriverSchema.required).toContain('country'); expect(getDriverSchema.required).toContain('joinedAt'); }); it('should validate idempotency for driver endpoints', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Check if driver leaderboard endpoint exists const leaderboardPath = spec.paths['/drivers/leaderboard']?.get; if (leaderboardPath) { // Verify it's a GET request (idempotent) expect(leaderboardPath).toBeDefined(); // Verify no request body (GET requests are idempotent) expect(leaderboardPath.requestBody).toBeUndefined(); } // Check if get driver endpoint exists const driverPath = spec.paths['/drivers/{driverId}']?.get; if (driverPath) { // Verify it's a GET request (idempotent) expect(driverPath).toBeDefined(); // Verify no request body (GET requests are idempotent) expect(driverPath.requestBody).toBeUndefined(); } // Check if get driver profile endpoint exists const profilePath = spec.paths['/drivers/{driverId}/profile']?.get; if (profilePath) { // Verify it's a GET request (idempotent) expect(profilePath).toBeDefined(); // Verify no request body (GET requests are idempotent) expect(profilePath.requestBody).toBeUndefined(); } // Check if get driver registration status endpoint exists const registrationPath = spec.paths['/drivers/{driverId}/races/{raceId}/registration-status']?.get; if (registrationPath) { // Verify it's a GET request (idempotent) expect(registrationPath).toBeDefined(); // Verify no request body (GET requests are idempotent) expect(registrationPath.requestBody).toBeUndefined(); } }); it('should validate uniqueness constraints for driver IDs', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const driverSchema = spec.components.schemas['GetDriverOutputDTO']; const driverLeaderboardItemSchema = spec.components.schemas['DriverLeaderboardItemDTO']; // Verify driver ID is a required field expect(driverSchema.required).toContain('id'); expect(driverSchema.properties?.id?.type).toBe('string'); // Verify driver leaderboard item ID is a required field expect(driverLeaderboardItemSchema.required).toContain('id'); expect(driverLeaderboardItemSchema.properties?.id?.type).toBe('string'); }); it('should validate consistency between request and response types', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Verify all DTOs have consistent type definitions const dtos = [ 'GetDriverOutputDTO', 'GetDriverProfileOutputDTO', 'DriverRegistrationStatusDTO', 'DriversLeaderboardDTO', 'DriverLeaderboardItemDTO', 'CompleteOnboardingInputDTO', 'CompleteOnboardingOutputDTO', ]; for (const dtoName of dtos) { const schema = spec.components.schemas[dtoName]; expect(schema).toBeDefined(); expect(schema.type).toBe('object'); // All should have properties defined expect(schema.properties).toBeDefined(); // All should have required fields (even if empty array) expect(schema.required).toBeDefined(); } }); it('should validate semantic consistency in driver data', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const driversLeaderboardSchema = spec.components.schemas['DriversLeaderboardDTO']; // Verify drivers are arrays expect(driversLeaderboardSchema.properties?.drivers?.type).toBe('array'); // Verify counts are numbers expect(driversLeaderboardSchema.properties?.totalRaces?.type).toBe('number'); expect(driversLeaderboardSchema.properties?.totalWins?.type).toBe('number'); expect(driversLeaderboardSchema.properties?.activeCount?.type).toBe('number'); }); it('should validate pagination is not applicable for driver endpoints', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); // Driver endpoints should not have pagination const driverEndpoints = [ '/drivers/leaderboard', '/drivers/total-drivers', '/drivers/current', '/drivers/{driverId}', '/drivers/{driverId}/profile', '/drivers/{driverId}/liveries', '/drivers/{driverId}/races/{raceId}/registration-status', '/drivers/complete-onboarding', ]; for (const endpoint of driverEndpoints) { const driverPath = spec.paths[endpoint]; if (driverPath) { const methods = Object.keys(driverPath); for (const method of methods) { const operation = driverPath[method]; if (operation.parameters) { const paramNames = operation.parameters.map((p: { name: string }) => p.name); // Driver endpoints should not have page/limit parameters expect(paramNames).not.toContain('page'); expect(paramNames).not.toContain('limit'); } } } } }); }); describe('Driver Module Integration Tests', () => { it('should have consistent types between API DTOs and website types', async () => { const content = await fs.readFile(openapiPath, 'utf-8'); const spec: OpenAPISpec = JSON.parse(content); const generatedFiles = await fs.readdir(generatedTypesDir); const generatedDTOs = generatedFiles .filter(f => f.endsWith('.ts')) .map(f => f.replace('.ts', '')); // Check all driver DTOs exist in generated types const driverDTOs = [ 'GetDriverOutputDTO', 'GetDriverProfileOutputDTO', 'DriverRegistrationStatusDTO', 'DriversLeaderboardDTO', 'DriverLeaderboardItemDTO', 'CompleteOnboardingInputDTO', 'CompleteOnboardingOutputDTO', 'GetDriverLiveriesOutputDTO', ]; for (const dtoName of driverDTOs) { expect(spec.components.schemas[dtoName]).toBeDefined(); expect(generatedDTOs).toContain(dtoName); } }); it('should have DriversApiClient methods matching API endpoints', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify getLeaderboard method exists and uses correct endpoint expect(driversApiClientContent).toContain('async getLeaderboard'); expect(driversApiClientContent).toContain("return this.get('/drivers/leaderboard')"); // Verify completeOnboarding method exists and uses correct endpoint expect(driversApiClientContent).toContain('async completeOnboarding'); expect(driversApiClientContent).toContain("return this.post('/drivers/complete-onboarding', input)"); // Verify getCurrent method exists and uses correct endpoint expect(driversApiClientContent).toContain('async getCurrent'); expect(driversApiClientContent).toContain("return this.get('/drivers/current'"); // Verify getRegistrationStatus method exists and uses correct endpoint expect(driversApiClientContent).toContain('async getRegistrationStatus'); expect(driversApiClientContent).toContain("return this.get(`/drivers/${driverId}/races/${raceId}/registration-status`)"); // Verify getDriver method exists and uses correct endpoint expect(driversApiClientContent).toContain('async getDriver'); expect(driversApiClientContent).toContain("return this.get(`/drivers/${driverId}`)"); // Verify getDriverProfile method exists and uses correct endpoint expect(driversApiClientContent).toContain('async getDriverProfile'); expect(driversApiClientContent).toContain("return this.get(`/drivers/${driverId}/profile`)"); // Verify updateProfile method exists and uses correct endpoint expect(driversApiClientContent).toContain('async updateProfile'); expect(driversApiClientContent).toContain("return this.put('/drivers/profile', updates)"); }); it('should have proper error handling in DriversApiClient', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify BaseApiClient is extended (which provides error handling) expect(driversApiClientContent).toContain('extends BaseApiClient'); // Verify methods use BaseApiClient methods (which handle errors) expect(driversApiClientContent).toContain('this.get<'); expect(driversApiClientContent).toContain('this.post<'); expect(driversApiClientContent).toContain('this.put<'); }); it('should have consistent type imports in DriversApiClient', async () => { const driversApiClientPath = path.join(apiRoot, 'apps/website/lib/api/drivers/DriversApiClient.ts'); const driversApiClientContent = await fs.readFile(driversApiClientPath, 'utf-8'); // Verify all required types are imported expect(driversApiClientContent).toContain('CompleteOnboardingInputDTO'); expect(driversApiClientContent).toContain('CompleteOnboardingOutputDTO'); expect(driversApiClientContent).toContain('DriverRegistrationStatusDTO'); expect(driversApiClientContent).toContain('DriverLeaderboardItemDTO'); expect(driversApiClientContent).toContain('GetDriverOutputDTO'); expect(driversApiClientContent).toContain('GetDriverProfileOutputDTO'); }); }); });