/** * Integration Tests for LeaguesPageQuery * * Tests the LeaguesPageQuery with mocked API clients to verify: * - Happy path: API returns valid leagues data * - Error handling: 404 when leagues endpoint not found * - Error handling: 500 when API server error * - Empty results: API returns empty leagues list */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { LeaguesPageQuery } from '@/lib/page-queries/LeaguesPageQuery'; // Mock data factories const createMockLeaguesData = () => ({ leagues: [ { id: 'league-1', name: 'Test League 1', description: 'A test league', ownerId: 'driver-1', createdAt: new Date().toISOString(), usedSlots: 5, settings: { maxDrivers: 10, }, scoring: { gameId: 'game-1', gameName: 'Test Game', primaryChampionshipType: 'driver' as const, scoringPresetId: 'preset-1', scoringPresetName: 'Test Preset', dropPolicySummary: 'No drops', scoringPatternSummary: 'Standard scoring', }, }, { id: 'league-2', name: 'Test League 2', description: 'Another test league', ownerId: 'driver-2', createdAt: new Date().toISOString(), usedSlots: 15, settings: { maxDrivers: 20, }, scoring: { gameId: 'game-1', gameName: 'Test Game', primaryChampionshipType: 'driver' as const, scoringPresetId: 'preset-1', scoringPresetName: 'Test Preset', dropPolicySummary: 'No drops', scoringPatternSummary: 'Standard scoring', }, }, ], totalCount: 2, }); const createMockEmptyLeaguesData = () => ({ leagues: [], }); describe('LeaguesPageQuery Integration', () => { let originalFetch: typeof global.fetch; beforeEach(() => { // Store original fetch to restore later originalFetch = global.fetch; }); afterEach(() => { // Restore original fetch global.fetch = originalFetch; }); describe('Happy Path', () => { it('should return valid leagues data when API returns success', async () => { // Arrange const mockData = createMockLeaguesData(); global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockData, text: async () => JSON.stringify(mockData), }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isOk()).toBe(true); const viewData = result.unwrap(); expect(viewData).toBeDefined(); expect(viewData.leagues).toBeDefined(); expect(viewData.leagues.length).toBe(2); // Verify first league expect(viewData.leagues[0].id).toBe('league-1'); expect(viewData.leagues[0].name).toBe('Test League 1'); expect(viewData.leagues[0].settings.maxDrivers).toBe(10); expect(viewData.leagues[0].usedSlots).toBe(5); // Verify second league expect(viewData.leagues[1].id).toBe('league-2'); expect(viewData.leagues[1].name).toBe('Test League 2'); expect(viewData.leagues[1].settings.maxDrivers).toBe(20); expect(viewData.leagues[1].usedSlots).toBe(15); }); it('should handle single league correctly', async () => { // Arrange const mockData = { leagues: [ { id: 'single-league', name: 'Single League', description: 'Only one league', ownerId: 'driver-1', createdAt: new Date().toISOString(), usedSlots: 3, settings: { maxDrivers: 5, }, scoring: { gameId: 'game-1', gameName: 'Test Game', primaryChampionshipType: 'driver' as const, scoringPresetId: 'preset-1', scoringPresetName: 'Test Preset', dropPolicySummary: 'No drops', scoringPatternSummary: 'Standard scoring', }, }, ], }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockData, text: async () => JSON.stringify(mockData), }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isOk()).toBe(true); const viewData = result.unwrap(); expect(viewData.leagues.length).toBe(1); expect(viewData.leagues[0].id).toBe('single-league'); expect(viewData.leagues[0].name).toBe('Single League'); }); }); describe('Empty Results', () => { it('should handle empty leagues list from API', async () => { // Arrange const mockData = createMockEmptyLeaguesData(); global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockData, text: async () => JSON.stringify(mockData), }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isOk()).toBe(true); const viewData = result.unwrap(); expect(viewData).toBeDefined(); expect(viewData.leagues).toBeDefined(); expect(viewData.leagues.length).toBe(0); }); }); describe('Error Handling', () => { it('should handle 404 error when leagues endpoint not found', async () => { // Arrange global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'Leagues not found', }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('LEAGUES_FETCH_FAILED'); }); it('should handle 500 error when API server error', async () => { // Arrange global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 500, statusText: 'Internal Server Error', text: async () => 'Internal Server Error', }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('LEAGUES_FETCH_FAILED'); }); it('should handle network error', async () => { // Arrange global.fetch = vi.fn().mockRejectedValue(new Error('Network error: Unable to reach the API server')); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('LEAGUES_FETCH_FAILED'); }); it('should handle timeout error', async () => { // Arrange const timeoutError = new Error('Request timed out after 30 seconds'); timeoutError.name = 'AbortError'; global.fetch = vi.fn().mockRejectedValue(timeoutError); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('LEAGUES_FETCH_FAILED'); }); it('should handle unauthorized error (redirect)', async () => { // Arrange global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 401, statusText: 'Unauthorized', text: async () => 'Unauthorized', }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('redirect'); }); it('should handle forbidden error (redirect)', async () => { // Arrange global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 403, statusText: 'Forbidden', text: async () => 'Forbidden', }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('redirect'); }); it('should handle unknown error type', async () => { // Arrange global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 999, statusText: 'Unknown Error', text: async () => 'Unknown error', }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('UNKNOWN_ERROR'); }); }); describe('Edge Cases', () => { it('should handle API returning null or undefined data', async () => { // Arrange global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => null, text: async () => 'null', }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('LEAGUES_FETCH_FAILED'); }); it('should handle API returning malformed data', async () => { // Arrange const mockData = { // Missing 'leagues' property someOtherProperty: 'value', }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockData, }); // Act const result = await LeaguesPageQuery.execute(); // Assert expect(result.isErr()).toBe(true); const error = result.getError(); expect(error).toBe('LEAGUES_FETCH_FAILED'); }); it('should handle API returning leagues with missing required fields', async () => { // Arrange const mockData = { leagues: [ { id: 'league-1', name: 'Test League', // Missing other required fields }, ], }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockData, }); // Act const result = await LeaguesPageQuery.execute(); // Assert // Should still succeed - the builder should handle partial data expect(result.isOk()).toBe(true); const viewData = result.unwrap(); expect(viewData.leagues.length).toBe(1); }); }); });