view models
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { describe, it, expect, vi, Mocked } from 'vitest';
|
||||
import { LeagueMembershipService } from './LeagueMembershipService';
|
||||
import { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
|
||||
import { LeagueMemberViewModel } from '../../view-models';
|
||||
import type { LeagueMemberDTO } from '../../types/generated';
|
||||
|
||||
describe('LeagueMembershipService', () => {
|
||||
let mockApiClient: Mocked<LeaguesApiClient>;
|
||||
let service: LeagueMembershipService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApiClient = {
|
||||
getMemberships: vi.fn(),
|
||||
removeMember: vi.fn(),
|
||||
} as Mocked<LeaguesApiClient>;
|
||||
|
||||
service = new LeagueMembershipService(mockApiClient);
|
||||
});
|
||||
|
||||
describe('getLeagueMemberships', () => {
|
||||
it('should call apiClient.getMemberships and return array of LeagueMemberViewModel', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const currentUserId = 'user-456';
|
||||
|
||||
const mockDto = {
|
||||
members: [
|
||||
{ driverId: 'driver-1' },
|
||||
{ driverId: 'driver-2' },
|
||||
] as LeagueMemberDTO[],
|
||||
};
|
||||
|
||||
mockApiClient.getMemberships.mockResolvedValue(mockDto);
|
||||
|
||||
const result = await service.getLeagueMemberships(leagueId, currentUserId);
|
||||
|
||||
expect(mockApiClient.getMemberships).toHaveBeenCalledWith(leagueId);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toBeInstanceOf(LeagueMemberViewModel);
|
||||
expect(result[0].driverId).toBe('driver-1');
|
||||
expect(result[0].currentUserId).toBe(currentUserId);
|
||||
expect(result[1]).toBeInstanceOf(LeagueMemberViewModel);
|
||||
expect(result[1].driverId).toBe('driver-2');
|
||||
expect(result[1].currentUserId).toBe(currentUserId);
|
||||
});
|
||||
|
||||
it('should handle empty members array', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const currentUserId = 'user-456';
|
||||
|
||||
const mockDto = {
|
||||
members: [] as LeagueMemberDTO[],
|
||||
};
|
||||
|
||||
mockApiClient.getMemberships.mockResolvedValue(mockDto);
|
||||
|
||||
const result = await service.getLeagueMemberships(leagueId, currentUserId);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
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('removeMember', () => {
|
||||
it('should call apiClient.removeMember and return the result', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const performerDriverId = 'performer-456';
|
||||
const targetDriverId = 'target-789';
|
||||
|
||||
const mockResult = { success: true };
|
||||
mockApiClient.removeMember.mockResolvedValue(mockResult);
|
||||
|
||||
const result = await service.removeMember(leagueId, performerDriverId, targetDriverId);
|
||||
|
||||
expect(mockApiClient.removeMember).toHaveBeenCalledWith(leagueId, performerDriverId, targetDriverId);
|
||||
expect(result).toEqual(mockResult);
|
||||
});
|
||||
|
||||
it('should handle unsuccessful removal', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const performerDriverId = 'performer-456';
|
||||
const targetDriverId = 'target-789';
|
||||
|
||||
const mockResult = { success: false };
|
||||
mockApiClient.removeMember.mockResolvedValue(mockResult);
|
||||
|
||||
const result = await service.removeMember(leagueId, performerDriverId, targetDriverId);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,11 @@
|
||||
import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
|
||||
import { LeagueMemberViewModel } from '@/lib/view-models/LeagueMemberViewModel';
|
||||
import type { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
|
||||
import { LeagueMemberViewModel } from '../../view-models';
|
||||
import type { LeagueMemberDTO } from '../../types/generated';
|
||||
|
||||
// TODO: Move to generated types when available
|
||||
type LeagueMembershipsDTO = {
|
||||
members: LeagueMemberDTO[];
|
||||
};
|
||||
|
||||
/**
|
||||
* League Membership Service
|
||||
@@ -17,7 +22,7 @@ export class LeagueMembershipService {
|
||||
* Get league memberships with view model transformation
|
||||
*/
|
||||
async getLeagueMemberships(leagueId: string, currentUserId: string): Promise<LeagueMemberViewModel[]> {
|
||||
const dto = await this.apiClient.getMemberships(leagueId);
|
||||
const dto: LeagueMembershipsDTO = await this.apiClient.getMemberships(leagueId);
|
||||
return dto.members.map((member: LeagueMemberDTO) => new LeagueMemberViewModel(member, currentUserId));
|
||||
}
|
||||
|
||||
|
||||
299
apps/website/lib/services/leagues/LeagueService.test.ts
Normal file
299
apps/website/lib/services/leagues/LeagueService.test.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { describe, it, expect, vi, Mocked } from 'vitest';
|
||||
import { LeagueService } from './LeagueService';
|
||||
import { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
|
||||
import { LeagueSummaryViewModel, LeagueStandingsViewModel, LeagueStatsViewModel, LeagueScheduleViewModel, LeagueMembershipsViewModel, CreateLeagueViewModel, RemoveMemberViewModel, LeagueMemberViewModel } from '../../view-models';
|
||||
import type { LeagueWithCapacityDTO, CreateLeagueInputDTO, CreateLeagueOutputDTO, RemoveLeagueMemberOutputDTO, LeagueMemberDTO } from '../../types/generated';
|
||||
|
||||
describe('LeagueService', () => {
|
||||
let mockApiClient: Mocked<LeaguesApiClient>;
|
||||
let service: LeagueService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApiClient = {
|
||||
getAllWithCapacity: vi.fn(),
|
||||
getStandings: vi.fn(),
|
||||
getTotal: vi.fn(),
|
||||
getSchedule: vi.fn(),
|
||||
getMemberships: vi.fn(),
|
||||
create: vi.fn(),
|
||||
removeMember: vi.fn(),
|
||||
} as Mocked<LeaguesApiClient>;
|
||||
|
||||
service = new LeagueService(mockApiClient);
|
||||
});
|
||||
|
||||
describe('getAllLeagues', () => {
|
||||
it('should call apiClient.getAllWithCapacity and return array of LeagueSummaryViewModel', async () => {
|
||||
const mockDto = {
|
||||
leagues: [
|
||||
{ id: 'league-1', name: 'League One' },
|
||||
{ id: 'league-2', name: 'League Two' },
|
||||
] as LeagueWithCapacityDTO[],
|
||||
};
|
||||
|
||||
mockApiClient.getAllWithCapacity.mockResolvedValue(mockDto);
|
||||
|
||||
const result = await service.getAllLeagues();
|
||||
|
||||
expect(mockApiClient.getAllWithCapacity).toHaveBeenCalled();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toBeInstanceOf(LeagueSummaryViewModel);
|
||||
expect(result[0].id).toBe('league-1');
|
||||
expect(result[1]).toBeInstanceOf(LeagueSummaryViewModel);
|
||||
expect(result[1].id).toBe('league-2');
|
||||
});
|
||||
|
||||
it('should handle empty leagues array', async () => {
|
||||
const mockDto = {
|
||||
leagues: [] as LeagueWithCapacityDTO[],
|
||||
};
|
||||
|
||||
mockApiClient.getAllWithCapacity.mockResolvedValue(mockDto);
|
||||
|
||||
const result = await service.getAllLeagues();
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should throw error when apiClient.getAllWithCapacity fails', async () => {
|
||||
const error = new Error('API call failed');
|
||||
mockApiClient.getAllWithCapacity.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';
|
||||
|
||||
const mockDto = {
|
||||
id: leagueId,
|
||||
name: 'Test League',
|
||||
};
|
||||
|
||||
mockApiClient.getStandings.mockResolvedValue(mockDto);
|
||||
|
||||
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' }, { id: 'race-2' }] };
|
||||
|
||||
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 = {
|
||||
memberships: [
|
||||
{ driverId: 'driver-1' },
|
||||
{ driverId: 'driver-2' },
|
||||
] as LeagueMemberDTO[],
|
||||
};
|
||||
|
||||
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);
|
||||
expect(result.memberships[0]).toBeInstanceOf(LeagueMemberViewModel);
|
||||
expect(result.memberships[0].driverId).toBe('driver-1');
|
||||
});
|
||||
|
||||
it('should handle empty memberships array', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const currentUserId = 'user-456';
|
||||
|
||||
const mockDto = {
|
||||
memberships: [] as LeagueMemberDTO[],
|
||||
};
|
||||
|
||||
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 and return CreateLeagueViewModel', async () => {
|
||||
const input: CreateLeagueInputDTO = {
|
||||
name: 'New League',
|
||||
description: 'A new league',
|
||||
};
|
||||
|
||||
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).toBeInstanceOf(CreateLeagueViewModel);
|
||||
expect(result.leagueId).toBe('new-league-id');
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle unsuccessful creation', async () => {
|
||||
const input: CreateLeagueInputDTO = {
|
||||
name: 'New League',
|
||||
description: 'A new league',
|
||||
};
|
||||
|
||||
const mockDto: CreateLeagueOutputDTO = {
|
||||
leagueId: '',
|
||||
success: false,
|
||||
};
|
||||
|
||||
mockApiClient.create.mockResolvedValue(mockDto);
|
||||
|
||||
const result = await service.createLeague(input);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.successMessage).toBe('Failed to create league.');
|
||||
});
|
||||
|
||||
it('should throw error when apiClient.create fails', async () => {
|
||||
const input: CreateLeagueInputDTO = {
|
||||
name: 'New League',
|
||||
description: 'A new league',
|
||||
};
|
||||
|
||||
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.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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
|
||||
import { LeagueSummaryViewModel, LeagueStandingsViewModel } from '../../view-models';
|
||||
import type { CreateLeagueInputDTO, CreateLeagueOutputDTO, LeagueWithCapacityDTO } from '../../types/generated';
|
||||
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
|
||||
import { CreateLeagueInputDTO } from "@/lib/types/generated/CreateLeagueInputDTO";
|
||||
import { LeagueWithCapacityDTO } from "@/lib/types/generated/LeagueWithCapacityDTO";
|
||||
import { CreateLeagueViewModel } from "@/lib/view-models/CreateLeagueViewModel";
|
||||
import { LeagueMembershipsViewModel } from "@/lib/view-models/LeagueMembershipsViewModel";
|
||||
import { LeagueScheduleViewModel } from "@/lib/view-models/LeagueScheduleViewModel";
|
||||
import { LeagueStandingsViewModel } from "@/lib/view-models/LeagueStandingsViewModel";
|
||||
import { LeagueStatsViewModel } from "@/lib/view-models/LeagueStatsViewModel";
|
||||
import { LeagueSummaryViewModel } from "@/lib/view-models/LeagueSummaryViewModel";
|
||||
import { RemoveMemberViewModel } from "@/lib/view-models/RemoveMemberViewModel";
|
||||
|
||||
// TODO: Move these types to apps/website/lib/types/generated when available
|
||||
type LeagueStatsDto = { totalLeagues: number };
|
||||
type LeagueScheduleDto = { races: Array<unknown> };
|
||||
type LeagueMembershipsDto = { memberships: Array<unknown> };
|
||||
|
||||
/**
|
||||
* League Service
|
||||
@@ -43,35 +46,40 @@ export class LeagueService {
|
||||
/**
|
||||
* Get league statistics
|
||||
*/
|
||||
async getLeagueStats(): Promise<LeagueStatsDto> {
|
||||
return await this.apiClient.getTotal();
|
||||
async getLeagueStats(): Promise<LeagueStatsViewModel> {
|
||||
const dto = await this.apiClient.getTotal();
|
||||
return new LeagueStatsViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get league schedule
|
||||
*/
|
||||
async getLeagueSchedule(leagueId: string): Promise<LeagueScheduleDto> {
|
||||
return await this.apiClient.getSchedule(leagueId);
|
||||
async getLeagueSchedule(leagueId: string): Promise<LeagueScheduleViewModel> {
|
||||
const dto = await this.apiClient.getSchedule(leagueId);
|
||||
return new LeagueScheduleViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get league memberships
|
||||
*/
|
||||
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDto> {
|
||||
return await this.apiClient.getMemberships(leagueId);
|
||||
async getLeagueMemberships(leagueId: string, currentUserId: string): Promise<LeagueMembershipsViewModel> {
|
||||
const dto = await this.apiClient.getMemberships(leagueId);
|
||||
return new LeagueMembershipsViewModel(dto, currentUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new league
|
||||
*/
|
||||
async createLeague(input: CreateLeagueInputDTO): Promise<CreateLeagueOutputDTO> {
|
||||
return await this.apiClient.create(input);
|
||||
async createLeague(input: CreateLeagueInputDTO): Promise<CreateLeagueViewModel> {
|
||||
const dto = await this.apiClient.create(input);
|
||||
return new CreateLeagueViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a member from league
|
||||
*/
|
||||
async removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise<{ success: boolean }> {
|
||||
return await this.apiClient.removeMember(leagueId, performerDriverId, targetDriverId);
|
||||
async removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise<RemoveMemberViewModel> {
|
||||
const dto = await this.apiClient.removeMember(leagueId, performerDriverId, targetDriverId);
|
||||
return new RemoveMemberViewModel(dto);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user