/** * Integration Test: League Detail Use Case Orchestration * * Tests the orchestration logic of league detail-related Use Cases: * - GetLeagueUseCase: Retrieves league details * - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers) * - Uses In-Memory adapters for fast, deterministic testing * * Focus: Business logic orchestration, NOT UI rendering */ import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; import { InMemoryLeagueRepository } from '../../../adapters/leagues/persistence/inmemory/InMemoryLeagueRepository'; import { InMemoryLeagueEventPublisher } from '../../../adapters/leagues/events/InMemoryLeagueEventPublisher'; import { GetLeagueUseCase } from '../../../core/leagues/application/use-cases/GetLeagueUseCase'; import { CreateLeagueUseCase } from '../../../core/leagues/application/use-cases/CreateLeagueUseCase'; import { LeagueCreateCommand } from '../../../core/leagues/application/ports/LeagueCreateCommand'; describe('League Detail Use Case Orchestration', () => { let leagueRepository: InMemoryLeagueRepository; let eventPublisher: InMemoryLeagueEventPublisher; let getLeagueUseCase: GetLeagueUseCase; let createLeagueUseCase: CreateLeagueUseCase; beforeAll(() => { leagueRepository = new InMemoryLeagueRepository(); eventPublisher = new InMemoryLeagueEventPublisher(); getLeagueUseCase = new GetLeagueUseCase(leagueRepository, eventPublisher); createLeagueUseCase = new CreateLeagueUseCase(leagueRepository, eventPublisher); }); beforeEach(() => { leagueRepository.clear(); eventPublisher.clear(); }); describe('GetLeagueDetailUseCase - Success Path', () => { it('should retrieve complete league detail with all data', async () => { // Scenario: League with complete data // Given: A league exists with complete data const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Complete League', description: 'A league with all data', visibility: 'public', ownerId: driverId, maxDrivers: 20, approvalRequired: true, lateJoinAllowed: true, raceFrequency: 'weekly', raceDay: 'Saturday', raceTime: '18:00', tracks: ['Monza', 'Spa'], scoringSystem: { points: [25, 18, 15] }, bonusPointsEnabled: true, penaltiesEnabled: true, protestsEnabled: true, appealsEnabled: true, stewardTeam: ['steward-1'], gameType: 'iRacing', skillLevel: 'Intermediate', category: 'GT3', tags: ['competitive'], }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain all league sections expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Complete League'); expect(result.description).toBe('A league with all data'); expect(result.ownerId).toBe(driverId); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); const events = eventPublisher.getLeagueAccessedEvents(); expect(events[0].leagueId).toBe(league.id); expect(events[0].driverId).toBe(driverId); }); it('should retrieve league detail with minimal data', async () => { // Scenario: League with minimal data // Given: A league exists with only basic information (name, description, owner) const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Minimal League', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain basic league info expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Minimal League'); expect(result.ownerId).toBe(driverId); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should retrieve league detail with career history but no recent results', async () => { // Scenario: League with career history but no recent results // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Career History League', description: 'A league with career history', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain career history expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should retrieve league detail with recent results but no career history', async () => { // Scenario: League with recent results but no career history // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Recent Results League', description: 'A league with recent results', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain recent race results expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should retrieve league detail with championship standings but no other data', async () => { // Scenario: League with championship standings but no other data // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Championship League', description: 'A league with championship standings', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain championship standings expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should retrieve league detail with social links but no team affiliation', async () => { // Scenario: League with social links but no team affiliation // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Social Links League', description: 'A league with social links', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain social links expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should retrieve league detail with team affiliation but no social links', async () => { // Scenario: League with team affiliation but no social links // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Team Affiliation League', description: 'A league with team affiliation', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain team affiliation expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); }); describe('GetLeagueDetailUseCase - Edge Cases', () => { it('should handle league with no career history', async () => { // Scenario: League with no career history // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'No Career History League', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain league profile expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should handle league with no recent race results', async () => { // Scenario: League with no recent race results // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'No Recent Results League', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain league profile expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should handle league with no championship standings', async () => { // Scenario: League with no championship standings // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'No Championship League', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain league profile expect(result).toBeDefined(); expect(result.id).toBe(league.id); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); it('should handle league with no data at all', async () => { // Scenario: League with absolutely no data // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'No Data League', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called with league ID const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: The result should contain basic league info expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('No Data League'); // And: EventPublisher should emit LeagueAccessedEvent expect(eventPublisher.getLeagueAccessedEventCount()).toBe(1); }); }); describe('GetLeagueDetailUseCase - Error Handling', () => { it('should throw error when league does not exist', async () => { // Scenario: Non-existent league // Given: No league exists with the given ID const nonExistentLeagueId = 'non-existent-league-id'; // When: GetLeagueUseCase.execute() is called with non-existent league ID // Then: Should throw error await expect(getLeagueUseCase.execute({ leagueId: nonExistentLeagueId, driverId: 'driver-123' })) .rejects.toThrow(); // And: EventPublisher should NOT emit any events expect(eventPublisher.getLeagueAccessedEventCount()).toBe(0); }); it('should throw error when league ID is invalid', async () => { // Scenario: Invalid league ID // Given: An invalid league ID (e.g., empty string) const invalidLeagueId = ''; // When: GetLeagueUseCase.execute() is called with invalid league ID // Then: Should throw error await expect(getLeagueUseCase.execute({ leagueId: invalidLeagueId, driverId: 'driver-123' })) .rejects.toThrow(); // And: EventPublisher should NOT emit any events expect(eventPublisher.getLeagueAccessedEventCount()).toBe(0); }); it('should handle repository errors gracefully', async () => { // Scenario: Repository throws error // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Test League', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // And: LeagueRepository throws an error during query const originalFindById = leagueRepository.findById; leagueRepository.findById = async () => { throw new Error('Repository error'); }; // When: GetLeagueUseCase.execute() is called // Then: Should propagate the error appropriately await expect(getLeagueUseCase.execute({ leagueId: league.id, driverId })) .rejects.toThrow('Repository error'); // And: EventPublisher should NOT emit any events expect(eventPublisher.getLeagueAccessedEventCount()).toBe(0); // Restore original method leagueRepository.findById = originalFindById; }); }); describe('League Detail Data Orchestration', () => { it('should correctly calculate league statistics from race results', async () => { // Scenario: League statistics calculation // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Statistics League', description: 'A league for statistics calculation', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: League statistics should show: expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Statistics League'); }); it('should correctly format career history with league and team information', async () => { // Scenario: Career history formatting // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Career History League', description: 'A league for career history formatting', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: Career history should show: expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Career History League'); }); it('should correctly format recent race results with proper details', async () => { // Scenario: Recent race results formatting // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Recent Results League', description: 'A league for recent results formatting', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: Recent race results should show: expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Recent Results League'); }); it('should correctly aggregate championship standings across leagues', async () => { // Scenario: Championship standings aggregation // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Championship League', description: 'A league for championship standings', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: Championship standings should show: expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Championship League'); }); it('should correctly format social links with proper URLs', async () => { // Scenario: Social links formatting // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Social Links League', description: 'A league for social links formatting', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: Social links should show: expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Social Links League'); }); it('should correctly format team affiliation with role', async () => { // Scenario: Team affiliation formatting // Given: A league exists const driverId = 'driver-123'; const league = await createLeagueUseCase.execute({ name: 'Team Affiliation League', description: 'A league for team affiliation formatting', visibility: 'public', ownerId: driverId, approvalRequired: false, lateJoinAllowed: false, bonusPointsEnabled: false, penaltiesEnabled: false, protestsEnabled: false, appealsEnabled: false, }); // When: GetLeagueUseCase.execute() is called const result = await getLeagueUseCase.execute({ leagueId: league.id, driverId }); // Then: Team affiliation should show: expect(result).toBeDefined(); expect(result.id).toBe(league.id); expect(result.name).toBe('Team Affiliation League'); }); }); });