Files
gridpilot.gg/core/racing/application/use-cases/CompleteRaceUseCaseWithRatings.test.ts
2025-12-21 00:43:42 +01:00

225 lines
7.8 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
CompleteRaceUseCaseWithRatings,
type CompleteRaceWithRatingsInput,
type CompleteRaceWithRatingsResult,
} from './CompleteRaceUseCaseWithRatings';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
import { RatingUpdateService } from '@core/identity/domain/services/RatingUpdateService';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
describe('CompleteRaceUseCaseWithRatings', () => {
let useCase: CompleteRaceUseCaseWithRatings;
let raceRepository: {
findById: Mock;
update: Mock;
};
let raceRegistrationRepository: {
getRegisteredDrivers: Mock;
};
let resultRepository: {
create: Mock;
};
let standingRepository: {
findByDriverIdAndLeagueId: Mock;
save: Mock;
};
let driverRatingProvider: {
getRatings: Mock;
};
let ratingUpdateService: {
updateDriverRatingsAfterRace: Mock;
};
let output: { present: Mock };
beforeEach(() => {
raceRepository = {
findById: vi.fn(),
update: vi.fn(),
};
raceRegistrationRepository = {
getRegisteredDrivers: vi.fn(),
};
resultRepository = {
create: vi.fn(),
};
standingRepository = {
findByDriverIdAndLeagueId: vi.fn(),
save: vi.fn(),
};
driverRatingProvider = {
getRatings: vi.fn(),
};
ratingUpdateService = {
updateDriverRatingsAfterRace: vi.fn(),
};
output = { present: vi.fn() };
useCase = new CompleteRaceUseCaseWithRatings(
raceRepository as unknown as IRaceRepository,
raceRegistrationRepository as unknown as IRaceRegistrationRepository,
resultRepository as unknown as IResultRepository,
standingRepository as unknown as IStandingRepository,
driverRatingProvider,
ratingUpdateService as unknown as RatingUpdateService,
output as unknown as UseCaseOutputPort<CompleteRaceWithRatingsResult>,
);
});
it('completes race with ratings when race exists and has registered drivers', async () => {
const command: CompleteRaceWithRatingsInput = {
raceId: 'race-1',
};
const mockRace = {
id: 'race-1',
leagueId: 'league-1',
status: 'scheduled',
complete: vi.fn().mockReturnValue({ id: 'race-1', status: 'completed' }),
};
raceRepository.findById.mockResolvedValue(mockRace);
raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1', 'driver-2']);
driverRatingProvider.getRatings.mockReturnValue(
new Map([
['driver-1', 1600],
['driver-2', 1500],
]),
);
resultRepository.create.mockResolvedValue(undefined);
standingRepository.findByDriverIdAndLeagueId.mockResolvedValue(null);
standingRepository.save.mockResolvedValue(undefined);
ratingUpdateService.updateDriverRatingsAfterRace.mockResolvedValue(undefined);
raceRepository.update.mockResolvedValue(undefined);
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(raceRepository.findById).toHaveBeenCalledWith('race-1');
expect(raceRegistrationRepository.getRegisteredDrivers).toHaveBeenCalledWith('race-1');
expect(driverRatingProvider.getRatings).toHaveBeenCalledWith(['driver-1', 'driver-2']);
expect(resultRepository.create).toHaveBeenCalledTimes(2);
expect(standingRepository.save).toHaveBeenCalledTimes(2);
expect(ratingUpdateService.updateDriverRatingsAfterRace).toHaveBeenCalled();
expect(mockRace.complete).toHaveBeenCalled();
expect(raceRepository.update).toHaveBeenCalledWith({ id: 'race-1', status: 'completed' });
expect(output.present).toHaveBeenCalledTimes(1);
expect(output.present).toHaveBeenCalledWith({
raceId: 'race-1',
ratingsUpdatedForDriverIds: ['driver-1', 'driver-2'],
});
});
it('returns error when race does not exist', async () => {
const command: CompleteRaceWithRatingsInput = {
raceId: 'race-1',
};
raceRepository.findById.mockResolvedValue(null);
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('RACE_NOT_FOUND');
expect(output.present).not.toHaveBeenCalled();
});
it('returns error when race is already completed', async () => {
const command: CompleteRaceWithRatingsInput = {
raceId: 'race-1',
};
const mockRace = {
id: 'race-1',
leagueId: 'league-1',
status: 'completed',
complete: vi.fn(),
};
raceRepository.findById.mockResolvedValue(mockRace);
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('ALREADY_COMPLETED');
expect(output.present).not.toHaveBeenCalled();
expect(raceRegistrationRepository.getRegisteredDrivers).not.toHaveBeenCalled();
});
it('returns error when no registered drivers', async () => {
const command: CompleteRaceWithRatingsInput = {
raceId: 'race-1',
};
const mockRace = {
id: 'race-1',
leagueId: 'league-1',
status: 'scheduled',
complete: vi.fn(),
};
raceRepository.findById.mockResolvedValue(mockRace);
raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue([]);
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('NO_REGISTERED_DRIVERS');
expect(output.present).not.toHaveBeenCalled();
});
it('returns rating update error when rating service throws', async () => {
const command: CompleteRaceWithRatingsInput = {
raceId: 'race-1',
};
const mockRace = {
id: 'race-1',
leagueId: 'league-1',
status: 'scheduled',
complete: vi.fn().mockReturnValue({ id: 'race-1', status: 'completed' }),
};
raceRepository.findById.mockResolvedValue(mockRace);
raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1']);
driverRatingProvider.getRatings.mockReturnValue(new Map([['driver-1', 1600]]));
resultRepository.create.mockResolvedValue(undefined);
standingRepository.findByDriverIdAndLeagueId.mockResolvedValue(null);
standingRepository.save.mockResolvedValue(undefined);
ratingUpdateService.updateDriverRatingsAfterRace.mockRejectedValue(new Error('Rating error'));
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('RATING_UPDATE_FAILED');
expect(error.details?.message).toBe('Rating error');
expect(output.present).not.toHaveBeenCalled();
});
it('returns repository error when persistence fails', async () => {
const command: CompleteRaceWithRatingsInput = {
raceId: 'race-1',
};
const mockRace = {
id: 'race-1',
leagueId: 'league-1',
status: 'scheduled',
complete: vi.fn().mockReturnValue({ id: 'race-1', status: 'completed' }),
};
raceRepository.findById.mockResolvedValue(mockRace);
raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1']);
driverRatingProvider.getRatings.mockReturnValue(new Map([['driver-1', 1600]]));
resultRepository.create.mockRejectedValue(new Error('DB error'));
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details?.message).toBe('DB error');
expect(output.present).not.toHaveBeenCalled();
});
});