/** * 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'); }); }); });