1329 lines
57 KiB
TypeScript
1329 lines
57 KiB
TypeScript
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<string, OpenAPISchema>;
|
|
required?: string[];
|
|
enum?: string[];
|
|
nullable?: boolean;
|
|
description?: string;
|
|
default?: unknown;
|
|
}
|
|
|
|
interface OpenAPISpec {
|
|
openapi: string;
|
|
info: {
|
|
title: string;
|
|
description: string;
|
|
version: string;
|
|
};
|
|
paths: Record<string, {
|
|
get?: {
|
|
parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>;
|
|
responses: Record<string, {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
}>;
|
|
requestBody?: {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
};
|
|
};
|
|
post?: {
|
|
parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>;
|
|
responses: Record<string, {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
}>;
|
|
requestBody?: {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
};
|
|
};
|
|
put?: {
|
|
parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>;
|
|
responses: Record<string, {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
}>;
|
|
requestBody?: {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
};
|
|
};
|
|
patch?: {
|
|
parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>;
|
|
responses: Record<string, {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
}>;
|
|
requestBody?: {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
};
|
|
};
|
|
delete?: {
|
|
parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>;
|
|
responses: Record<string, {
|
|
content: Record<string, {
|
|
schema: { $ref: string };
|
|
}>;
|
|
}>;
|
|
};
|
|
parameters?: Array<{ name: string; in: string; schema?: OpenAPISchema }>;
|
|
}>;
|
|
components: {
|
|
schemas: Record<string, OpenAPISchema>;
|
|
};
|
|
}
|
|
|
|
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<DriversLeaderboardDto>('/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<CompleteOnboardingOutputDTO>('/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<GetDriverOutputDTO | null>('/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<DriverRegistrationStatusDTO>(`/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<GetDriverOutputDTO | null>(`/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<GetDriverProfileOutputDTO>(`/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<GetDriverOutputDTO>('/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<DriversLeaderboardDto>('/drivers/leaderboard')");
|
|
|
|
// Verify completeOnboarding method exists and uses correct endpoint
|
|
expect(driversApiClientContent).toContain('async completeOnboarding');
|
|
expect(driversApiClientContent).toContain("return this.post<CompleteOnboardingOutputDTO>('/drivers/complete-onboarding', input)");
|
|
|
|
// Verify getCurrent method exists and uses correct endpoint
|
|
expect(driversApiClientContent).toContain('async getCurrent');
|
|
expect(driversApiClientContent).toContain("return this.get<GetDriverOutputDTO | null>('/drivers/current'");
|
|
|
|
// Verify getRegistrationStatus method exists and uses correct endpoint
|
|
expect(driversApiClientContent).toContain('async getRegistrationStatus');
|
|
expect(driversApiClientContent).toContain("return this.get<DriverRegistrationStatusDTO>(`/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<GetDriverOutputDTO | null>(`/drivers/${driverId}`)");
|
|
|
|
// Verify getDriverProfile method exists and uses correct endpoint
|
|
expect(driversApiClientContent).toContain('async getDriverProfile');
|
|
expect(driversApiClientContent).toContain("return this.get<GetDriverProfileOutputDTO>(`/drivers/${driverId}/profile`)");
|
|
|
|
// Verify updateProfile method exists and uses correct endpoint
|
|
expect(driversApiClientContent).toContain('async updateProfile');
|
|
expect(driversApiClientContent).toContain("return this.put<GetDriverOutputDTO>('/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');
|
|
});
|
|
});
|
|
});
|