281 lines
8.9 KiB
TypeScript
281 lines
8.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
|
import { JoinLeagueUseCase } from './JoinLeagueUseCase';
|
|
import type { JoinLeagueCommand } from '../ports/JoinLeagueCommand';
|
|
import { LeagueRepository } from '../ports/LeagueRepository';
|
|
import { DriverRepository } from '../../../racing/domain/repositories/DriverRepository';
|
|
import { EventPublisher } from '../../../shared/ports/EventPublisher';
|
|
|
|
const mockLeagueRepository: LeagueRepository = {
|
|
create: vi.fn() as unknown as Mock,
|
|
findById: vi.fn() as unknown as Mock,
|
|
findByName: vi.fn() as unknown as Mock,
|
|
findByOwner: vi.fn() as unknown as Mock,
|
|
search: vi.fn() as unknown as Mock,
|
|
update: vi.fn() as unknown as Mock,
|
|
delete: vi.fn() as unknown as Mock,
|
|
getStats: vi.fn() as unknown as Mock,
|
|
updateStats: vi.fn() as unknown as Mock,
|
|
getFinancials: vi.fn() as unknown as Mock,
|
|
updateFinancials: vi.fn() as unknown as Mock,
|
|
getStewardingMetrics: vi.fn() as unknown as Mock,
|
|
updateStewardingMetrics: vi.fn() as unknown as Mock,
|
|
getPerformanceMetrics: vi.fn() as unknown as Mock,
|
|
updatePerformanceMetrics: vi.fn() as unknown as Mock,
|
|
getRatingMetrics: vi.fn() as unknown as Mock,
|
|
updateRatingMetrics: vi.fn() as unknown as Mock,
|
|
getTrendMetrics: vi.fn() as unknown as Mock,
|
|
updateTrendMetrics: vi.fn() as unknown as Mock,
|
|
getSuccessRateMetrics: vi.fn() as unknown as Mock,
|
|
updateSuccessRateMetrics: vi.fn() as unknown as Mock,
|
|
getResolutionTimeMetrics: vi.fn() as unknown as Mock,
|
|
updateResolutionTimeMetrics: vi.fn() as unknown as Mock,
|
|
getComplexSuccessRateMetrics: vi.fn() as unknown as Mock,
|
|
updateComplexSuccessRateMetrics: vi.fn() as unknown as Mock,
|
|
getComplexResolutionTimeMetrics: vi.fn() as unknown as Mock,
|
|
updateComplexResolutionTimeMetrics: vi.fn() as unknown as Mock,
|
|
getLeagueMembers: vi.fn() as unknown as Mock,
|
|
getPendingRequests: vi.fn() as unknown as Mock,
|
|
addLeagueMembers: vi.fn() as unknown as Mock,
|
|
updateLeagueMember: vi.fn() as unknown as Mock,
|
|
removeLeagueMember: vi.fn() as unknown as Mock,
|
|
addPendingRequests: vi.fn() as unknown as Mock,
|
|
removePendingRequest: vi.fn() as unknown as Mock,
|
|
};
|
|
|
|
const mockDriverRepository: DriverRepository = {
|
|
findById: vi.fn() as unknown as Mock,
|
|
findByIRacingId: vi.fn() as unknown as Mock,
|
|
findAll: vi.fn() as unknown as Mock,
|
|
create: vi.fn() as unknown as Mock,
|
|
update: vi.fn() as unknown as Mock,
|
|
delete: vi.fn() as unknown as Mock,
|
|
exists: vi.fn() as unknown as Mock,
|
|
existsByIRacingId: vi.fn() as unknown as Mock,
|
|
};
|
|
|
|
const mockEventPublisher: EventPublisher = {
|
|
publish: vi.fn() as unknown as Mock,
|
|
};
|
|
|
|
describe('JoinLeagueUseCase', () => {
|
|
let useCase: JoinLeagueUseCase;
|
|
|
|
beforeEach(() => {
|
|
// Reset mocks
|
|
vi.clearAllMocks();
|
|
|
|
useCase = new JoinLeagueUseCase(
|
|
mockLeagueRepository,
|
|
mockDriverRepository,
|
|
mockEventPublisher
|
|
);
|
|
});
|
|
|
|
describe('Scenario 1: League missing', () => {
|
|
it('should throw "League not found" when league does not exist', async () => {
|
|
// Given
|
|
const command: JoinLeagueCommand = {
|
|
leagueId: 'league-123',
|
|
driverId: 'driver-456',
|
|
};
|
|
|
|
(mockLeagueRepository.findById as Mock).mockImplementation(() => Promise.resolve(null));
|
|
|
|
// When & Then
|
|
await expect(useCase.execute(command)).rejects.toThrow('League not found');
|
|
expect(mockLeagueRepository.findById).toHaveBeenCalledWith('league-123');
|
|
});
|
|
});
|
|
|
|
describe('Scenario 2: Driver missing', () => {
|
|
it('should throw "Driver not found" when driver does not exist', async () => {
|
|
// Given
|
|
const command: JoinLeagueCommand = {
|
|
leagueId: 'league-123',
|
|
driverId: 'driver-456',
|
|
};
|
|
|
|
const mockLeague = {
|
|
id: 'league-123',
|
|
name: 'Test League',
|
|
description: null,
|
|
visibility: 'public' as const,
|
|
ownerId: 'owner-789',
|
|
status: 'active' as const,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
maxDrivers: null,
|
|
approvalRequired: true,
|
|
lateJoinAllowed: true,
|
|
raceFrequency: null,
|
|
raceDay: null,
|
|
raceTime: null,
|
|
tracks: null,
|
|
scoringSystem: null,
|
|
bonusPointsEnabled: false,
|
|
penaltiesEnabled: false,
|
|
protestsEnabled: false,
|
|
appealsEnabled: false,
|
|
stewardTeam: null,
|
|
gameType: null,
|
|
skillLevel: null,
|
|
category: null,
|
|
tags: null,
|
|
};
|
|
|
|
(mockLeagueRepository.findById as Mock).mockImplementation(() => Promise.resolve(mockLeague));
|
|
(mockDriverRepository.findById as Mock).mockImplementation(() => Promise.resolve(null));
|
|
|
|
// When & Then
|
|
await expect(useCase.execute(command)).rejects.toThrow('Driver not found');
|
|
expect(mockLeagueRepository.findById).toHaveBeenCalledWith('league-123');
|
|
expect(mockDriverRepository.findById).toHaveBeenCalledWith('driver-456');
|
|
});
|
|
});
|
|
|
|
describe('Scenario 3: approvalRequired path uses pending requests + time determinism', () => {
|
|
it('should add pending request with deterministic time when approvalRequired is true', async () => {
|
|
// Given
|
|
const command: JoinLeagueCommand = {
|
|
leagueId: 'league-123',
|
|
driverId: 'driver-456',
|
|
};
|
|
|
|
const mockLeague = {
|
|
id: 'league-123',
|
|
name: 'Test League',
|
|
description: null,
|
|
visibility: 'public' as const,
|
|
ownerId: 'owner-789',
|
|
status: 'active' as const,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
maxDrivers: null,
|
|
approvalRequired: true,
|
|
lateJoinAllowed: true,
|
|
raceFrequency: null,
|
|
raceDay: null,
|
|
raceTime: null,
|
|
tracks: null,
|
|
scoringSystem: null,
|
|
bonusPointsEnabled: false,
|
|
penaltiesEnabled: false,
|
|
protestsEnabled: false,
|
|
appealsEnabled: false,
|
|
stewardTeam: null,
|
|
gameType: null,
|
|
skillLevel: null,
|
|
category: null,
|
|
tags: null,
|
|
};
|
|
|
|
const mockDriver = {
|
|
id: 'driver-456',
|
|
name: 'Test Driver',
|
|
iracingId: 'iracing-123',
|
|
avatarUrl: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
// Freeze time for deterministic testing
|
|
const frozenTime = new Date('2024-01-01T00:00:00.000Z');
|
|
vi.setSystemTime(frozenTime);
|
|
|
|
(mockLeagueRepository.findById as Mock).mockResolvedValue(mockLeague);
|
|
(mockDriverRepository.findById as Mock).mockResolvedValue(mockDriver);
|
|
|
|
// When
|
|
await useCase.execute(command);
|
|
|
|
// Then
|
|
expect(mockLeagueRepository.addPendingRequests).toHaveBeenCalledWith(
|
|
'league-123',
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
id: expect.any(String),
|
|
driverId: 'driver-456',
|
|
name: 'Test Driver',
|
|
requestDate: frozenTime,
|
|
}),
|
|
])
|
|
);
|
|
|
|
// Verify no members were added
|
|
expect(mockLeagueRepository.addLeagueMembers).not.toHaveBeenCalled();
|
|
|
|
// Reset system time
|
|
vi.useRealTimers();
|
|
});
|
|
});
|
|
|
|
describe('Scenario 4: no-approval path adds member', () => {
|
|
it('should add member when approvalRequired is false', async () => {
|
|
// Given
|
|
const command: JoinLeagueCommand = {
|
|
leagueId: 'league-123',
|
|
driverId: 'driver-456',
|
|
};
|
|
|
|
const mockLeague = {
|
|
id: 'league-123',
|
|
name: 'Test League',
|
|
description: null,
|
|
visibility: 'public' as const,
|
|
ownerId: 'owner-789',
|
|
status: 'active' as const,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
maxDrivers: null,
|
|
approvalRequired: false,
|
|
lateJoinAllowed: true,
|
|
raceFrequency: null,
|
|
raceDay: null,
|
|
raceTime: null,
|
|
tracks: null,
|
|
scoringSystem: null,
|
|
bonusPointsEnabled: false,
|
|
penaltiesEnabled: false,
|
|
protestsEnabled: false,
|
|
appealsEnabled: false,
|
|
stewardTeam: null,
|
|
gameType: null,
|
|
skillLevel: null,
|
|
category: null,
|
|
tags: null,
|
|
};
|
|
|
|
const mockDriver = {
|
|
id: 'driver-456',
|
|
name: 'Test Driver',
|
|
iracingId: 'iracing-123',
|
|
avatarUrl: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
(mockLeagueRepository.findById as Mock).mockResolvedValue(mockLeague);
|
|
(mockDriverRepository.findById as Mock).mockResolvedValue(mockDriver);
|
|
|
|
// When
|
|
await useCase.execute(command);
|
|
|
|
// Then
|
|
expect(mockLeagueRepository.addLeagueMembers).toHaveBeenCalledWith(
|
|
'league-123',
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
driverId: 'driver-456',
|
|
name: 'Test Driver',
|
|
role: 'member',
|
|
joinDate: expect.any(Date),
|
|
}),
|
|
])
|
|
);
|
|
|
|
// Verify no pending requests were added
|
|
expect(mockLeagueRepository.addPendingRequests).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|