/** * Unit tests for GetDashboardUseCase * * Tests cover: * 1) Validation of driverId (empty and whitespace) * 2) Driver not found * 3) Filters invalid races (missing trackName, past dates) */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { GetDashboardUseCase } from './GetDashboardUseCase'; import { ValidationError } from '../../../shared/errors/ValidationError'; import { DriverNotFoundError } from '../../domain/errors/DriverNotFoundError'; import { DashboardRepository } from '../ports/DashboardRepository'; import { DashboardEventPublisher } from '../ports/DashboardEventPublisher'; import { Logger } from '../../../shared/domain/Logger'; import { DriverData, RaceData, LeagueStandingData, ActivityData } from '../ports/DashboardRepository'; describe('GetDashboardUseCase', () => { let mockDriverRepository: DashboardRepository; let mockRaceRepository: DashboardRepository; let mockLeagueRepository: DashboardRepository; let mockActivityRepository: DashboardRepository; let mockEventPublisher: DashboardEventPublisher; let mockLogger: Logger; let useCase: GetDashboardUseCase; beforeEach(() => { // Mock all ports with vi.fn() mockDriverRepository = { findDriverById: vi.fn(), getUpcomingRaces: vi.fn(), getLeagueStandings: vi.fn(), getRecentActivity: vi.fn(), getFriends: vi.fn(), }; mockRaceRepository = { findDriverById: vi.fn(), getUpcomingRaces: vi.fn(), getLeagueStandings: vi.fn(), getRecentActivity: vi.fn(), getFriends: vi.fn(), }; mockLeagueRepository = { findDriverById: vi.fn(), getUpcomingRaces: vi.fn(), getLeagueStandings: vi.fn(), getRecentActivity: vi.fn(), getFriends: vi.fn(), }; mockActivityRepository = { findDriverById: vi.fn(), getUpcomingRaces: vi.fn(), getLeagueStandings: vi.fn(), getRecentActivity: vi.fn(), getFriends: vi.fn(), }; mockEventPublisher = { publishDashboardAccessed: vi.fn(), publishDashboardError: vi.fn(), }; mockLogger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; useCase = new GetDashboardUseCase({ driverRepository: mockDriverRepository, raceRepository: mockRaceRepository, leagueRepository: mockLeagueRepository, activityRepository: mockActivityRepository, eventPublisher: mockEventPublisher, logger: mockLogger, }); }); describe('Scenario 1: Validation of driverId', () => { it('should throw ValidationError when driverId is empty string', async () => { // Given const query = { driverId: '' }; // When & Then await expect(useCase.execute(query)).rejects.toThrow(ValidationError); await expect(useCase.execute(query)).rejects.toThrow('Driver ID cannot be empty'); // Verify no repositories were called expect(mockDriverRepository.findDriverById).not.toHaveBeenCalled(); expect(mockRaceRepository.getUpcomingRaces).not.toHaveBeenCalled(); expect(mockLeagueRepository.getLeagueStandings).not.toHaveBeenCalled(); expect(mockActivityRepository.getRecentActivity).not.toHaveBeenCalled(); expect(mockEventPublisher.publishDashboardAccessed).not.toHaveBeenCalled(); }); it('should throw ValidationError when driverId is whitespace only', async () => { // Given const query = { driverId: ' ' }; // When & Then await expect(useCase.execute(query)).rejects.toThrow(ValidationError); await expect(useCase.execute(query)).rejects.toThrow('Driver ID cannot be empty'); // Verify no repositories were called expect(mockDriverRepository.findDriverById).not.toHaveBeenCalled(); expect(mockRaceRepository.getUpcomingRaces).not.toHaveBeenCalled(); expect(mockLeagueRepository.getLeagueStandings).not.toHaveBeenCalled(); expect(mockActivityRepository.getRecentActivity).not.toHaveBeenCalled(); expect(mockEventPublisher.publishDashboardAccessed).not.toHaveBeenCalled(); }); }); describe('Scenario 2: Driver not found', () => { it('should throw DriverNotFoundError when driverRepository.findDriverById returns null', async () => { // Given const query = { driverId: 'driver-123' }; (mockDriverRepository.findDriverById as any).mockResolvedValue(null); // When & Then await expect(useCase.execute(query)).rejects.toThrow(DriverNotFoundError); await expect(useCase.execute(query)).rejects.toThrow('Driver with ID "driver-123" not found'); // Verify driver repository was called expect(mockDriverRepository.findDriverById).toHaveBeenCalledWith('driver-123'); // Verify other repositories were not called (since driver not found) expect(mockRaceRepository.getUpcomingRaces).not.toHaveBeenCalled(); expect(mockLeagueRepository.getLeagueStandings).not.toHaveBeenCalled(); expect(mockActivityRepository.getRecentActivity).not.toHaveBeenCalled(); expect(mockEventPublisher.publishDashboardAccessed).not.toHaveBeenCalled(); }); }); describe('Scenario 3: Filters invalid races', () => { it('should exclude races missing trackName', async () => { // Given const query = { driverId: 'driver-123' }; // Mock driver exists (mockDriverRepository.findDriverById as any).mockResolvedValue({ id: 'driver-123', name: 'Test Driver', rating: 1500, rank: 10, starts: 50, wins: 10, podiums: 20, leagues: 3, } as DriverData); // Mock races with missing trackName (mockRaceRepository.getUpcomingRaces as any).mockResolvedValue([ { id: 'race-1', trackName: '', // Missing trackName carType: 'GT3', scheduledDate: new Date('2026-01-25T10:00:00.000Z'), }, { id: 'race-2', trackName: 'Track A', carType: 'GT3', scheduledDate: new Date('2026-01-26T10:00:00.000Z'), }, ] as RaceData[]); (mockLeagueRepository.getLeagueStandings as any).mockResolvedValue([]); (mockActivityRepository.getRecentActivity as any).mockResolvedValue([]); // When const result = await useCase.execute(query); // Then expect(result.upcomingRaces).toHaveLength(1); expect(result.upcomingRaces[0].trackName).toBe('Track A'); }); it('should exclude races with past scheduledDate', async () => { // Given const query = { driverId: 'driver-123' }; // Mock driver exists (mockDriverRepository.findDriverById as any).mockResolvedValue({ id: 'driver-123', name: 'Test Driver', rating: 1500, rank: 10, starts: 50, wins: 10, podiums: 20, leagues: 3, } as DriverData); // Mock races with past dates (mockRaceRepository.getUpcomingRaces as any).mockResolvedValue([ { id: 'race-1', trackName: 'Track A', carType: 'GT3', scheduledDate: new Date('2026-01-23T10:00:00.000Z'), // Past }, { id: 'race-2', trackName: 'Track B', carType: 'GT3', scheduledDate: new Date('2026-01-26T10:00:00.000Z'), // Future }, ] as RaceData[]); (mockLeagueRepository.getLeagueStandings as any).mockResolvedValue([]); (mockActivityRepository.getRecentActivity as any).mockResolvedValue([]); // When const result = await useCase.execute(query); // Then expect(result.upcomingRaces).toHaveLength(1); expect(result.upcomingRaces[0].trackName).toBe('Track B'); }); it('should exclude races with missing trackName and past dates', async () => { // Given const query = { driverId: 'driver-123' }; // Mock driver exists (mockDriverRepository.findDriverById as any).mockResolvedValue({ id: 'driver-123', name: 'Test Driver', rating: 1500, rank: 10, starts: 50, wins: 10, podiums: 20, leagues: 3, } as DriverData); // Mock races with various invalid states (mockRaceRepository.getUpcomingRaces as any).mockResolvedValue([ { id: 'race-1', trackName: '', // Missing trackName carType: 'GT3', scheduledDate: new Date('2026-01-25T10:00:00.000Z'), // Future }, { id: 'race-2', trackName: 'Track A', carType: 'GT3', scheduledDate: new Date('2026-01-23T10:00:00.000Z'), // Past }, { id: 'race-3', trackName: 'Track B', carType: 'GT3', scheduledDate: new Date('2026-01-26T10:00:00.000Z'), // Future }, ] as RaceData[]); (mockLeagueRepository.getLeagueStandings as any).mockResolvedValue([]); (mockActivityRepository.getRecentActivity as any).mockResolvedValue([]); // When const result = await useCase.execute(query); // Then expect(result.upcomingRaces).toHaveLength(1); expect(result.upcomingRaces[0].trackName).toBe('Track B'); }); it('should include only valid races with trackName and future dates', async () => { // Given const query = { driverId: 'driver-123' }; // Mock driver exists (mockDriverRepository.findDriverById as any).mockResolvedValue({ id: 'driver-123', name: 'Test Driver', rating: 1500, rank: 10, starts: 50, wins: 10, podiums: 20, leagues: 3, } as DriverData); // Mock races with valid data (mockRaceRepository.getUpcomingRaces as any).mockResolvedValue([ { id: 'race-1', trackName: 'Track A', carType: 'GT3', scheduledDate: new Date('2026-01-26T10:00:00.000Z'), }, { id: 'race-2', trackName: 'Track B', carType: 'GT4', scheduledDate: new Date('2026-01-27T10:00:00.000Z'), }, ] as RaceData[]); (mockLeagueRepository.getLeagueStandings as any).mockResolvedValue([]); (mockActivityRepository.getRecentActivity as any).mockResolvedValue([]); // When const result = await useCase.execute(query); // Then expect(result.upcomingRaces).toHaveLength(2); expect(result.upcomingRaces[0].trackName).toBe('Track A'); expect(result.upcomingRaces[1].trackName).toBe('Track B'); }); }); });