307 lines
10 KiB
TypeScript
307 lines
10 KiB
TypeScript
import { describe, it, expect, vi, Mocked, beforeEach } from 'vitest';
|
|
import { LeagueService } from './LeagueService';
|
|
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
|
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
|
|
import type { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO';
|
|
import type { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO';
|
|
import type { RemoveLeagueMemberOutputDTO } from '@/lib/types/generated/RemoveLeagueMemberOutputDTO';
|
|
|
|
describe('LeagueService', () => {
|
|
let mockApiClient: Mocked<LeaguesApiClient>;
|
|
let mockRacesApiClient: Mocked<RacesApiClient>;
|
|
let service: LeagueService;
|
|
|
|
beforeEach(() => {
|
|
process.env.API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3001';
|
|
mockApiClient = {
|
|
getAllWithCapacity: vi.fn(),
|
|
getAllWithCapacityAndScoring: vi.fn(),
|
|
getStandings: vi.fn(),
|
|
getTotal: vi.fn(),
|
|
getSchedule: vi.fn(),
|
|
getMemberships: vi.fn(),
|
|
getLeagueConfig: vi.fn(),
|
|
create: vi.fn(),
|
|
removeRosterMember: vi.fn(),
|
|
updateRosterMemberRole: vi.fn(),
|
|
} as unknown as Mocked<LeaguesApiClient>;
|
|
|
|
mockRacesApiClient = {
|
|
getPageData: vi.fn(),
|
|
} as unknown as Mocked<RacesApiClient>;
|
|
|
|
mockApiClient.getLeagueConfig.mockResolvedValue({ form: null } as any);
|
|
|
|
service = new LeagueService(mockApiClient);
|
|
(service as any).racesApiClient = mockRacesApiClient;
|
|
});
|
|
|
|
describe('getAllLeagues', () => {
|
|
it('should call apiClient.getAllWithCapacityAndScoring and return DTO', async () => {
|
|
const mockDto = {
|
|
totalCount: 2,
|
|
leagues: [
|
|
{ id: 'league-1', name: 'League One' },
|
|
{ id: 'league-2', name: 'League Two' },
|
|
],
|
|
} as any;
|
|
|
|
mockApiClient.getAllWithCapacityAndScoring.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.getAllLeagues();
|
|
|
|
expect(mockApiClient.getAllWithCapacityAndScoring).toHaveBeenCalled();
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual(mockDto);
|
|
});
|
|
|
|
it('should handle empty leagues array', async () => {
|
|
const mockDto = { totalCount: 0, leagues: [] } as any;
|
|
|
|
mockApiClient.getAllWithCapacityAndScoring.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.getAllLeagues();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual(mockDto);
|
|
});
|
|
|
|
it('should throw error when apiClient.getAllWithCapacityAndScoring fails', async () => {
|
|
const error = new Error('API call failed');
|
|
mockApiClient.getAllWithCapacityAndScoring.mockRejectedValue(error);
|
|
|
|
const result = await service.getAllLeagues();
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError().message).toBe('API call failed');
|
|
});
|
|
});
|
|
|
|
describe('getLeagueStandings', () => {
|
|
it('should call apiClient.getStandings and return DTO', async () => {
|
|
const leagueId = 'league-123';
|
|
const mockDto = { standings: [] } as any;
|
|
|
|
mockApiClient.getStandings.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.getLeagueStandings(leagueId);
|
|
|
|
expect(mockApiClient.getStandings).toHaveBeenCalledWith(leagueId);
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should throw error when apiClient.getStandings fails', async () => {
|
|
const leagueId = 'league-123';
|
|
const error = new Error('API call failed');
|
|
mockApiClient.getStandings.mockRejectedValue(error);
|
|
|
|
await expect(service.getLeagueStandings(leagueId)).rejects.toThrow('API call failed');
|
|
});
|
|
});
|
|
|
|
describe('getLeagueStats', () => {
|
|
it('should call apiClient.getTotal and return DTO', async () => {
|
|
const mockDto = { totalLeagues: 42 };
|
|
|
|
mockApiClient.getTotal.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.getLeagueStats();
|
|
|
|
expect(mockApiClient.getTotal).toHaveBeenCalled();
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should throw error when apiClient.getTotal fails', async () => {
|
|
const error = new Error('API call failed');
|
|
mockApiClient.getTotal.mockRejectedValue(error);
|
|
|
|
await expect(service.getLeagueStats()).rejects.toThrow('API call failed');
|
|
});
|
|
});
|
|
|
|
describe('getLeagueSchedule', () => {
|
|
it('should call apiClient.getSchedule and return DTO', async () => {
|
|
const leagueId = 'league-123';
|
|
const mockDto = {
|
|
races: [
|
|
{ id: 'race-1', name: 'Race One', date: '2024-12-31T20:00:00Z' },
|
|
{ id: 'race-2', name: 'Race Two', date: '2025-01-02T20:00:00Z' },
|
|
],
|
|
} as any;
|
|
|
|
mockApiClient.getSchedule.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.getLeagueSchedule(leagueId);
|
|
|
|
expect(mockApiClient.getSchedule).toHaveBeenCalledWith(leagueId);
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should handle empty races array', async () => {
|
|
const leagueId = 'league-123';
|
|
const mockDto = { races: [] };
|
|
|
|
mockApiClient.getSchedule.mockResolvedValue(mockDto as any);
|
|
|
|
const result = await service.getLeagueSchedule(leagueId);
|
|
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should throw error when apiClient.getSchedule fails', async () => {
|
|
const leagueId = 'league-123';
|
|
const error = new Error('API call failed');
|
|
mockApiClient.getSchedule.mockRejectedValue(error);
|
|
|
|
await expect(service.getLeagueSchedule(leagueId)).rejects.toThrow('API call failed');
|
|
});
|
|
});
|
|
|
|
describe('getLeagueDetailData', () => {
|
|
it('should use races page-data to enrich races with status/strengthOfField', async () => {
|
|
const leagueId = 'league-123';
|
|
const league = { id: leagueId, name: 'League One', createdAt: '2024-01-01T00:00:00Z' } as any;
|
|
|
|
mockApiClient.getAllWithCapacityAndScoring.mockResolvedValue({ totalCount: 1, leagues: [league] } as any);
|
|
mockApiClient.getMemberships.mockResolvedValue({ members: [] } as any);
|
|
mockRacesApiClient.getPageData.mockResolvedValue({
|
|
races: [
|
|
{
|
|
id: 'race-1',
|
|
track: 'Monza',
|
|
car: 'GT3',
|
|
scheduledAt: '2026-01-01T00:00:00.000Z',
|
|
status: 'running',
|
|
leagueId,
|
|
leagueName: 'League One',
|
|
strengthOfField: 2500,
|
|
isUpcoming: false,
|
|
isLive: true,
|
|
isPast: false,
|
|
},
|
|
],
|
|
} as any);
|
|
|
|
const result = await service.getLeagueDetailData(leagueId);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.races).toHaveLength(1);
|
|
expect((dto.races[0] as any).status).toBe('running');
|
|
expect((dto.races[0] as any).strengthOfField).toBe(2500);
|
|
expect(dto.races[0]!.name).toBe('Monza - GT3');
|
|
expect(dto.races[0]!.date).toBe('2026-01-01T00:00:00.000Z');
|
|
});
|
|
});
|
|
|
|
describe('getLeagueMemberships', () => {
|
|
it('should call apiClient.getMemberships and return DTO', async () => {
|
|
const leagueId = 'league-123';
|
|
const mockDto = {
|
|
members: [{ driverId: 'driver-1' }, { driverId: 'driver-2' }],
|
|
} as any;
|
|
|
|
mockApiClient.getMemberships.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.getLeagueMemberships(leagueId);
|
|
|
|
expect(mockApiClient.getMemberships).toHaveBeenCalledWith(leagueId);
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should handle empty memberships array', async () => {
|
|
const leagueId = 'league-123';
|
|
const mockDto = { members: [] } as any;
|
|
|
|
mockApiClient.getMemberships.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.getLeagueMemberships(leagueId);
|
|
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should throw error when apiClient.getMemberships fails', async () => {
|
|
const leagueId = 'league-123';
|
|
const error = new Error('API call failed');
|
|
mockApiClient.getMemberships.mockRejectedValue(error);
|
|
|
|
await expect(service.getLeagueMemberships(leagueId)).rejects.toThrow('API call failed');
|
|
});
|
|
});
|
|
|
|
describe('createLeague', () => {
|
|
it('should call apiClient.create', async () => {
|
|
const input: CreateLeagueInputDTO = {
|
|
name: 'New League',
|
|
description: 'A new league',
|
|
visibility: 'public',
|
|
ownerId: 'owner-1',
|
|
};
|
|
|
|
const mockDto: CreateLeagueOutputDTO = {
|
|
leagueId: 'new-league-id',
|
|
success: true,
|
|
};
|
|
|
|
mockApiClient.create.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.createLeague(input);
|
|
|
|
expect(mockApiClient.create).toHaveBeenCalledWith(input);
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should throw error when apiClient.create fails', async () => {
|
|
const input: CreateLeagueInputDTO = {
|
|
name: 'New League',
|
|
description: 'A new league',
|
|
visibility: 'public',
|
|
ownerId: 'owner-1',
|
|
};
|
|
|
|
const error = new Error('API call failed');
|
|
mockApiClient.create.mockRejectedValue(error);
|
|
|
|
await expect(service.createLeague(input)).rejects.toThrow('API call failed');
|
|
});
|
|
});
|
|
|
|
describe('removeMember', () => {
|
|
it('should call apiClient.removeRosterMember and return DTO', async () => {
|
|
const leagueId = 'league-123';
|
|
const targetDriverId = 'target-789';
|
|
|
|
const mockDto: RemoveLeagueMemberOutputDTO = { success: true };
|
|
|
|
mockApiClient.removeRosterMember.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.removeMember(leagueId, targetDriverId);
|
|
|
|
expect(mockApiClient.removeRosterMember).toHaveBeenCalledWith(leagueId, targetDriverId);
|
|
expect(result).toEqual(mockDto);
|
|
});
|
|
|
|
it('should handle unsuccessful removal', async () => {
|
|
const leagueId = 'league-123';
|
|
const targetDriverId = 'target-789';
|
|
|
|
const mockDto: RemoveLeagueMemberOutputDTO = { success: false };
|
|
|
|
mockApiClient.removeRosterMember.mockResolvedValue(mockDto);
|
|
|
|
const result = await service.removeMember(leagueId, targetDriverId);
|
|
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should throw error when apiClient.removeRosterMember fails', async () => {
|
|
const leagueId = 'league-123';
|
|
const targetDriverId = 'target-789';
|
|
|
|
const error = new Error('API call failed');
|
|
mockApiClient.removeRosterMember.mockRejectedValue(error);
|
|
|
|
await expect(service.removeMember(leagueId, targetDriverId)).rejects.toThrow('API call failed');
|
|
});
|
|
});
|
|
});
|