Files
gridpilot.gg/apps/website/lib/services/leagues/LeagueService.test.ts
2025-12-27 11:58:35 +01:00

333 lines
12 KiB
TypeScript

import { describe, it, expect, vi, Mocked } from 'vitest';
import { LeagueService } from './LeagueService';
import { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
import { LeagueStandingsViewModel } from '../../view-models/LeagueStandingsViewModel';
import { LeagueStatsViewModel } from '../../view-models/LeagueStatsViewModel';
import { LeagueScheduleViewModel } from '../../view-models/LeagueScheduleViewModel';
import { LeagueMembershipsViewModel } from '../../view-models/LeagueMembershipsViewModel';
import { RemoveMemberViewModel } from '../../view-models/RemoveMemberViewModel';
import { LeagueMemberViewModel } from '../../view-models/LeagueMemberViewModel';
import type { CreateLeagueInputDTO } from '../../types/generated/CreateLeagueInputDTO';
import type { CreateLeagueOutputDTO } from '../../types/generated/CreateLeagueOutputDTO';
import type { RemoveLeagueMemberOutputDTO } from '../../types/generated/RemoveLeagueMemberOutputDTO';
describe('LeagueService', () => {
let mockApiClient: Mocked<LeaguesApiClient>;
let service: LeagueService;
beforeEach(() => {
mockApiClient = {
getAllWithCapacity: vi.fn(),
getAllWithCapacityAndScoring: vi.fn(),
getStandings: vi.fn(),
getTotal: vi.fn(),
getSchedule: vi.fn(),
getMemberships: vi.fn(),
create: vi.fn(),
removeMember: vi.fn(),
} as unknown as Mocked<LeaguesApiClient>;
service = new LeagueService(mockApiClient);
});
describe('getAllLeagues', () => {
it('should call apiClient.getAllWithCapacityAndScoring and return array of LeagueSummaryViewModel', 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).toHaveLength(2);
expect(result.map((l) => l.id)).toEqual(['league-1', 'league-2']);
});
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).toHaveLength(0);
});
it('should throw error when apiClient.getAllWithCapacityAndScoring fails', async () => {
const error = new Error('API call failed');
mockApiClient.getAllWithCapacityAndScoring.mockRejectedValue(error);
await expect(service.getAllLeagues()).rejects.toThrow('API call failed');
});
});
describe('getLeagueStandings', () => {
it('should call apiClient.getStandings and return LeagueStandingsViewModel', async () => {
const leagueId = 'league-123';
const currentUserId = 'user-456';
mockApiClient.getStandings.mockResolvedValue({ standings: [] } as any);
mockApiClient.getMemberships.mockResolvedValue({ members: [] } as any);
const result = await service.getLeagueStandings(leagueId, currentUserId);
expect(mockApiClient.getStandings).toHaveBeenCalledWith(leagueId);
expect(result).toBeInstanceOf(LeagueStandingsViewModel);
});
it('should throw error when apiClient.getStandings fails', async () => {
const leagueId = 'league-123';
const currentUserId = 'user-456';
const error = new Error('API call failed');
mockApiClient.getStandings.mockRejectedValue(error);
await expect(service.getLeagueStandings(leagueId, currentUserId)).rejects.toThrow('API call failed');
});
});
describe('getLeagueStats', () => {
it('should call apiClient.getTotal and return LeagueStatsViewModel', async () => {
const mockDto = { totalLeagues: 42 };
mockApiClient.getTotal.mockResolvedValue(mockDto);
const result = await service.getLeagueStats();
expect(mockApiClient.getTotal).toHaveBeenCalled();
expect(result).toBeInstanceOf(LeagueStatsViewModel);
expect(result.totalLeagues).toBe(42);
});
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 LeagueScheduleViewModel', async () => {
const leagueId = 'league-123';
const mockDto = {
races: [
{ id: 'race-1', name: 'Race One', date: new Date().toISOString() },
{ id: 'race-2', name: 'Race Two', date: new Date().toISOString() },
],
} as any;
mockApiClient.getSchedule.mockResolvedValue(mockDto);
const result = await service.getLeagueSchedule(leagueId);
expect(mockApiClient.getSchedule).toHaveBeenCalledWith(leagueId);
expect(result).toBeInstanceOf(LeagueScheduleViewModel);
expect(result.races).toEqual(mockDto.races);
});
it('should handle empty races array', async () => {
const leagueId = 'league-123';
const mockDto = { races: [] };
mockApiClient.getSchedule.mockResolvedValue(mockDto);
const result = await service.getLeagueSchedule(leagueId);
expect(result.races).toEqual([]);
expect(result.hasRaces).toBe(false);
});
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('getLeagueMemberships', () => {
it('should call apiClient.getMemberships and return LeagueMembershipsViewModel', async () => {
const leagueId = 'league-123';
const currentUserId = 'user-456';
const mockDto = {
members: [{ driverId: 'driver-1' }, { driverId: 'driver-2' }],
} as any;
mockApiClient.getMemberships.mockResolvedValue(mockDto);
const result = await service.getLeagueMemberships(leagueId, currentUserId);
expect(mockApiClient.getMemberships).toHaveBeenCalledWith(leagueId);
expect(result).toBeInstanceOf(LeagueMembershipsViewModel);
expect(result.memberships).toHaveLength(2);
const first = result.memberships[0]!;
expect(first).toBeInstanceOf(LeagueMemberViewModel);
expect(first.driverId).toBe('driver-1');
});
it('should handle empty memberships array', async () => {
const leagueId = 'league-123';
const currentUserId = 'user-456';
const mockDto = { members: [] } as any;
mockApiClient.getMemberships.mockResolvedValue(mockDto);
const result = await service.getLeagueMemberships(leagueId, currentUserId);
expect(result.memberships).toHaveLength(0);
expect(result.hasMembers).toBe(false);
});
it('should throw error when apiClient.getMemberships fails', async () => {
const leagueId = 'league-123';
const currentUserId = 'user-456';
const error = new Error('API call failed');
mockApiClient.getMemberships.mockRejectedValue(error);
await expect(service.getLeagueMemberships(leagueId, currentUserId)).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);
await service.createLeague(input);
expect(mockApiClient.create).toHaveBeenCalledWith(input);
});
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');
});
it('should not call apiClient.create when submitBlocker is blocked', async () => {
const input: CreateLeagueInputDTO = {
name: 'New League',
description: 'A new league',
visibility: 'public',
ownerId: 'owner-1',
};
// First call should succeed
const mockDto: CreateLeagueOutputDTO = {
leagueId: 'new-league-id',
success: true,
};
mockApiClient.create.mockResolvedValue(mockDto);
await service.createLeague(input); // This should block the submitBlocker
// Reset mock to check calls
mockApiClient.create.mockClear();
// Second call should not call API
await service.createLeague(input);
expect(mockApiClient.create).not.toHaveBeenCalled();
});
it('should not call apiClient.create when throttle is active', async () => {
const input: CreateLeagueInputDTO = {
name: 'New League',
description: 'A new league',
visibility: 'public',
ownerId: 'owner-1',
};
// First call
const mockDto: CreateLeagueOutputDTO = {
leagueId: 'new-league-id',
success: true,
};
mockApiClient.create.mockResolvedValue(mockDto);
await service.createLeague(input); // This blocks throttle for 500ms
// Reset mock
mockApiClient.create.mockClear();
// Immediate second call should not call API due to throttle
await service.createLeague(input);
expect(mockApiClient.create).not.toHaveBeenCalled();
});
});
describe('removeMember', () => {
it('should call apiClient.removeMember and return RemoveMemberViewModel', async () => {
const leagueId = 'league-123';
const performerDriverId = 'performer-456';
const targetDriverId = 'target-789';
const mockDto: RemoveLeagueMemberOutputDTO = { success: true };
mockApiClient.removeMember.mockResolvedValue(mockDto);
const result = await service.removeMember(leagueId, performerDriverId, targetDriverId);
expect(mockApiClient.removeMember).toHaveBeenCalledWith(leagueId, performerDriverId, targetDriverId);
expect(result).toBeInstanceOf(RemoveMemberViewModel);
expect(result.success).toBe(true);
});
it('should handle unsuccessful removal', async () => {
const leagueId = 'league-123';
const performerDriverId = 'performer-456';
const targetDriverId = 'target-789';
const mockDto: RemoveLeagueMemberOutputDTO = { success: false };
mockApiClient.removeMember.mockResolvedValue(mockDto);
const result = await service.removeMember(leagueId, performerDriverId, targetDriverId);
expect(result.success).toBe(false);
expect(result.successMessage).toBe('Failed to remove member.');
});
it('should throw error when apiClient.removeMember fails', async () => {
const leagueId = 'league-123';
const performerDriverId = 'performer-456';
const targetDriverId = 'target-789';
const error = new Error('API call failed');
mockApiClient.removeMember.mockRejectedValue(error);
await expect(service.removeMember(leagueId, performerDriverId, targetDriverId)).rejects.toThrow('API call failed');
});
});
});