website refactor

This commit is contained in:
2026-01-21 22:36:01 +01:00
parent ea58909070
commit 5ed958281d
49 changed files with 8763 additions and 131 deletions

View File

@@ -0,0 +1,662 @@
/**
* 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');
});
});
});

View File

@@ -0,0 +1,364 @@
/**
* 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);
});
});
});

View File

@@ -0,0 +1,149 @@
import { LeaguesApiClient } from '../../../../apps/website/lib/api/leagues/LeaguesApiClient';
import { ApiError } from '../../../../apps/website/lib/api/base/ApiError';
import type { Logger } from '../../../../apps/website/lib/interfaces/Logger';
import type { ErrorReporter } from '../../../../apps/website/lib/interfaces/ErrorReporter';
/**
* Mock LeaguesApiClient for testing
* Allows controlled responses without making actual HTTP calls
*/
export class MockLeaguesApiClient extends LeaguesApiClient {
private mockResponses: Map<string, any> = new Map();
private mockErrors: Map<string, ApiError> = new Map();
constructor(
baseUrl: string = 'http://localhost:3001',
errorReporter: ErrorReporter = {
report: () => {},
} as any,
logger: Logger = {
info: () => {},
warn: () => {},
error: () => {},
} as any
) {
super(baseUrl, errorReporter, logger);
}
/**
* Set a mock response for a specific endpoint
*/
setMockResponse(endpoint: string, response: any): void {
this.mockResponses.set(endpoint, response);
}
/**
* Set a mock error for a specific endpoint
*/
setMockError(endpoint: string, error: ApiError): void {
this.mockErrors.set(endpoint, error);
}
/**
* Clear all mock responses and errors
*/
clearMocks(): void {
this.mockResponses.clear();
this.mockErrors.clear();
}
/**
* Override getAllWithCapacityAndScoring to return mock data
*/
async getAllWithCapacityAndScoring(): Promise<any> {
const endpoint = '/leagues/all-with-capacity-and-scoring';
if (this.mockErrors.has(endpoint)) {
throw this.mockErrors.get(endpoint);
}
if (this.mockResponses.has(endpoint)) {
return this.mockResponses.get(endpoint);
}
// Default mock response
return {
leagues: [
{
id: 'league-1',
name: 'Test League',
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',
scoringPresetId: 'preset-1',
scoringPresetName: 'Test Preset',
dropPolicySummary: 'No drops',
scoringPatternSummary: 'Standard scoring',
},
},
],
totalCount: 1,
};
}
/**
* Override getMemberships to return mock data
*/
async getMemberships(leagueId: string): Promise<any> {
const endpoint = `/leagues/${leagueId}/memberships`;
if (this.mockErrors.has(endpoint)) {
throw this.mockErrors.get(endpoint);
}
if (this.mockResponses.has(endpoint)) {
return this.mockResponses.get(endpoint);
}
// Default mock response
return {
members: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
iracingId: '12345',
name: 'Test Driver',
country: 'US',
joinedAt: new Date().toISOString(),
},
role: 'owner',
status: 'active',
joinedAt: new Date().toISOString(),
},
],
};
}
/**
* Override getLeagueConfig to return mock data
*/
async getLeagueConfig(leagueId: string): Promise<any> {
const endpoint = `/leagues/${leagueId}/config`;
if (this.mockErrors.has(endpoint)) {
throw this.mockErrors.get(endpoint);
}
if (this.mockResponses.has(endpoint)) {
return this.mockResponses.get(endpoint);
}
// Default mock response
return {
form: {
scoring: {
presetId: 'preset-1',
},
},
};
}
}