365 lines
10 KiB
TypeScript
365 lines
10 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|
|
});
|