1074 lines
46 KiB
TypeScript
1074 lines
46 KiB
TypeScript
/**
|
|
* Contract Validation Tests for Dashboard Module
|
|
*
|
|
* These tests validate that the dashboard API DTOs and OpenAPI spec are consistent
|
|
* and that the generated types will be compatible with the website dashboard client.
|
|
*/
|
|
|
|
import * as fs from 'fs/promises';
|
|
import * as path from 'path';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
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, any>;
|
|
components: {
|
|
schemas: Record<string, OpenAPISchema>;
|
|
};
|
|
}
|
|
|
|
describe('Dashboard Module Contract Validation', () => {
|
|
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 Dashboard Endpoints', () => {
|
|
it('should have dashboard endpoints defined in OpenAPI spec', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
// Check for dashboard endpoints
|
|
expect(spec.paths['/dashboard/overview']).toBeDefined();
|
|
|
|
// Verify GET methods exist
|
|
expect(spec.paths['/dashboard/overview'].get).toBeDefined();
|
|
});
|
|
|
|
it('should have DashboardOverviewDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardOverviewDTO'];
|
|
expect(schema).toBeDefined();
|
|
expect(schema.type).toBe('object');
|
|
|
|
// Verify required fields
|
|
expect(schema.required).toContain('myUpcomingRaces');
|
|
expect(schema.required).toContain('otherUpcomingRaces');
|
|
expect(schema.required).toContain('upcomingRaces');
|
|
expect(schema.required).toContain('activeLeaguesCount');
|
|
expect(schema.required).toContain('recentResults');
|
|
expect(schema.required).toContain('leagueStandingsSummaries');
|
|
expect(schema.required).toContain('feedSummary');
|
|
expect(schema.required).toContain('friends');
|
|
|
|
// Verify optional fields
|
|
expect(schema.properties?.currentDriver).toBeDefined();
|
|
expect(schema.properties?.nextRace).toBeDefined();
|
|
});
|
|
|
|
it('should have DashboardDriverSummaryDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardDriverSummaryDTO'];
|
|
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('country');
|
|
expect(schema.required).toContain('totalRaces');
|
|
expect(schema.required).toContain('wins');
|
|
expect(schema.required).toContain('podiums');
|
|
|
|
// Verify optional fields
|
|
expect(schema.properties?.avatarUrl).toBeDefined();
|
|
expect(schema.properties?.category).toBeDefined();
|
|
expect(schema.properties?.rating).toBeDefined();
|
|
expect(schema.properties?.globalRank).toBeDefined();
|
|
expect(schema.properties?.consistency).toBeDefined();
|
|
});
|
|
|
|
it('should have DashboardRaceSummaryDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardRaceSummaryDTO'];
|
|
expect(schema).toBeDefined();
|
|
expect(schema.type).toBe('object');
|
|
|
|
// Verify required fields
|
|
expect(schema.required).toContain('id');
|
|
expect(schema.required).toContain('track');
|
|
expect(schema.required).toContain('car');
|
|
expect(schema.required).toContain('scheduledAt');
|
|
expect(schema.required).toContain('status');
|
|
expect(schema.required).toContain('isMyLeague');
|
|
|
|
// Verify optional fields
|
|
expect(schema.properties?.leagueId).toBeDefined();
|
|
expect(schema.properties?.leagueName).toBeDefined();
|
|
});
|
|
|
|
it('should have DashboardRecentResultDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardRecentResultDTO'];
|
|
expect(schema).toBeDefined();
|
|
expect(schema.type).toBe('object');
|
|
|
|
// Verify required fields
|
|
expect(schema.required).toContain('raceId');
|
|
expect(schema.required).toContain('raceName');
|
|
expect(schema.required).toContain('finishedAt');
|
|
expect(schema.required).toContain('position');
|
|
expect(schema.required).toContain('incidents');
|
|
|
|
// Verify optional fields
|
|
expect(schema.properties?.leagueId).toBeDefined();
|
|
expect(schema.properties?.leagueName).toBeDefined();
|
|
});
|
|
|
|
it('should have DashboardLeagueStandingSummaryDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardLeagueStandingSummaryDTO'];
|
|
expect(schema).toBeDefined();
|
|
expect(schema.type).toBe('object');
|
|
|
|
// Verify required fields
|
|
expect(schema.required).toContain('leagueId');
|
|
expect(schema.required).toContain('leagueName');
|
|
expect(schema.required).toContain('totalDrivers');
|
|
|
|
// Verify optional fields
|
|
expect(schema.properties?.position).toBeDefined();
|
|
expect(schema.properties?.points).toBeDefined();
|
|
});
|
|
|
|
it('should have DashboardFeedItemSummaryDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardFeedItemSummaryDTO'];
|
|
expect(schema).toBeDefined();
|
|
expect(schema.type).toBe('object');
|
|
|
|
// Verify required fields
|
|
expect(schema.required).toContain('id');
|
|
expect(schema.required).toContain('type');
|
|
expect(schema.required).toContain('headline');
|
|
expect(schema.required).toContain('timestamp');
|
|
|
|
// Verify optional fields
|
|
expect(schema.properties?.body).toBeDefined();
|
|
expect(schema.properties?.ctaLabel).toBeDefined();
|
|
expect(schema.properties?.ctaHref).toBeDefined();
|
|
});
|
|
|
|
it('should have DashboardFeedSummaryDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardFeedSummaryDTO'];
|
|
expect(schema).toBeDefined();
|
|
expect(schema.type).toBe('object');
|
|
|
|
// Verify required fields
|
|
expect(schema.required).toContain('notificationCount');
|
|
expect(schema.required).toContain('items');
|
|
});
|
|
|
|
it('should have DashboardFriendSummaryDTO schema defined', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardFriendSummaryDTO'];
|
|
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('country');
|
|
|
|
// Verify optional fields
|
|
expect(schema.properties?.avatarUrl).toBeDefined();
|
|
});
|
|
|
|
it('should have proper request/response structure for dashboard overview endpoint', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const dashboardPath = spec.paths['/dashboard/overview']?.get;
|
|
expect(dashboardPath).toBeDefined();
|
|
|
|
// Verify query parameters
|
|
expect(dashboardPath.parameters).toBeDefined();
|
|
const driverIdParam = dashboardPath.parameters.find((p: any) => p.name === 'driverId');
|
|
expect(driverIdParam).toBeDefined();
|
|
expect(driverIdParam.in).toBe('query');
|
|
|
|
// Verify response
|
|
const response200 = dashboardPath.responses['200'];
|
|
expect(response200).toBeDefined();
|
|
expect(response200.content['application/json'].schema.$ref).toBe('#/components/schemas/DashboardOverviewDTO');
|
|
});
|
|
});
|
|
|
|
describe('DTO Consistency', () => {
|
|
it('should have generated DTO files for dashboard 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 dashboard-related DTOs
|
|
const dashboardDTOs = [
|
|
'DashboardOverviewDTO',
|
|
'DashboardDriverSummaryDTO',
|
|
'DashboardRaceSummaryDTO',
|
|
'DashboardRecentResultDTO',
|
|
'DashboardLeagueStandingSummaryDTO',
|
|
'DashboardFeedItemSummaryDTO',
|
|
'DashboardFeedSummaryDTO',
|
|
'DashboardFriendSummaryDTO',
|
|
];
|
|
|
|
for (const dtoName of dashboardDTOs) {
|
|
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 DashboardOverviewDTO
|
|
const dashboardOverviewSchema = schemas['DashboardOverviewDTO'];
|
|
const dashboardOverviewDtoPath = path.join(generatedTypesDir, 'DashboardOverviewDTO.ts');
|
|
const dashboardOverviewDtoExists = await fs.access(dashboardOverviewDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardOverviewDtoExists) {
|
|
const dashboardOverviewDtoContent = await fs.readFile(dashboardOverviewDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardOverviewSchema.required) {
|
|
for (const requiredProp of dashboardOverviewSchema.required) {
|
|
expect(dashboardOverviewDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardOverviewSchema.properties) {
|
|
for (const propName of Object.keys(dashboardOverviewSchema.properties)) {
|
|
expect(dashboardOverviewDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test DashboardDriverSummaryDTO
|
|
const dashboardDriverSummarySchema = schemas['DashboardDriverSummaryDTO'];
|
|
const dashboardDriverSummaryDtoPath = path.join(generatedTypesDir, 'DashboardDriverSummaryDTO.ts');
|
|
const dashboardDriverSummaryDtoExists = await fs.access(dashboardDriverSummaryDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardDriverSummaryDtoExists) {
|
|
const dashboardDriverSummaryDtoContent = await fs.readFile(dashboardDriverSummaryDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardDriverSummarySchema.required) {
|
|
for (const requiredProp of dashboardDriverSummarySchema.required) {
|
|
expect(dashboardDriverSummaryDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardDriverSummarySchema.properties) {
|
|
for (const propName of Object.keys(dashboardDriverSummarySchema.properties)) {
|
|
expect(dashboardDriverSummaryDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test DashboardRaceSummaryDTO
|
|
const dashboardRaceSummarySchema = schemas['DashboardRaceSummaryDTO'];
|
|
const dashboardRaceSummaryDtoPath = path.join(generatedTypesDir, 'DashboardRaceSummaryDTO.ts');
|
|
const dashboardRaceSummaryDtoExists = await fs.access(dashboardRaceSummaryDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardRaceSummaryDtoExists) {
|
|
const dashboardRaceSummaryDtoContent = await fs.readFile(dashboardRaceSummaryDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardRaceSummarySchema.required) {
|
|
for (const requiredProp of dashboardRaceSummarySchema.required) {
|
|
expect(dashboardRaceSummaryDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardRaceSummarySchema.properties) {
|
|
for (const propName of Object.keys(dashboardRaceSummarySchema.properties)) {
|
|
expect(dashboardRaceSummaryDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test DashboardRecentResultDTO
|
|
const dashboardRecentResultSchema = schemas['DashboardRecentResultDTO'];
|
|
const dashboardRecentResultDtoPath = path.join(generatedTypesDir, 'DashboardRecentResultDTO.ts');
|
|
const dashboardRecentResultDtoExists = await fs.access(dashboardRecentResultDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardRecentResultDtoExists) {
|
|
const dashboardRecentResultDtoContent = await fs.readFile(dashboardRecentResultDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardRecentResultSchema.required) {
|
|
for (const requiredProp of dashboardRecentResultSchema.required) {
|
|
expect(dashboardRecentResultDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardRecentResultSchema.properties) {
|
|
for (const propName of Object.keys(dashboardRecentResultSchema.properties)) {
|
|
expect(dashboardRecentResultDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test DashboardLeagueStandingSummaryDTO
|
|
const dashboardLeagueStandingSchema = schemas['DashboardLeagueStandingSummaryDTO'];
|
|
const dashboardLeagueStandingDtoPath = path.join(generatedTypesDir, 'DashboardLeagueStandingSummaryDTO.ts');
|
|
const dashboardLeagueStandingDtoExists = await fs.access(dashboardLeagueStandingDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardLeagueStandingDtoExists) {
|
|
const dashboardLeagueStandingDtoContent = await fs.readFile(dashboardLeagueStandingDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardLeagueStandingSchema.required) {
|
|
for (const requiredProp of dashboardLeagueStandingSchema.required) {
|
|
expect(dashboardLeagueStandingDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardLeagueStandingSchema.properties) {
|
|
for (const propName of Object.keys(dashboardLeagueStandingSchema.properties)) {
|
|
expect(dashboardLeagueStandingDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test DashboardFeedItemSummaryDTO
|
|
const dashboardFeedItemSchema = schemas['DashboardFeedItemSummaryDTO'];
|
|
const dashboardFeedItemDtoPath = path.join(generatedTypesDir, 'DashboardFeedItemSummaryDTO.ts');
|
|
const dashboardFeedItemDtoExists = await fs.access(dashboardFeedItemDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardFeedItemDtoExists) {
|
|
const dashboardFeedItemDtoContent = await fs.readFile(dashboardFeedItemDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardFeedItemSchema.required) {
|
|
for (const requiredProp of dashboardFeedItemSchema.required) {
|
|
expect(dashboardFeedItemDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardFeedItemSchema.properties) {
|
|
for (const propName of Object.keys(dashboardFeedItemSchema.properties)) {
|
|
expect(dashboardFeedItemDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test DashboardFeedSummaryDTO
|
|
const dashboardFeedSummarySchema = schemas['DashboardFeedSummaryDTO'];
|
|
const dashboardFeedSummaryDtoPath = path.join(generatedTypesDir, 'DashboardFeedSummaryDTO.ts');
|
|
const dashboardFeedSummaryDtoExists = await fs.access(dashboardFeedSummaryDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardFeedSummaryDtoExists) {
|
|
const dashboardFeedSummaryDtoContent = await fs.readFile(dashboardFeedSummaryDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardFeedSummarySchema.required) {
|
|
for (const requiredProp of dashboardFeedSummarySchema.required) {
|
|
expect(dashboardFeedSummaryDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardFeedSummarySchema.properties) {
|
|
for (const propName of Object.keys(dashboardFeedSummarySchema.properties)) {
|
|
expect(dashboardFeedSummaryDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test DashboardFriendSummaryDTO
|
|
const dashboardFriendSummarySchema = schemas['DashboardFriendSummaryDTO'];
|
|
const dashboardFriendSummaryDtoPath = path.join(generatedTypesDir, 'DashboardFriendSummaryDTO.ts');
|
|
const dashboardFriendSummaryDtoExists = await fs.access(dashboardFriendSummaryDtoPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardFriendSummaryDtoExists) {
|
|
const dashboardFriendSummaryDtoContent = await fs.readFile(dashboardFriendSummaryDtoPath, 'utf-8');
|
|
|
|
// Check that all required properties are present
|
|
if (dashboardFriendSummarySchema.required) {
|
|
for (const requiredProp of dashboardFriendSummarySchema.required) {
|
|
expect(dashboardFriendSummaryDtoContent).toContain(requiredProp);
|
|
}
|
|
}
|
|
|
|
// Check that all properties are present
|
|
if (dashboardFriendSummarySchema.properties) {
|
|
for (const propName of Object.keys(dashboardFriendSummarySchema.properties)) {
|
|
expect(dashboardFriendSummaryDtoContent).toContain(propName);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should have dashboard types defined in tbd folder', async () => {
|
|
// Check if dashboard types exist in tbd folder (similar to admin types)
|
|
const tbdDir = path.join(websiteTypesDir, 'tbd');
|
|
const tbdFiles = await fs.readdir(tbdDir).catch(() => []);
|
|
|
|
// Dashboard types might be in a separate file or combined with existing types
|
|
// For now, we'll check if the generated types are properly available
|
|
const generatedFiles = await fs.readdir(generatedTypesDir);
|
|
const dashboardGenerated = generatedFiles.filter(f =>
|
|
f.includes('Dashboard') ||
|
|
f.includes('Driver') ||
|
|
f.includes('Race') ||
|
|
f.includes('Feed') ||
|
|
f.includes('Friend') ||
|
|
f.includes('League')
|
|
);
|
|
|
|
expect(dashboardGenerated.length).toBeGreaterThanOrEqual(8);
|
|
});
|
|
|
|
it('should have dashboard types re-exported from main types file', async () => {
|
|
// Check if there's a dashboard.ts file or if types are exported elsewhere
|
|
const dashboardTypesPath = path.join(websiteTypesDir, 'dashboard.ts');
|
|
const dashboardTypesExists = await fs.access(dashboardTypesPath).then(() => true).catch(() => false);
|
|
|
|
if (dashboardTypesExists) {
|
|
const dashboardTypesContent = await fs.readFile(dashboardTypesPath, 'utf-8');
|
|
|
|
// Verify re-exports
|
|
expect(dashboardTypesContent).toContain('DashboardOverviewDTO');
|
|
expect(dashboardTypesContent).toContain('DashboardDriverSummaryDTO');
|
|
expect(dashboardTypesContent).toContain('DashboardRaceSummaryDTO');
|
|
expect(dashboardTypesContent).toContain('DashboardRecentResultDTO');
|
|
expect(dashboardTypesContent).toContain('DashboardLeagueStandingSummaryDTO');
|
|
expect(dashboardTypesContent).toContain('DashboardFeedItemSummaryDTO');
|
|
expect(dashboardTypesContent).toContain('DashboardFeedSummaryDTO');
|
|
expect(dashboardTypesContent).toContain('DashboardFriendSummaryDTO');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Dashboard API Client Contract', () => {
|
|
it('should have DashboardApiClient defined', async () => {
|
|
const dashboardApiClientPath = path.join(apiRoot, 'apps/website/lib/api/dashboard/DashboardApiClient.ts');
|
|
const dashboardApiClientExists = await fs.access(dashboardApiClientPath).then(() => true).catch(() => false);
|
|
|
|
expect(dashboardApiClientExists).toBe(true);
|
|
|
|
const dashboardApiClientContent = await fs.readFile(dashboardApiClientPath, 'utf-8');
|
|
|
|
// Verify class definition
|
|
expect(dashboardApiClientContent).toContain('export class DashboardApiClient');
|
|
expect(dashboardApiClientContent).toContain('extends BaseApiClient');
|
|
|
|
// Verify methods exist
|
|
expect(dashboardApiClientContent).toContain('getDashboardOverview');
|
|
|
|
// Verify method signatures
|
|
expect(dashboardApiClientContent).toContain('getDashboardOverview()');
|
|
});
|
|
|
|
it('should have proper request construction in getDashboardOverview method', async () => {
|
|
const dashboardApiClientPath = path.join(apiRoot, 'apps/website/lib/api/dashboard/DashboardApiClient.ts');
|
|
const dashboardApiClientContent = await fs.readFile(dashboardApiClientPath, 'utf-8');
|
|
|
|
// Verify GET request
|
|
expect(dashboardApiClientContent).toContain("return this.get<DashboardOverviewDTO>('/dashboard/overview')");
|
|
});
|
|
});
|
|
|
|
describe('Request Correctness Tests', () => {
|
|
it('should validate DashboardOverviewDTO required fields', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardOverviewDTO'];
|
|
|
|
// Verify all required fields are present
|
|
expect(schema.required).toContain('myUpcomingRaces');
|
|
expect(schema.required).toContain('otherUpcomingRaces');
|
|
expect(schema.required).toContain('upcomingRaces');
|
|
expect(schema.required).toContain('activeLeaguesCount');
|
|
expect(schema.required).toContain('recentResults');
|
|
expect(schema.required).toContain('leagueStandingsSummaries');
|
|
expect(schema.required).toContain('feedSummary');
|
|
expect(schema.required).toContain('friends');
|
|
|
|
// Verify no extra required fields
|
|
expect(schema.required.length).toBe(8);
|
|
});
|
|
|
|
it('should validate DashboardOverviewDTO optional fields', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardOverviewDTO'];
|
|
|
|
// Verify optional fields are not required
|
|
expect(schema.required).not.toContain('currentDriver');
|
|
expect(schema.required).not.toContain('nextRace');
|
|
|
|
// Verify optional fields exist
|
|
expect(schema.properties?.currentDriver).toBeDefined();
|
|
expect(schema.properties?.nextRace).toBeDefined();
|
|
});
|
|
|
|
it('should validate DashboardDriverSummaryDTO required fields', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardDriverSummaryDTO'];
|
|
|
|
// Verify all required fields are present
|
|
expect(schema.required).toContain('id');
|
|
expect(schema.required).toContain('name');
|
|
expect(schema.required).toContain('country');
|
|
expect(schema.required).toContain('totalRaces');
|
|
expect(schema.required).toContain('wins');
|
|
expect(schema.required).toContain('podiums');
|
|
|
|
// Verify no extra required fields
|
|
expect(schema.required.length).toBe(6);
|
|
});
|
|
|
|
it('should validate DashboardDriverSummaryDTO optional fields', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardDriverSummaryDTO'];
|
|
|
|
// Verify optional fields are not required
|
|
expect(schema.required).not.toContain('avatarUrl');
|
|
expect(schema.required).not.toContain('category');
|
|
expect(schema.required).not.toContain('rating');
|
|
expect(schema.required).not.toContain('globalRank');
|
|
expect(schema.required).not.toContain('consistency');
|
|
|
|
// Verify optional fields exist
|
|
expect(schema.properties?.avatarUrl).toBeDefined();
|
|
expect(schema.properties?.category).toBeDefined();
|
|
expect(schema.properties?.rating).toBeDefined();
|
|
expect(schema.properties?.globalRank).toBeDefined();
|
|
expect(schema.properties?.consistency).toBeDefined();
|
|
});
|
|
|
|
it('should validate DashboardRaceSummaryDTO structure', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardRaceSummaryDTO'];
|
|
|
|
// Verify all required fields
|
|
expect(schema.required).toContain('id');
|
|
expect(schema.required).toContain('track');
|
|
expect(schema.required).toContain('car');
|
|
expect(schema.required).toContain('scheduledAt');
|
|
expect(schema.required).toContain('status');
|
|
expect(schema.required).toContain('isMyLeague');
|
|
|
|
// Verify field types
|
|
expect(schema.properties?.id?.type).toBe('string');
|
|
expect(schema.properties?.track?.type).toBe('string');
|
|
expect(schema.properties?.car?.type).toBe('string');
|
|
expect(schema.properties?.scheduledAt?.type).toBe('string');
|
|
expect(schema.properties?.status?.type).toBe('string');
|
|
expect(schema.properties?.isMyLeague?.type).toBe('boolean');
|
|
});
|
|
|
|
it('should validate DashboardRecentResultDTO structure', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardRecentResultDTO'];
|
|
|
|
// Verify all required fields
|
|
expect(schema.required).toContain('raceId');
|
|
expect(schema.required).toContain('raceName');
|
|
expect(schema.required).toContain('finishedAt');
|
|
expect(schema.required).toContain('position');
|
|
expect(schema.required).toContain('incidents');
|
|
|
|
// Verify field types
|
|
expect(schema.properties?.raceId?.type).toBe('string');
|
|
expect(schema.properties?.raceName?.type).toBe('string');
|
|
expect(schema.properties?.finishedAt?.type).toBe('string');
|
|
expect(schema.properties?.position?.type).toBe('number');
|
|
expect(schema.properties?.incidents?.type).toBe('number');
|
|
});
|
|
|
|
it('should validate DashboardLeagueStandingSummaryDTO structure', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardLeagueStandingSummaryDTO'];
|
|
|
|
// Verify all required fields
|
|
expect(schema.required).toContain('leagueId');
|
|
expect(schema.required).toContain('leagueName');
|
|
expect(schema.required).toContain('totalDrivers');
|
|
|
|
// Verify field types
|
|
expect(schema.properties?.leagueId?.type).toBe('string');
|
|
expect(schema.properties?.leagueName?.type).toBe('string');
|
|
expect(schema.properties?.totalDrivers?.type).toBe('number');
|
|
});
|
|
|
|
it('should validate DashboardFeedItemSummaryDTO structure', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardFeedItemSummaryDTO'];
|
|
|
|
// Verify all required fields
|
|
expect(schema.required).toContain('id');
|
|
expect(schema.required).toContain('type');
|
|
expect(schema.required).toContain('headline');
|
|
expect(schema.required).toContain('timestamp');
|
|
|
|
// Verify field types
|
|
expect(schema.properties?.id?.type).toBe('string');
|
|
expect(schema.properties?.type?.type).toBe('string');
|
|
expect(schema.properties?.headline?.type).toBe('string');
|
|
expect(schema.properties?.timestamp?.type).toBe('string');
|
|
});
|
|
|
|
it('should validate DashboardFeedSummaryDTO structure', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardFeedSummaryDTO'];
|
|
|
|
// Verify all required fields
|
|
expect(schema.required).toContain('notificationCount');
|
|
expect(schema.required).toContain('items');
|
|
|
|
// Verify field types
|
|
expect(schema.properties?.notificationCount?.type).toBe('number');
|
|
expect(schema.properties?.items?.type).toBe('array');
|
|
});
|
|
|
|
it('should validate DashboardFriendSummaryDTO structure', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const schema = spec.components.schemas['DashboardFriendSummaryDTO'];
|
|
|
|
// Verify all required fields
|
|
expect(schema.required).toContain('id');
|
|
expect(schema.required).toContain('name');
|
|
expect(schema.required).toContain('country');
|
|
|
|
// Verify field types
|
|
expect(schema.properties?.id?.type).toBe('string');
|
|
expect(schema.properties?.name?.type).toBe('string');
|
|
expect(schema.properties?.country?.type).toBe('string');
|
|
});
|
|
});
|
|
|
|
describe('Response Handling Tests', () => {
|
|
it('should handle successful dashboard overview response', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const dashboardOverviewSchema = spec.components.schemas['DashboardOverviewDTO'];
|
|
|
|
// Verify response structure
|
|
expect(dashboardOverviewSchema.properties?.currentDriver).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.myUpcomingRaces).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.otherUpcomingRaces).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.upcomingRaces).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.activeLeaguesCount).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.nextRace).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.recentResults).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.leagueStandingsSummaries).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.feedSummary).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.friends).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 dashboardOverviewSchema = spec.components.schemas['DashboardOverviewDTO'];
|
|
|
|
// Verify all required fields are present
|
|
for (const field of ['myUpcomingRaces', 'otherUpcomingRaces', 'upcomingRaces', 'activeLeaguesCount', 'recentResults', 'leagueStandingsSummaries', 'feedSummary', 'friends']) {
|
|
expect(dashboardOverviewSchema.required).toContain(field);
|
|
expect(dashboardOverviewSchema.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['DashboardDriverSummaryDTO'];
|
|
|
|
// Verify optional fields are nullable
|
|
expect(driverSchema.properties?.avatarUrl?.nullable).toBe(true);
|
|
expect(driverSchema.properties?.category?.nullable).toBe(true);
|
|
expect(driverSchema.properties?.rating?.nullable).toBe(true);
|
|
expect(driverSchema.properties?.globalRank?.nullable).toBe(true);
|
|
expect(driverSchema.properties?.consistency?.nullable).toBe(true);
|
|
|
|
// Verify optional fields are not in required array
|
|
expect(driverSchema.required).not.toContain('avatarUrl');
|
|
expect(driverSchema.required).not.toContain('category');
|
|
expect(driverSchema.required).not.toContain('rating');
|
|
expect(driverSchema.required).not.toContain('globalRank');
|
|
expect(driverSchema.required).not.toContain('consistency');
|
|
});
|
|
|
|
it('should handle optional fields in race response correctly', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const raceSchema = spec.components.schemas['DashboardRaceSummaryDTO'];
|
|
|
|
// Verify optional fields are nullable
|
|
expect(raceSchema.properties?.leagueId?.nullable).toBe(true);
|
|
expect(raceSchema.properties?.leagueName?.nullable).toBe(true);
|
|
|
|
// Verify optional fields are not in required array
|
|
expect(raceSchema.required).not.toContain('leagueId');
|
|
expect(raceSchema.required).not.toContain('leagueName');
|
|
});
|
|
|
|
it('should handle optional fields in recent result response correctly', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const recentResultSchema = spec.components.schemas['DashboardRecentResultDTO'];
|
|
|
|
// Verify optional fields are nullable
|
|
expect(recentResultSchema.properties?.leagueId?.nullable).toBe(true);
|
|
expect(recentResultSchema.properties?.leagueName?.nullable).toBe(true);
|
|
|
|
// Verify optional fields are not in required array
|
|
expect(recentResultSchema.required).not.toContain('leagueId');
|
|
expect(recentResultSchema.required).not.toContain('leagueName');
|
|
});
|
|
|
|
it('should handle optional fields in league standing response correctly', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const leagueStandingSchema = spec.components.schemas['DashboardLeagueStandingSummaryDTO'];
|
|
|
|
// Verify optional fields are nullable
|
|
expect(leagueStandingSchema.properties?.position?.nullable).toBe(true);
|
|
expect(leagueStandingSchema.properties?.points?.nullable).toBe(true);
|
|
|
|
// Verify optional fields are not in required array
|
|
expect(leagueStandingSchema.required).not.toContain('position');
|
|
expect(leagueStandingSchema.required).not.toContain('points');
|
|
});
|
|
|
|
it('should handle optional fields in feed item response correctly', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const feedItemSchema = spec.components.schemas['DashboardFeedItemSummaryDTO'];
|
|
|
|
// Verify optional fields are nullable
|
|
expect(feedItemSchema.properties?.body?.nullable).toBe(true);
|
|
expect(feedItemSchema.properties?.ctaLabel?.nullable).toBe(true);
|
|
expect(feedItemSchema.properties?.ctaHref?.nullable).toBe(true);
|
|
|
|
// Verify optional fields are not in required array
|
|
expect(feedItemSchema.required).not.toContain('body');
|
|
expect(feedItemSchema.required).not.toContain('ctaLabel');
|
|
expect(feedItemSchema.required).not.toContain('ctaHref');
|
|
});
|
|
|
|
it('should handle optional fields in friend response correctly', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const friendSchema = spec.components.schemas['DashboardFriendSummaryDTO'];
|
|
|
|
// Verify optional fields are nullable
|
|
expect(friendSchema.properties?.avatarUrl?.nullable).toBe(true);
|
|
|
|
// Verify optional fields are not in required array
|
|
expect(friendSchema.required).not.toContain('avatarUrl');
|
|
});
|
|
});
|
|
|
|
describe('Error Handling Tests', () => {
|
|
it('should document 401 Unauthorized response for dashboard overview endpoint', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const dashboardPath = spec.paths['/dashboard/overview']?.get;
|
|
|
|
// Check if 401 response is documented
|
|
if (dashboardPath.responses['401']) {
|
|
expect(dashboardPath.responses['401']).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it('should document 500 Internal Server Error response', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const dashboardPath = spec.paths['/dashboard/overview']?.get;
|
|
|
|
// Check if 500 response is documented for dashboard endpoint
|
|
if (dashboardPath.responses['500']) {
|
|
expect(dashboardPath.responses['500']).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it('should have proper error handling in DashboardApiClient', async () => {
|
|
const dashboardApiClientPath = path.join(apiRoot, 'apps/website/lib/api/dashboard/DashboardApiClient.ts');
|
|
const dashboardApiClientContent = await fs.readFile(dashboardApiClientPath, 'utf-8');
|
|
|
|
// Verify BaseApiClient is extended (which provides error handling)
|
|
expect(dashboardApiClientContent).toContain('extends BaseApiClient');
|
|
|
|
// Verify methods use BaseApiClient methods (which handle errors)
|
|
expect(dashboardApiClientContent).toContain('this.get<');
|
|
});
|
|
});
|
|
|
|
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 dashboard overview request/response consistency
|
|
const dashboardOverviewSchema = spec.components.schemas['DashboardOverviewDTO'];
|
|
|
|
// Output should contain all required fields
|
|
expect(dashboardOverviewSchema.properties?.myUpcomingRaces).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.otherUpcomingRaces).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.upcomingRaces).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.activeLeaguesCount).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.recentResults).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.leagueStandingsSummaries).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.feedSummary).toBeDefined();
|
|
expect(dashboardOverviewSchema.properties?.friends).toBeDefined();
|
|
});
|
|
|
|
it('should validate semantic consistency in dashboard overview', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const dashboardOverviewSchema = spec.components.schemas['DashboardOverviewDTO'];
|
|
|
|
// Verify overview has all required fields
|
|
expect(dashboardOverviewSchema.required).toContain('myUpcomingRaces');
|
|
expect(dashboardOverviewSchema.required).toContain('otherUpcomingRaces');
|
|
expect(dashboardOverviewSchema.required).toContain('upcomingRaces');
|
|
expect(dashboardOverviewSchema.required).toContain('activeLeaguesCount');
|
|
expect(dashboardOverviewSchema.required).toContain('recentResults');
|
|
expect(dashboardOverviewSchema.required).toContain('leagueStandingsSummaries');
|
|
expect(dashboardOverviewSchema.required).toContain('feedSummary');
|
|
expect(dashboardOverviewSchema.required).toContain('friends');
|
|
});
|
|
|
|
it('should validate idempotency for dashboard overview', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
// Check if dashboard overview endpoint exists
|
|
const dashboardPath = spec.paths['/dashboard/overview']?.get;
|
|
if (dashboardPath) {
|
|
// Verify it's a GET request (idempotent)
|
|
expect(dashboardPath).toBeDefined();
|
|
|
|
// Verify no request body (GET requests are idempotent)
|
|
expect(dashboardPath.requestBody).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it('should validate uniqueness constraints for driver and race IDs', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const driverSchema = spec.components.schemas['DashboardDriverSummaryDTO'];
|
|
const raceSchema = spec.components.schemas['DashboardRaceSummaryDTO'];
|
|
|
|
// Verify driver ID is a required field
|
|
expect(driverSchema.required).toContain('id');
|
|
expect(driverSchema.properties?.id?.type).toBe('string');
|
|
|
|
// Verify race ID is a required field
|
|
expect(raceSchema.required).toContain('id');
|
|
expect(raceSchema.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 = [
|
|
'DashboardOverviewDTO',
|
|
'DashboardDriverSummaryDTO',
|
|
'DashboardRaceSummaryDTO',
|
|
'DashboardRecentResultDTO',
|
|
'DashboardLeagueStandingSummaryDTO',
|
|
'DashboardFeedItemSummaryDTO',
|
|
'DashboardFeedSummaryDTO',
|
|
'DashboardFriendSummaryDTO',
|
|
];
|
|
|
|
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 dashboard data', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
const dashboardOverviewSchema = spec.components.schemas['DashboardOverviewDTO'];
|
|
|
|
// Verify upcoming races are arrays
|
|
expect(dashboardOverviewSchema.properties?.myUpcomingRaces?.type).toBe('array');
|
|
expect(dashboardOverviewSchema.properties?.otherUpcomingRaces?.type).toBe('array');
|
|
expect(dashboardOverviewSchema.properties?.upcomingRaces?.type).toBe('array');
|
|
|
|
// Verify recent results are arrays
|
|
expect(dashboardOverviewSchema.properties?.recentResults?.type).toBe('array');
|
|
|
|
// Verify league standings are arrays
|
|
expect(dashboardOverviewSchema.properties?.leagueStandingsSummaries?.type).toBe('array');
|
|
|
|
// Verify friends are arrays
|
|
expect(dashboardOverviewSchema.properties?.friends?.type).toBe('array');
|
|
|
|
// Verify activeLeaguesCount is a number
|
|
expect(dashboardOverviewSchema.properties?.activeLeaguesCount?.type).toBe('number');
|
|
});
|
|
|
|
it('should validate pagination is not applicable for dashboard overview', async () => {
|
|
const content = await fs.readFile(openapiPath, 'utf-8');
|
|
const spec: OpenAPISpec = JSON.parse(content);
|
|
|
|
// Dashboard overview should not have pagination
|
|
const dashboardPath = spec.paths['/dashboard/overview'];
|
|
if (dashboardPath) {
|
|
// Check if there are any query parameters for pagination
|
|
const methods = Object.keys(dashboardPath);
|
|
for (const method of methods) {
|
|
const operation = dashboardPath[method];
|
|
if (operation.parameters) {
|
|
const paramNames = operation.parameters.map((p: any) => p.name);
|
|
// Dashboard overview should not have page/limit parameters
|
|
expect(paramNames).not.toContain('page');
|
|
expect(paramNames).not.toContain('limit');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Dashboard 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 dashboard DTOs exist in generated types
|
|
const dashboardDTOs = [
|
|
'DashboardOverviewDTO',
|
|
'DashboardDriverSummaryDTO',
|
|
'DashboardRaceSummaryDTO',
|
|
'DashboardRecentResultDTO',
|
|
'DashboardLeagueStandingSummaryDTO',
|
|
'DashboardFeedItemSummaryDTO',
|
|
'DashboardFeedSummaryDTO',
|
|
'DashboardFriendSummaryDTO',
|
|
];
|
|
|
|
for (const dtoName of dashboardDTOs) {
|
|
expect(spec.components.schemas[dtoName]).toBeDefined();
|
|
expect(generatedDTOs).toContain(dtoName);
|
|
}
|
|
});
|
|
|
|
it('should have DashboardApiClient methods matching API endpoints', async () => {
|
|
const dashboardApiClientPath = path.join(apiRoot, 'apps/website/lib/api/dashboard/DashboardApiClient.ts');
|
|
const dashboardApiClientContent = await fs.readFile(dashboardApiClientPath, 'utf-8');
|
|
|
|
// Verify getDashboardOverview method exists and uses correct endpoint
|
|
expect(dashboardApiClientContent).toContain('async getDashboardOverview');
|
|
expect(dashboardApiClientContent).toContain("return this.get<DashboardOverviewDTO>('/dashboard/overview')");
|
|
});
|
|
|
|
it('should have proper error handling in DashboardApiClient', async () => {
|
|
const dashboardApiClientPath = path.join(apiRoot, 'apps/website/lib/api/dashboard/DashboardApiClient.ts');
|
|
const dashboardApiClientContent = await fs.readFile(dashboardApiClientPath, 'utf-8');
|
|
|
|
// Verify BaseApiClient is extended (which provides error handling)
|
|
expect(dashboardApiClientContent).toContain('extends BaseApiClient');
|
|
|
|
// Verify methods use BaseApiClient methods (which handle errors)
|
|
expect(dashboardApiClientContent).toContain('this.get<');
|
|
});
|
|
|
|
it('should have consistent type imports in DashboardApiClient', async () => {
|
|
const dashboardApiClientPath = path.join(apiRoot, 'apps/website/lib/api/dashboard/DashboardApiClient.ts');
|
|
const dashboardApiClientContent = await fs.readFile(dashboardApiClientPath, 'utf-8');
|
|
|
|
// Verify all required types are imported
|
|
expect(dashboardApiClientContent).toContain('DashboardOverviewDTO');
|
|
});
|
|
});
|
|
});
|