/** * Integration Tests for LeagueDetailPageQuery * * Tests the LeagueDetailPageQuery with mocked API clients to verify: * - Happy path: API returns valid league detail data * - Error handling: 404 when league not found * - Error handling: 500 when API server error * - Missing data: API returns partial data */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { LeagueDetailPageQuery } from '@/lib/page-queries/LeagueDetailPageQuery'; import { MockLeaguesApiClient } from './mocks/MockLeaguesApiClient'; import { ApiError } from '../../../apps/website/lib/api/base/ApiError'; // Mock data factories const createMockLeagueDetailData = () => ({ leagues: [ { id: 'league-1', name: 'Test League', description: 'A test league', capacity: 10, currentMembers: 5, ownerId: 'driver-1', status: 'active' as const, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], }); const createMockMembershipsData = () => ({ members: [ { driverId: 'driver-1', driver: { id: 'driver-1', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date().toISOString(), }, role: 'owner' as const, status: 'active' as const, joinedAt: new Date().toISOString(), }, ], }); const createMockRacesPageData = () => ({ races: [ { id: 'race-1', track: 'Test Track', car: 'Test Car', scheduledAt: new Date().toISOString(), leagueName: 'Test League', status: 'scheduled' as const, strengthOfField: 50, }, ], }); const createMockDriverData = () => ({ id: 'driver-1', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date().toISOString(), }); const createMockLeagueConfigData = () => ({ form: { scoring: { presetId: 'preset-1', }, }, }); describe('LeagueDetailPageQuery Integration', () => { let mockLeaguesApiClient: MockLeaguesApiClient; beforeEach(() => { mockLeaguesApiClient = new MockLeaguesApiClient(); }); afterEach(() => { mockLeaguesApiClient.clearMocks(); }); describe('Happy Path', () => { it('should return valid league detail data when API returns success', async () => { // Arrange const leagueId = 'league-1'; const mockLeaguesData = createMockLeagueDetailData(); const mockMembershipsData = createMockMembershipsData(); const mockRacesPageData = createMockRacesPageData(); const mockDriverData = createMockDriverData(); const mockLeagueConfigData = createMockLeagueConfigData(); // Mock fetch to return different data based on the URL global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve(createMockResponse(mockLeaguesData)); } if (url.includes('/memberships')) { return Promise.resolve(createMockResponse(mockMembershipsData)); } if (url.includes('/races/page-data')) { return Promise.resolve(createMockResponse(mockRacesPageData)); } if (url.includes('/drivers/driver-1')) { return Promise.resolve(createMockResponse(mockDriverData)); } if (url.includes('/config')) { return Promise.resolve(createMockResponse(mockLeagueConfigData)); } return Promise.resolve(createMockErrorResponse(404, 'Not Found', 'Not Found')); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data).toBeDefined(); expect(data.league).toBeDefined(); expect(data.league.id).toBe('league-1'); expect(data.league.name).toBe('Test League'); expect(data.league.capacity).toBe(10); expect(data.league.currentMembers).toBe(5); expect(data.owner).toBeDefined(); expect(data.owner?.id).toBe('driver-1'); expect(data.owner?.name).toBe('Test Driver'); expect(data.memberships).toBeDefined(); expect(data.memberships.members).toBeDefined(); expect(data.memberships.members.length).toBe(1); expect(data.races).toBeDefined(); expect(data.races.length).toBe(1); expect(data.races[0].id).toBe('race-1'); expect(data.races[0].name).toBe('Test Track - Test Car'); expect(data.scoringConfig).toBeDefined(); expect(data.scoringConfig?.scoringPresetId).toBe('preset-1'); }); it('should handle league without owner', async () => { // Arrange const leagueId = 'league-2'; const mockLeaguesData = { leagues: [ { id: 'league-2', name: 'League Without Owner', description: 'A league without an owner', capacity: 15, currentMembers: 8, // No ownerId status: 'active' as const, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], }; const mockMembershipsData = createMockMembershipsData(); const mockRacesPageData = createMockRacesPageData(); global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve(createMockResponse(mockLeaguesData)); } if (url.includes('/memberships')) { return Promise.resolve(createMockResponse(mockMembershipsData)); } if (url.includes('/races/page-data')) { return Promise.resolve(createMockResponse(mockRacesPageData)); } return Promise.resolve(createMockErrorResponse(404, 'Not Found', 'Not Found')); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.owner).toBeNull(); expect(data.league.id).toBe('league-2'); expect(data.league.name).toBe('League Without Owner'); }); it('should handle league with no races', async () => { // Arrange const leagueId = 'league-3'; const mockLeaguesData = createMockLeagueDetailData(); const mockMembershipsData = createMockMembershipsData(); const mockRacesPageData = { races: [] }; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve(createMockResponse(mockLeaguesData)); } if (url.includes('/memberships')) { return Promise.resolve(createMockResponse(mockMembershipsData)); } if (url.includes('/races/page-data')) { return Promise.resolve(createMockResponse(mockRacesPageData)); } return Promise.resolve(createMockErrorResponse(404, 'Not Found', 'Not Found')); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.races).toBeDefined(); expect(data.races.length).toBe(0); }); }); describe('Error Handling', () => { it('should handle 404 error when league not found', async () => { // Arrange const leagueId = 'non-existent-league'; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve(createMockResponse({ leagues: [] })); } return Promise.resolve(createMockErrorResponse(404, 'Not Found', 'League not found')); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('notFound'); }); it('should handle 500 error when API server error', async () => { // Arrange const leagueId = 'league-1'; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve(createMockErrorResponse(500, 'Internal Server Error', 'Internal Server Error')); } return Promise.resolve(createMockErrorResponse(500, 'Internal Server Error', 'Internal Server Error')); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('serverError'); }); it('should handle network error', async () => { // Arrange const leagueId = 'league-1'; global.fetch = vi.fn().mockRejectedValue(new Error('Network error: Unable to reach the API server')); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('serverError'); }); it('should handle timeout error', async () => { // Arrange const leagueId = 'league-1'; const timeoutError = new Error('Request timed out after 30 seconds'); timeoutError.name = 'AbortError'; global.fetch = vi.fn().mockRejectedValue(timeoutError); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('serverError'); }); it('should handle unauthorized error', async () => { // Arrange const leagueId = 'league-1'; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: false, status: 401, statusText: 'Unauthorized', text: async () => 'Unauthorized', }); } return Promise.resolve({ ok: false, status: 401, statusText: 'Unauthorized', text: async () => 'Unauthorized', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('unauthorized'); }); it('should handle forbidden error', async () => { // Arrange const leagueId = 'league-1'; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: false, status: 403, statusText: 'Forbidden', text: async () => 'Forbidden', }); } return Promise.resolve({ ok: false, status: 403, statusText: 'Forbidden', text: async () => 'Forbidden', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('unauthorized'); }); }); describe('Missing Data', () => { it('should handle API returning partial data (missing memberships)', async () => { // Arrange const leagueId = 'league-1'; const mockLeaguesData = createMockLeagueDetailData(); const mockRacesPageData = createMockRacesPageData(); global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: true, json: async () => mockLeaguesData, }); } if (url.includes('/memberships')) { return Promise.resolve({ ok: true, json: async () => ({ members: [] }), }); } if (url.includes('/races/page-data')) { return Promise.resolve({ ok: true, json: async () => mockRacesPageData, }); } return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Not Found', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.memberships).toBeDefined(); expect(data.memberships.members).toBeDefined(); expect(data.memberships.members.length).toBe(0); }); it('should handle API returning partial data (missing races)', async () => { // Arrange const leagueId = 'league-1'; const mockLeaguesData = createMockLeagueDetailData(); const mockMembershipsData = createMockMembershipsData(); global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: true, json: async () => mockLeaguesData, }); } if (url.includes('/memberships')) { return Promise.resolve({ ok: true, json: async () => mockMembershipsData, }); } if (url.includes('/races/page-data')) { return Promise.resolve({ ok: true, json: async () => ({ races: [] }), }); } return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Not Found', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.races).toBeDefined(); expect(data.races.length).toBe(0); }); it('should handle API returning partial data (missing scoring config)', async () => { // Arrange const leagueId = 'league-1'; const mockLeaguesData = createMockLeagueDetailData(); const mockMembershipsData = createMockMembershipsData(); const mockRacesPageData = createMockRacesPageData(); global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: true, json: async () => mockLeaguesData, }); } if (url.includes('/memberships')) { return Promise.resolve({ ok: true, json: async () => mockMembershipsData, }); } if (url.includes('/races/page-data')) { return Promise.resolve({ ok: true, json: async () => mockRacesPageData, }); } if (url.includes('/config')) { return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Config not found', }); } return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Not Found', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.scoringConfig).toBeNull(); }); it('should handle API returning partial data (missing owner)', async () => { // Arrange const leagueId = 'league-1'; const mockLeaguesData = { leagues: [ { id: 'league-1', name: 'Test League', description: 'A test league', capacity: 10, currentMembers: 5, ownerId: 'driver-1', status: 'active' as const, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], }; const mockMembershipsData = createMockMembershipsData(); const mockRacesPageData = createMockRacesPageData(); global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: true, json: async () => mockLeaguesData, }); } if (url.includes('/memberships')) { return Promise.resolve({ ok: true, json: async () => mockMembershipsData, }); } if (url.includes('/races/page-data')) { return Promise.resolve({ ok: true, json: async () => mockRacesPageData, }); } if (url.includes('/drivers/driver-1')) { return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Driver not found', }); } return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Not Found', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.owner).toBeNull(); }); }); describe('Edge Cases', () => { it('should handle API returning empty leagues array', async () => { // Arrange const leagueId = 'league-1'; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: true, json: async () => ({ leagues: [] }), }); } return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Not Found', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.type).toBe('notFound'); expect(error.message).toContain('Leagues not found'); }); it('should handle API returning null data', async () => { // Arrange const leagueId = 'league-1'; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: true, json: async () => null, }); } return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Not Found', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.type).toBe('notFound'); expect(error.message).toContain('Leagues not found'); }); it('should handle API returning malformed data', async () => { // Arrange const leagueId = 'league-1'; global.fetch = vi.fn((url: string) => { if (url.includes('/leagues/all-with-capacity-and-scoring')) { return Promise.resolve({ ok: true, json: async () => ({ someOtherProperty: 'value' }), }); } return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Not Found', }); }); // Act const result = await LeagueDetailPageQuery.execute(leagueId); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.type).toBe('notFound'); expect(error.message).toContain('Leagues not found'); }); }); });