296 lines
11 KiB
TypeScript
296 lines
11 KiB
TypeScript
import { Standing } from '@core/racing/domain/entities/Standing';
|
|
import type { Logger } from '@core/shared/domain/Logger';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { InMemoryStandingRepository } from './InMemoryStandingRepository';
|
|
|
|
/* 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);
|
|
});
|
|
});
|
|
}); |