Files
gridpilot.gg/core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase.test.ts
2025-12-23 15:38:50 +01:00

303 lines
10 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
CreateLeagueWithSeasonAndScoringUseCase,
type CreateLeagueWithSeasonAndScoringCommand,
type CreateLeagueWithSeasonAndScoringResult,
} from './CreateLeagueWithSeasonAndScoringUseCase';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
let useCase: CreateLeagueWithSeasonAndScoringUseCase;
let leagueRepository: {
create: Mock;
};
let seasonRepository: {
create: Mock;
};
let leagueScoringConfigRepository: {
save: Mock;
};
let getLeagueScoringPresetById: Mock;
let logger: {
debug: Mock;
info: Mock;
warn: Mock;
error: Mock;
};
let output: { present: Mock } & UseCaseOutputPort<CreateLeagueWithSeasonAndScoringResult>;
beforeEach(() => {
leagueRepository = {
create: vi.fn(),
};
seasonRepository = {
create: vi.fn(),
};
leagueScoringConfigRepository = {
save: vi.fn(),
};
getLeagueScoringPresetById = vi.fn();
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
output = { present: vi.fn() } as unknown as typeof output;
useCase = new CreateLeagueWithSeasonAndScoringUseCase(
leagueRepository as unknown as ILeagueRepository,
seasonRepository as unknown as ISeasonRepository,
leagueScoringConfigRepository as unknown as ILeagueScoringConfigRepository,
getLeagueScoringPresetById,
logger as unknown as Logger,
output,
);
});
it('should create league, season, and scoring successfully', async () => {
const command = {
name: 'Test League',
description: 'A test league',
visibility: 'unranked' as const,
ownerId: 'owner-1',
gameId: 'game-1',
maxDrivers: 20,
maxTeams: 5,
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
scoringPresetId: 'club-default',
};
const mockPreset = {
id: 'club-default',
name: 'Club Default',
};
getLeagueScoringPresetById.mockResolvedValue(mockPreset);
leagueRepository.create.mockResolvedValue(undefined);
seasonRepository.create.mockResolvedValue(undefined);
leagueScoringConfigRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = (output.present as Mock).mock.calls[0]?.[0] as unknown as CreateLeagueWithSeasonAndScoringResult;
expect(presented?.league.id.toString()).toBeDefined();
expect(presented?.season.id).toBeDefined();
expect(presented?.scoringConfig.seasonId.toString()).toBe(presented?.season.id);
expect(leagueRepository.create).toHaveBeenCalledTimes(1);
expect(seasonRepository.create).toHaveBeenCalledTimes(1);
expect(leagueScoringConfigRepository.save).toHaveBeenCalledTimes(1);
});
it('should return error when league name is empty', async () => {
const command = {
name: '',
description: 'Test description',
visibility: 'unranked' as const,
ownerId: 'owner-1',
gameId: 'game-1',
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
};
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('VALIDATION_ERROR');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('League name is required');
}
expect(output.present).not.toHaveBeenCalled();
});
it('should return error when ownerId is empty', async () => {
const command = {
name: 'Test League',
description: 'Test description',
visibility: 'unranked' as const,
ownerId: '',
gameId: 'game-1',
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
};
const result = await useCase.execute(command as CreateLeagueWithSeasonAndScoringCommand);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('VALIDATION_ERROR');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('League ownerId is required');
}
expect(output.present).not.toHaveBeenCalled();
});
it('should return error when gameId is empty', async () => {
const command = {
name: 'Test League',
description: 'Test description',
visibility: 'unranked' as const,
ownerId: 'owner-1',
gameId: '',
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
};
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('VALIDATION_ERROR');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('gameId is required');
}
expect(output.present).not.toHaveBeenCalled();
});
it('should return error when visibility is missing', async () => {
const command: Partial<CreateLeagueWithSeasonAndScoringCommand> = {
name: 'Test League',
ownerId: 'owner-1',
gameId: 'game-1',
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
};
const result = await useCase.execute(command as CreateLeagueWithSeasonAndScoringCommand);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('VALIDATION_ERROR');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('visibility is required');
}
expect(output.present).not.toHaveBeenCalled();
});
it('should return error when maxDrivers is invalid', async () => {
const command = {
name: 'Test League',
description: 'Test description',
visibility: 'unranked' as const,
ownerId: 'owner-1',
gameId: 'game-1',
maxDrivers: 0,
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
};
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('VALIDATION_ERROR');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('maxDrivers must be greater than 0 when provided');
}
expect(output.present).not.toHaveBeenCalled();
});
it('should return error when ranked league has insufficient drivers', async () => {
const command = {
name: 'Test League',
description: 'Test description',
visibility: 'ranked' as const,
ownerId: 'owner-1',
gameId: 'game-1',
maxDrivers: 5,
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
};
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('VALIDATION_ERROR');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toContain('Ranked leagues require at least 10 drivers');
}
expect(output.present).not.toHaveBeenCalled();
});
it('should return error when scoring preset is unknown', async () => {
const command = {
name: 'Test League',
description: 'Test description',
visibility: 'unranked' as const,
ownerId: 'owner-1',
gameId: 'game-1',
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
scoringPresetId: 'unknown-preset',
};
getLeagueScoringPresetById.mockResolvedValue(undefined);
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('UNKNOWN_PRESET');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('Unknown scoring preset: unknown-preset');
}
expect(output.present).not.toHaveBeenCalled();
});
it('should return error when repository throws', async () => {
const command = {
name: 'Test League',
description: 'Test description',
visibility: 'unranked' as const,
ownerId: 'owner-1',
gameId: 'game-1',
enableDriverChampionship: true,
enableTeamChampionship: true,
enableNationsChampionship: false,
enableTrophyChampionship: false,
};
const mockPreset = {
id: 'club-default',
name: 'Club Default',
};
getLeagueScoringPresetById.mockResolvedValue(mockPreset);
leagueRepository.create.mockRejectedValue(new Error('DB error'));
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr();
expect(err.code).toBe('REPOSITORY_ERROR');
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('DB error');
}
expect(output.present).not.toHaveBeenCalled();
});
});