190 lines
5.8 KiB
TypeScript
190 lines
5.8 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { SendFinalResultsUseCase } from './SendFinalResultsUseCase';
|
|
import type { INotificationService } from '../../../notifications/application/ports/INotificationService';
|
|
import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository';
|
|
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
|
import type { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed';
|
|
|
|
describe('SendFinalResultsUseCase', () => {
|
|
it('sends final results notifications to all participating drivers', async () => {
|
|
const mockNotificationService = {
|
|
sendNotification: vi.fn(),
|
|
} as unknown as INotificationService;
|
|
|
|
const mockRaceEvent = {
|
|
id: 'race-1',
|
|
name: 'Test Race',
|
|
getMainRaceSession: vi.fn().mockReturnValue({ id: 'session-1' }),
|
|
};
|
|
|
|
const mockRaceEventRepository = {
|
|
findById: vi.fn().mockResolvedValue(mockRaceEvent),
|
|
} as unknown as IRaceEventRepository;
|
|
|
|
const mockResults = [
|
|
{
|
|
driverId: 'driver-1',
|
|
position: 1,
|
|
incidents: 0,
|
|
getPositionChange: vi.fn().mockReturnValue(2),
|
|
},
|
|
{
|
|
driverId: 'driver-2',
|
|
position: 2,
|
|
incidents: 1,
|
|
getPositionChange: vi.fn().mockReturnValue(-1),
|
|
},
|
|
];
|
|
|
|
const mockResultRepository = {
|
|
findByRaceId: vi.fn().mockResolvedValue(mockResults),
|
|
} as unknown as IResultRepository;
|
|
|
|
const useCase = new SendFinalResultsUseCase(
|
|
mockNotificationService,
|
|
mockRaceEventRepository,
|
|
mockResultRepository,
|
|
);
|
|
|
|
const event: RaceEventStewardingClosedEvent = {
|
|
eventType: 'RaceEventStewardingClosed',
|
|
aggregateId: 'race-1',
|
|
occurredAt: new Date(),
|
|
eventData: {
|
|
raceEventId: 'race-1',
|
|
seasonId: 'season-1',
|
|
leagueId: 'league-1',
|
|
driverIds: ['driver-1', 'driver-2'],
|
|
hadPenaltiesApplied: false,
|
|
closedAt: new Date(),
|
|
},
|
|
};
|
|
|
|
const result = await useCase.execute(event);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(mockRaceEventRepository.findById).toHaveBeenCalledWith('race-1');
|
|
expect(mockResultRepository.findByRaceId).toHaveBeenCalledWith('session-1');
|
|
expect(mockNotificationService.sendNotification).toHaveBeenCalledTimes(2);
|
|
|
|
// Check first notification
|
|
expect(mockNotificationService.sendNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
recipientId: 'driver-1',
|
|
type: 'race_final_results',
|
|
title: 'Final Results: Test Race',
|
|
body: expect.stringContaining('Final result: P1 (+2 positions). Clean race! +35 rating.'),
|
|
data: expect.objectContaining({
|
|
raceEventId: 'race-1',
|
|
sessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
position: 1,
|
|
positionChange: 2,
|
|
incidents: 0,
|
|
finalRatingChange: 35,
|
|
hadPenaltiesApplied: false,
|
|
}),
|
|
}),
|
|
);
|
|
|
|
// Check second notification
|
|
expect(mockNotificationService.sendNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
recipientId: 'driver-2',
|
|
type: 'race_final_results',
|
|
title: 'Final Results: Test Race',
|
|
body: expect.stringContaining('Final result: P2 (-1 positions). 1 incident +20 rating.'),
|
|
data: expect.objectContaining({
|
|
position: 2,
|
|
positionChange: -1,
|
|
incidents: 1,
|
|
finalRatingChange: 20,
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('skips sending notifications if race event not found', async () => {
|
|
const mockNotificationService = {
|
|
sendNotification: vi.fn(),
|
|
} as unknown as INotificationService;
|
|
|
|
const mockRaceEventRepository = {
|
|
findById: vi.fn().mockResolvedValue(null),
|
|
} as unknown as IRaceEventRepository;
|
|
|
|
const mockResultRepository = {
|
|
findByRaceId: vi.fn(),
|
|
} as unknown as IResultRepository;
|
|
|
|
const useCase = new SendFinalResultsUseCase(
|
|
mockNotificationService,
|
|
mockRaceEventRepository,
|
|
mockResultRepository,
|
|
);
|
|
|
|
const event: RaceEventStewardingClosedEvent = {
|
|
eventType: 'RaceEventStewardingClosed',
|
|
aggregateId: 'race-1',
|
|
occurredAt: new Date(),
|
|
eventData: {
|
|
raceEventId: 'race-1',
|
|
seasonId: 'season-1',
|
|
leagueId: 'league-1',
|
|
driverIds: ['driver-1'],
|
|
hadPenaltiesApplied: false,
|
|
closedAt: new Date(),
|
|
},
|
|
};
|
|
|
|
const result = await useCase.execute(event);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(mockNotificationService.sendNotification).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('skips sending notifications if no main race session', async () => {
|
|
const mockNotificationService = {
|
|
sendNotification: vi.fn(),
|
|
} as unknown as INotificationService;
|
|
|
|
const mockRaceEvent = {
|
|
id: 'race-1',
|
|
name: 'Test Race',
|
|
getMainRaceSession: vi.fn().mockReturnValue(null),
|
|
};
|
|
|
|
const mockRaceEventRepository = {
|
|
findById: vi.fn().mockResolvedValue(mockRaceEvent),
|
|
} as unknown as IRaceEventRepository;
|
|
|
|
const mockResultRepository = {
|
|
findByRaceId: vi.fn(),
|
|
} as unknown as IResultRepository;
|
|
|
|
const useCase = new SendFinalResultsUseCase(
|
|
mockNotificationService,
|
|
mockRaceEventRepository,
|
|
mockResultRepository,
|
|
);
|
|
|
|
const event: RaceEventStewardingClosedEvent = {
|
|
eventType: 'RaceEventStewardingClosed',
|
|
aggregateId: 'race-1',
|
|
occurredAt: new Date(),
|
|
eventData: {
|
|
raceEventId: 'race-1',
|
|
seasonId: 'season-1',
|
|
leagueId: 'league-1',
|
|
driverIds: ['driver-1'],
|
|
hadPenaltiesApplied: false,
|
|
closedAt: new Date(),
|
|
},
|
|
};
|
|
|
|
const result = await useCase.execute(event);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(mockNotificationService.sendNotification).not.toHaveBeenCalled();
|
|
});
|
|
}); |