243 lines
6.9 KiB
TypeScript
243 lines
6.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { JoinLeagueUseCase } from './JoinLeagueUseCase';
|
|
import type { LeagueRepository } from '../ports/LeagueRepository';
|
|
import type { DriverRepository } from '../../../racing/domain/repositories/DriverRepository';
|
|
import type { EventPublisher } from '../../../shared/ports/EventPublisher';
|
|
import type { JoinLeagueCommand } from '../ports/JoinLeagueCommand';
|
|
|
|
const mockLeagueRepository = {
|
|
findById: vi.fn(),
|
|
addPendingRequests: vi.fn(),
|
|
addLeagueMembers: vi.fn(),
|
|
};
|
|
|
|
const mockDriverRepository = {
|
|
findDriverById: vi.fn(),
|
|
};
|
|
|
|
const mockEventPublisher = {
|
|
publish: vi.fn(),
|
|
};
|
|
|
|
describe('JoinLeagueUseCase', () => {
|
|
let useCase: JoinLeagueUseCase;
|
|
|
|
beforeEach(() => {
|
|
// Reset mocks
|
|
vi.clearAllMocks();
|
|
|
|
useCase = new JoinLeagueUseCase(
|
|
mockLeagueRepository as any,
|
|
mockDriverRepository as any,
|
|
mockEventPublisher as any
|
|
);
|
|
});
|
|
|
|
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.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.mockImplementation(() => Promise.resolve(mockLeague));
|
|
mockDriverRepository.findDriverById.mockImplementation(() => Promise.resolve(null));
|
|
|
|
// When & Then
|
|
await expect(useCase.execute(command)).rejects.toThrow('Driver not found');
|
|
expect(mockLeagueRepository.findById).toHaveBeenCalledWith('league-123');
|
|
expect(mockDriverRepository.findDriverById).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.mockResolvedValue(mockLeague);
|
|
mockDriverRepository.findDriverById.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.mockResolvedValue(mockLeague);
|
|
mockDriverRepository.findDriverById.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();
|
|
});
|
|
});
|
|
});
|