Files
gridpilot.gg/tests/integration/league/schedule-data-flow.integration.test.ts
2026-01-21 22:36:01 +01:00

386 lines
14 KiB
TypeScript

/**
* Integration Test: League Schedule Data Flow
*
* Tests the complete data flow from database to API response for league schedule:
* 1. Database query returns correct data
* 2. Use case processes the data correctly
* 3. Presenter transforms data to DTOs
* 4. API returns correct response
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { IntegrationTestHarness, createTestHarness } from '../harness';
describe('League Schedule - Data Flow Integration', () => {
let harness: IntegrationTestHarness;
beforeAll(async () => {
harness = createTestHarness();
await harness.beforeAll();
}, 120000);
afterAll(async () => {
await harness.afterAll();
}, 30000);
beforeEach(async () => {
await harness.beforeEach();
});
describe('API to View Data Flow', () => {
it('should return correct schedule DTO structure from API', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Schedule Test League' });
const season = await factory.createSeason(league.id.toString());
// Create races with different statuses
const race1 = await factory.createRace({
leagueId: league.id.toString(),
track: 'Laguna Seca',
car: 'Formula Ford',
scheduledAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Future race
status: 'scheduled'
});
const race2 = await factory.createRace({
leagueId: league.id.toString(),
track: 'Road Atlanta',
car: 'Formula Ford',
scheduledAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), // Future race
status: 'scheduled'
});
const race3 = await factory.createRace({
leagueId: league.id.toString(),
track: 'Nürburgring',
car: 'GT3',
scheduledAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Past race
status: 'completed'
});
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
// Verify: API response structure
expect(response).toBeDefined();
expect(response.races).toBeDefined();
expect(Array.isArray(response.races)).toBe(true);
// Verify: Each race has correct DTO structure
for (const race of response.races) {
expect(race).toHaveProperty('id');
expect(race).toHaveProperty('track');
expect(race).toHaveProperty('car');
expect(race).toHaveProperty('scheduledAt');
expect(race).toHaveProperty('status');
expect(race).toHaveProperty('results');
expect(Array.isArray(race.results)).toBe(true);
}
// Verify: Race data matches what we created
const scheduledRaces = response.races.filter(r => r.status === 'scheduled');
const completedRaces = response.races.filter(r => r.status === 'completed');
expect(scheduledRaces).toHaveLength(2);
expect(completedRaces).toHaveLength(1);
});
it('should return empty schedule for league with no races', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Empty Schedule League' });
const season = await factory.createSeason(league.id.toString());
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
expect(response.races).toEqual([]);
});
it('should handle schedule with single race', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Single Race League' });
const season = await factory.createSeason(league.id.toString());
const race = await factory.createRace({
leagueId: league.id.toString(),
track: 'Monza',
car: 'GT3',
scheduledAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
status: 'scheduled'
});
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
expect(response.races).toHaveLength(1);
expect(response.races[0].track).toBe('Monza');
expect(response.races[0].car).toBe('GT3');
expect(response.races[0].status).toBe('scheduled');
});
});
describe('End-to-End Data Flow', () => {
it('should correctly transform race data to schedule DTO', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Transformation Test League' });
const season = await factory.createSeason(league.id.toString());
const driver = await factory.createDriver({ name: 'Test Driver', country: 'US' });
// Create a completed race with results
const race = await factory.createRace({
leagueId: league.id.toString(),
track: 'Suzuka',
car: 'Formula 1',
scheduledAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
status: 'completed'
});
await factory.createResult(race.id.toString(), driver.id.toString(), {
position: 1,
fastestLap: 92000,
incidents: 0,
startPosition: 2
});
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
expect(response.races).toHaveLength(1);
const raceData = response.races[0];
expect(raceData.track).toBe('Suzuka');
expect(raceData.car).toBe('Formula 1');
expect(raceData.status).toBe('completed');
expect(raceData.results).toHaveLength(1);
expect(raceData.results[0].position).toBe(1);
expect(raceData.results[0].driverId).toBe(driver.id.toString());
});
it('should handle schedule with multiple races and results', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Multi Race League' });
const season = await factory.createSeason(league.id.toString());
const drivers = await Promise.all([
factory.createDriver({ name: 'Driver 1', country: 'US' }),
factory.createDriver({ name: 'Driver 2', country: 'UK' }),
]);
// Create 3 races
const races = await Promise.all([
factory.createRace({
leagueId: league.id.toString(),
track: 'Track 1',
car: 'Car 1',
scheduledAt: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000),
status: 'completed'
}),
factory.createRace({
leagueId: league.id.toString(),
track: 'Track 2',
car: 'Car 1',
scheduledAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000),
status: 'completed'
}),
factory.createRace({
leagueId: league.id.toString(),
track: 'Track 3',
car: 'Car 1',
scheduledAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
status: 'scheduled'
}),
]);
// Add results to first two races
await factory.createResult(races[0].id.toString(), drivers[0].id.toString(), { position: 1, fastestLap: 90000, incidents: 0, startPosition: 1 });
await factory.createResult(races[0].id.toString(), drivers[1].id.toString(), { position: 2, fastestLap: 90500, incidents: 1, startPosition: 2 });
await factory.createResult(races[1].id.toString(), drivers[0].id.toString(), { position: 2, fastestLap: 89500, incidents: 1, startPosition: 2 });
await factory.createResult(races[1].id.toString(), drivers[1].id.toString(), { position: 1, fastestLap: 89000, incidents: 0, startPosition: 1 });
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
expect(response.races).toHaveLength(3);
// Verify completed races have results
const completedRaces = response.races.filter(r => r.status === 'completed');
expect(completedRaces).toHaveLength(2);
for (const race of completedRaces) {
expect(race.results).toHaveLength(2);
expect(race.results[0].position).toBeDefined();
expect(race.results[0].driverId).toBeDefined();
}
// Verify scheduled race has no results
const scheduledRace = response.races.find(r => r.status === 'scheduled');
expect(scheduledRace).toBeDefined();
expect(scheduledRace?.results).toEqual([]);
});
it('should handle schedule with published/unpublished races', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Publish Test League' });
const season = await factory.createSeason(league.id.toString());
// Create races with different publish states
const race1 = await factory.createRace({
leagueId: league.id.toString(),
track: 'Track A',
car: 'Car A',
scheduledAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
status: 'scheduled'
});
const race2 = await factory.createRace({
leagueId: league.id.toString(),
track: 'Track B',
car: 'Car B',
scheduledAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
status: 'scheduled'
});
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
expect(response.races).toHaveLength(2);
// Both races should be in the schedule
const trackNames = response.races.map(r => r.track);
expect(trackNames).toContain('Track A');
expect(trackNames).toContain('Track B');
});
});
describe('Data Consistency', () => {
it('should maintain data consistency across multiple API calls', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Consistency Schedule League' });
const season = await factory.createSeason(league.id.toString());
const race = await factory.createRace({
leagueId: league.id.toString(),
track: 'Consistency Track',
car: 'Consistency Car',
scheduledAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
status: 'scheduled'
});
const api = harness.getApi();
// Make multiple calls
const response1 = await api.get(`/leagues/${league.id}/schedule`);
const response2 = await api.get(`/leagues/${league.id}/schedule`);
const response3 = await api.get(`/leagues/${league.id}/schedule`);
// All responses should be identical
expect(response1).toEqual(response2);
expect(response2).toEqual(response3);
// Verify data integrity
expect(response1.races).toHaveLength(1);
expect(response1.races[0].track).toBe('Consistency Track');
expect(response1.races[0].car).toBe('Consistency Car');
});
it('should handle edge case: league with many races', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Large Schedule League' });
const season = await factory.createSeason(league.id.toString());
// Create 20 races
const races = await Promise.all(
Array.from({ length: 20 }, (_, i) =>
factory.createRace({
leagueId: league.id.toString(),
track: `Track ${i + 1}`,
car: 'GT3',
scheduledAt: new Date(Date.now() + (i + 1) * 24 * 60 * 60 * 1000),
status: 'scheduled'
})
)
);
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
// Should have all 20 races
expect(response.races).toHaveLength(20);
// All races should have correct structure
for (const race of response.races) {
expect(race).toHaveProperty('id');
expect(race).toHaveProperty('track');
expect(race).toHaveProperty('car');
expect(race).toHaveProperty('scheduledAt');
expect(race).toHaveProperty('status');
expect(race).toHaveProperty('results');
expect(Array.isArray(race.results)).toBe(true);
}
// All races should be scheduled
const allScheduled = response.races.every(r => r.status === 'scheduled');
expect(allScheduled).toBe(true);
});
it('should handle edge case: league with races spanning multiple seasons', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'Multi Season League' });
// Create two seasons
const season1 = await factory.createSeason(league.id.toString(), { name: 'Season 1', year: 2024 });
const season2 = await factory.createSeason(league.id.toString(), { name: 'Season 2', year: 2025 });
// Create races in both seasons
const race1 = await factory.createRace({
leagueId: league.id.toString(),
track: 'Season 1 Track',
car: 'Car 1',
scheduledAt: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000), // Last year
status: 'completed'
});
const race2 = await factory.createRace({
leagueId: league.id.toString(),
track: 'Season 2 Track',
car: 'Car 2',
scheduledAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // This year
status: 'scheduled'
});
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
// Should have both races (schedule endpoint returns all races for league)
expect(response.races).toHaveLength(2);
const trackNames = response.races.map(r => r.track);
expect(trackNames).toContain('Season 1 Track');
expect(trackNames).toContain('Season 2 Track');
});
it('should handle edge case: race with no results', async () => {
const factory = harness.getFactory();
const league = await factory.createLeague({ name: 'No Results League' });
const season = await factory.createSeason(league.id.toString());
// Create a completed race with no results
const race = await factory.createRace({
leagueId: league.id.toString(),
track: 'Empty Results Track',
car: 'Empty Car',
scheduledAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
status: 'completed'
});
const api = harness.getApi();
const response = await api.get(`/leagues/${league.id}/schedule`);
expect(response.races).toHaveLength(1);
expect(response.races[0].results).toEqual([]);
expect(response.races[0].status).toBe('completed');
});
});
});