Files
gridpilot.gg/adapters/racing/persistence/inmemory/InMemoryStandingRepository.test.ts
2025-12-17 12:05:00 +01:00

296 lines
11 KiB
TypeScript

import { vi, describe, it, expect, beforeEach } from 'vitest';
import { InMemoryStandingRepository } from './InMemoryStandingRepository';
import { Standing } from '@core/racing/domain/entities/Standing';
import type { Logger } from '@core/shared/application';
/* eslint-disable @typescript-eslint/no-explicit-any */
describe('InMemoryStandingRepository', () => {
let repository: InMemoryStandingRepository;
let mockLogger: Logger;
let mockResultRepository: any;
let mockRaceRepository: any;
let mockLeagueRepository: any;
const testPointsSystems: Record<string, Record<number, number>> = {
'f1-2024': {
1: 25, 2: 18, 3: 15, 4: 12, 5: 10,
6: 8, 7: 6, 8: 4, 9: 2, 10: 1
},
'indycar': {
1: 50, 2: 40, 3: 35, 4: 32, 5: 30,
6: 28, 7: 26, 8: 24, 9: 22, 10: 20,
11: 19, 12: 18, 13: 17, 14: 16, 15: 15
}
};
beforeEach(() => {
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockResultRepository = {
findById: vi.fn(),
findAll: vi.fn(),
findByRaceId: vi.fn(),
findByDriverId: vi.fn(),
findByDriverIdAndLeagueId: vi.fn(),
create: vi.fn(),
createMany: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
deleteByRaceId: vi.fn(),
exists: vi.fn(),
existsByRaceId: vi.fn(),
} as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockRaceRepository = {
findById: vi.fn(),
findAll: vi.fn(),
findByLeagueId: vi.fn(),
findUpcomingByLeagueId: vi.fn(),
findCompletedByLeagueId: vi.fn(),
findByStatus: vi.fn(),
findByDateRange: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
exists: vi.fn(),
} as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockLeagueRepository = {
findById: vi.fn(),
findByOwnerId: vi.fn(),
searchByName: vi.fn(),
findAll: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
exists: vi.fn(),
} as any;
repository = new InMemoryStandingRepository(
mockLogger,
testPointsSystems,
mockResultRepository,
mockRaceRepository,
mockLeagueRepository
);
});
const createTestStanding = (leagueId: string, driverId: string, position: number = 1, points: number = 0) => {
return Standing.create({
leagueId,
driverId,
position,
points,
});
};
describe('constructor', () => {
it('should initialize with required parameters', () => {
expect(repository).toBeDefined();
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryStandingRepository initialized.');
});
});
describe('findByLeagueId', () => {
it('should return standings for the specified league', async () => {
const standing1 = createTestStanding('league1', 'driver1', 1, 25);
const standing2 = createTestStanding('league1', 'driver2', 2, 18);
const standing3 = createTestStanding('league2', 'driver3', 1, 50);
await repository.save(standing1);
await repository.save(standing2);
await repository.save(standing3);
const result = await repository.findByLeagueId('league1');
expect(result).toHaveLength(2);
expect(result[0]).toEqual(standing1);
expect(result[1]).toEqual(standing2);
});
it('should return empty array if no standings for league', async () => {
const result = await repository.findByLeagueId('nonexistent');
expect(result).toEqual([]);
});
it('should sort standings by position then points', async () => {
const standing1 = createTestStanding('league1', 'driver1', 2, 18);
const standing2 = createTestStanding('league1', 'driver2', 1, 25);
await repository.save(standing1);
await repository.save(standing2);
const result = await repository.findByLeagueId('league1');
expect(result[0]).toEqual(standing2); // position 1
expect(result[1]).toEqual(standing1); // position 2
});
});
describe('findByDriverIdAndLeagueId', () => {
it('should return the standing if found', async () => {
const standing = createTestStanding('league1', 'driver1');
await repository.save(standing);
const result = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
expect(result).toEqual(standing);
});
it('should return null if not found', async () => {
const result = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
expect(result).toBeNull();
});
});
describe('findAll', () => {
it('should return all standings', async () => {
const standing1 = createTestStanding('league1', 'driver1');
const standing2 = createTestStanding('league2', 'driver2');
await repository.save(standing1);
await repository.save(standing2);
const result = await repository.findAll();
expect(result).toHaveLength(2);
expect(result).toContain(standing1);
expect(result).toContain(standing2);
});
});
describe('save', () => {
it('should save a new standing', async () => {
const standing = createTestStanding('league1', 'driver1');
const result = await repository.save(standing);
expect(result).toEqual(standing);
expect(mockLogger.info).toHaveBeenCalledWith('Standing for league league1, driver driver1 saved successfully.');
});
it('should update existing standing', async () => {
const standing = createTestStanding('league1', 'driver1', 1, 25);
await repository.save(standing);
const updatedStanding = createTestStanding('league1', 'driver1', 1, 35);
const result = await repository.save(updatedStanding);
expect(result).toEqual(updatedStanding);
});
});
describe('saveMany', () => {
it('should save multiple standings', async () => {
const standings = [
createTestStanding('league1', 'driver1'),
createTestStanding('league1', 'driver2'),
];
const result = await repository.saveMany(standings);
expect(result).toEqual(standings);
expect(mockLogger.info).toHaveBeenCalledWith('2 standings saved successfully.');
});
});
describe('delete', () => {
it('should delete existing standing', async () => {
const standing = createTestStanding('league1', 'driver1');
await repository.save(standing);
await repository.delete('league1', 'driver1');
const found = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
expect(found).toBeNull();
expect(mockLogger.info).toHaveBeenCalledWith('Standing for league league1, driver driver1 deleted successfully.');
});
it('should log warning if standing not found', async () => {
await repository.delete('league1', 'driver1');
expect(mockLogger.warn).toHaveBeenCalledWith('Standing for league league1, driver driver1 not found for deletion.');
});
});
describe('deleteByLeagueId', () => {
it('should delete all standings for a league', async () => {
const standing1 = createTestStanding('league1', 'driver1');
const standing2 = createTestStanding('league1', 'driver2');
const standing3 = createTestStanding('league2', 'driver3');
await repository.save(standing1);
await repository.save(standing2);
await repository.save(standing3);
await repository.deleteByLeagueId('league1');
const league1Standings = await repository.findByLeagueId('league1');
const league2Standings = await repository.findByLeagueId('league2');
expect(league1Standings).toHaveLength(0);
expect(league2Standings).toHaveLength(1);
expect(mockLogger.info).toHaveBeenCalledWith('Deleted 2 standings for league id: league1.');
});
});
describe('exists', () => {
it('should return true if standing exists', async () => {
const standing = createTestStanding('league1', 'driver1');
await repository.save(standing);
const result = await repository.exists('league1', 'driver1');
expect(result).toBe(true);
});
it('should return false if standing does not exist', async () => {
const result = await repository.exists('league1', 'driver1');
expect(result).toBe(false);
});
});
describe('recalculate', () => {
it('should throw error if repositories are missing', async () => {
const repoWithoutDeps = new InMemoryStandingRepository(mockLogger, testPointsSystems);
await expect(repoWithoutDeps.recalculate('league1')).rejects.toThrow('Cannot recalculate standings: missing required repositories');
});
it('should throw error if league not found', async () => {
mockLeagueRepository.findById.mockResolvedValue(null);
await expect(repository.recalculate('nonexistent')).rejects.toThrow('League with ID nonexistent not found');
});
it('should return empty array if no completed races', async () => {
const mockLeague = {
id: 'league1',
settings: { pointsSystem: 'f1-2024', customPoints: null },
};
mockLeagueRepository.findById.mockResolvedValue(mockLeague);
mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([]);
const result = await repository.recalculate('league1');
expect(result).toEqual([]);
});
it('should recalculate standings based on race results', async () => {
const mockLeague = {
id: 'league1',
settings: { pointsSystem: 'f1-2024', customPoints: null },
};
const mockRace = { id: 'race1' };
const mockResult1 = { driverId: 'driver1', position: 1 };
const mockResult2 = { driverId: 'driver2', position: 2 };
mockLeagueRepository.findById.mockResolvedValue(mockLeague);
mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([mockRace]);
mockResultRepository.findByRaceId.mockResolvedValue([mockResult1, mockResult2]);
const result = await repository.recalculate('league1');
expect(result).toHaveLength(2);
expect(result[0]?.driverId.toString()).toBe('driver1');
expect(result[0]?.points.toNumber()).toBe(25); // 1st place in f1-2024
expect(result[1]?.driverId.toString()).toBe('driver2');
expect(result[1]?.points.toNumber()).toBe(18); // 2nd place
});
it('should use custom points if available', async () => {
const customPoints = { 1: 100, 2: 80 };
const mockLeague = {
id: 'league1',
settings: { pointsSystem: 'f1-2024', customPoints },
};
const mockRace = { id: 'race1' };
const mockResult = { driverId: 'driver1', position: 1 };
mockLeagueRepository.findById.mockResolvedValue(mockLeague);
mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([mockRace]);
mockResultRepository.findByRaceId.mockResolvedValue([mockResult]);
const result = await repository.recalculate('league1');
expect(result[0]?.points.toNumber()).toBe(100);
});
});
});