/** * Integration Test: Profile Overview Use Case Orchestration * * Tests the orchestration logic of profile overview-related Use Cases: * - GetProfileOverviewUseCase: Retrieves driver's profile overview with stats, team memberships, and social summary * - UpdateDriverProfileUseCase: Updates driver's profile information * - 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 { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository'; import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository'; import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository'; import { InMemorySocialGraphRepository } from '../../../adapters/social/persistence/inmemory/InMemorySocialAndFeed'; import { InMemoryDriverStatsRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverStatsRepository'; import { InMemoryDriverExtendedProfileProvider } from '../../../adapters/racing/ports/InMemoryDriverExtendedProfileProvider'; import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher'; import { InMemoryResultRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryResultRepository'; import { InMemoryStandingRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryStandingRepository'; import { InMemoryRaceRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryRaceRepository'; import { GetProfileOverviewUseCase } from '../../../core/racing/application/use-cases/GetProfileOverviewUseCase'; import { UpdateDriverProfileUseCase } from '../../../core/racing/application/use-cases/UpdateDriverProfileUseCase'; import { DriverStatsUseCase } from '../../../core/racing/application/use-cases/DriverStatsUseCase'; import { RankingUseCase } from '../../../core/racing/application/use-cases/RankingUseCase'; import { Driver } from '../../../core/racing/domain/entities/Driver'; import { Team } from '../../../core/racing/domain/entities/Team'; import { TeamMembership } from '../../../core/racing/domain/types/TeamMembership'; import { DriverStats } from '../../../core/racing/application/use-cases/DriverStatsUseCase'; import { DriverRanking } from '../../../core/racing/application/use-cases/RankingUseCase'; import { Logger } from '../../../core/shared/domain/Logger'; // Mock logger for testing class MockLogger implements Logger { debug(message: string, ...args: any[]): void {} info(message: string, ...args: any[]): void {} warn(message: string, ...args: any[]): void {} error(message: string, ...args: any[]): void {} } describe('Profile Overview Use Case Orchestration', () => { let driverRepository: InMemoryDriverRepository; let teamRepository: InMemoryTeamRepository; let teamMembershipRepository: InMemoryTeamMembershipRepository; let socialRepository: InMemorySocialGraphRepository; let driverStatsRepository: InMemoryDriverStatsRepository; let driverExtendedProfileProvider: InMemoryDriverExtendedProfileProvider; let eventPublisher: InMemoryEventPublisher; let resultRepository: InMemoryResultRepository; let standingRepository: InMemoryStandingRepository; let raceRepository: InMemoryRaceRepository; let driverStatsUseCase: DriverStatsUseCase; let rankingUseCase: RankingUseCase; let getProfileOverviewUseCase: GetProfileOverviewUseCase; let updateDriverProfileUseCase: UpdateDriverProfileUseCase; let logger: MockLogger; beforeAll(() => { logger = new MockLogger(); driverRepository = new InMemoryDriverRepository(logger); teamRepository = new InMemoryTeamRepository(logger); teamMembershipRepository = new InMemoryTeamMembershipRepository(logger); socialRepository = new InMemorySocialGraphRepository(logger); driverStatsRepository = new InMemoryDriverStatsRepository(logger); driverExtendedProfileProvider = new InMemoryDriverExtendedProfileProvider(logger); eventPublisher = new InMemoryEventPublisher(); resultRepository = new InMemoryResultRepository(logger, raceRepository); standingRepository = new InMemoryStandingRepository(logger, {}, resultRepository, raceRepository); raceRepository = new InMemoryRaceRepository(logger); driverStatsUseCase = new DriverStatsUseCase(resultRepository, standingRepository, driverStatsRepository, logger); rankingUseCase = new RankingUseCase(standingRepository, driverRepository, driverStatsRepository, logger); getProfileOverviewUseCase = new GetProfileOverviewUseCase( driverRepository, teamRepository, teamMembershipRepository, socialRepository, driverExtendedProfileProvider, driverStatsUseCase, rankingUseCase ); updateDriverProfileUseCase = new UpdateDriverProfileUseCase(driverRepository, logger); }); beforeEach(async () => { await driverRepository.clear(); await teamRepository.clear(); await teamMembershipRepository.clear(); await socialRepository.clear(); await driverStatsRepository.clear(); eventPublisher.clear(); }); describe('GetProfileOverviewUseCase - Success Path', () => { it('should retrieve complete profile overview for driver with all data', async () => { // Scenario: Driver with complete profile data // Given: A driver exists with complete personal information const driverId = 'driver-123'; const driver = Driver.create({ id: driverId, iracingId: '12345', name: 'John Doe', country: 'US', bio: 'Professional racing driver with 10 years experience', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has complete statistics const stats: DriverStats = { totalRaces: 50, wins: 15, podiums: 25, dnfs: 5, avgFinish: 8.5, bestFinish: 1, worstFinish: 20, finishRate: 90, winRate: 30, podiumRate: 50, percentile: 85, rating: 1850, consistency: 92, overallRank: 42, }; await driverStatsRepository.saveDriverStats(driverId, stats); // And: The driver is a member of a team const team = Team.create({ id: 'team-1', name: 'Racing Team', tag: 'RT', description: 'Professional racing team', ownerId: 'owner-1', isRecruiting: true, }); await teamRepository.create(team); const membership: TeamMembership = { teamId: 'team-1', driverId: driverId, role: 'Driver', status: 'active', joinedAt: new Date('2024-01-01'), }; await teamMembershipRepository.saveMembership(membership); // And: The driver has friends const friendDriver = Driver.create({ id: 'friend-1', iracingId: '67890', name: 'Jane Smith', country: 'UK', avatarRef: undefined, }); await driverRepository.create(friendDriver); await socialRepository.seed({ drivers: [driver, friendDriver], friendships: [{ driverId: driverId, friendId: 'friend-1' }], feedEvents: [], }); // When: GetProfileOverviewUseCase.execute() is called with driver ID const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should contain all profile sections expect(result.isOk()).toBe(true); const profile = result.unwrap(); // And: Driver info should be complete expect(profile.driverInfo.driver.id).toBe(driverId); expect(profile.driverInfo.driver.name.toString()).toBe('John Doe'); expect(profile.driverInfo.driver.country.toString()).toBe('US'); expect(profile.driverInfo.driver.bio?.toString()).toBe('Professional racing driver with 10 years experience'); expect(profile.driverInfo.totalDrivers).toBeGreaterThan(0); expect(profile.driverInfo.globalRank).toBe(42); expect(profile.driverInfo.consistency).toBe(92); expect(profile.driverInfo.rating).toBe(1850); // And: Stats should be complete expect(profile.stats).not.toBeNull(); expect(profile.stats!.totalRaces).toBe(50); expect(profile.stats!.wins).toBe(15); expect(profile.stats!.podiums).toBe(25); expect(profile.stats!.dnfs).toBe(5); expect(profile.stats!.avgFinish).toBe(8.5); expect(profile.stats!.bestFinish).toBe(1); expect(profile.stats!.worstFinish).toBe(20); expect(profile.stats!.finishRate).toBe(90); expect(profile.stats!.winRate).toBe(30); expect(profile.stats!.podiumRate).toBe(50); expect(profile.stats!.percentile).toBe(85); expect(profile.stats!.rating).toBe(1850); expect(profile.stats!.consistency).toBe(92); expect(profile.stats!.overallRank).toBe(42); // And: Finish distribution should be calculated expect(profile.finishDistribution).not.toBeNull(); expect(profile.finishDistribution!.totalRaces).toBe(50); expect(profile.finishDistribution!.wins).toBe(15); expect(profile.finishDistribution!.podiums).toBe(25); expect(profile.finishDistribution!.dnfs).toBe(5); expect(profile.finishDistribution!.topTen).toBeGreaterThan(0); expect(profile.finishDistribution!.other).toBeGreaterThan(0); // And: Team memberships should be present expect(profile.teamMemberships).toHaveLength(1); expect(profile.teamMemberships[0].team.id).toBe('team-1'); expect(profile.teamMemberships[0].team.name.toString()).toBe('Racing Team'); expect(profile.teamMemberships[0].membership.role).toBe('Driver'); expect(profile.teamMemberships[0].membership.status).toBe('active'); // And: Social summary should show friends expect(profile.socialSummary.friendsCount).toBe(1); expect(profile.socialSummary.friends).toHaveLength(1); expect(profile.socialSummary.friends[0].id).toBe('friend-1'); expect(profile.socialSummary.friends[0].name.toString()).toBe('Jane Smith'); // And: Extended profile should be present (generated by provider) expect(profile.extendedProfile).not.toBeNull(); expect(profile.extendedProfile!.socialHandles).toBeInstanceOf(Array); expect(profile.extendedProfile!.achievements).toBeInstanceOf(Array); }); it('should retrieve profile overview for driver with minimal data', async () => { // Scenario: Driver with minimal profile data // Given: A driver exists with minimal information const driverId = 'driver-456'; const driver = Driver.create({ id: driverId, iracingId: '78901', name: 'New Driver', country: 'DE', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has no statistics // And: The driver is not a member of any team // And: The driver has no friends // When: GetProfileOverviewUseCase.execute() is called with driver ID const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should contain basic driver info expect(result.isOk()).toBe(true); const profile = result.unwrap(); // And: Driver info should be present expect(profile.driverInfo.driver.id).toBe(driverId); expect(profile.driverInfo.driver.name.toString()).toBe('New Driver'); expect(profile.driverInfo.driver.country.toString()).toBe('DE'); expect(profile.driverInfo.totalDrivers).toBeGreaterThan(0); // And: Stats should be null (no data) expect(profile.stats).toBeNull(); // And: Finish distribution should be null expect(profile.finishDistribution).toBeNull(); // And: Team memberships should be empty expect(profile.teamMemberships).toHaveLength(0); // And: Social summary should show no friends expect(profile.socialSummary.friendsCount).toBe(0); expect(profile.socialSummary.friends).toHaveLength(0); // And: Extended profile should be present (generated by provider) expect(profile.extendedProfile).not.toBeNull(); }); it('should retrieve profile overview with multiple team memberships', async () => { // Scenario: Driver with multiple team memberships // Given: A driver exists const driverId = 'driver-789'; const driver = Driver.create({ id: driverId, iracingId: '11111', name: 'Multi Team Driver', country: 'FR', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver is a member of multiple teams const team1 = Team.create({ id: 'team-1', name: 'Team A', tag: 'TA', description: 'Team A', ownerId: 'owner-1', isRecruiting: true, }); await teamRepository.create(team1); const team2 = Team.create({ id: 'team-2', name: 'Team B', tag: 'TB', description: 'Team B', ownerId: 'owner-2', isRecruiting: false, }); await teamRepository.create(team2); const membership1: TeamMembership = { teamId: 'team-1', driverId: driverId, role: 'Driver', status: 'active', joinedAt: new Date('2024-01-01'), }; await teamMembershipRepository.saveMembership(membership1); const membership2: TeamMembership = { teamId: 'team-2', driverId: driverId, role: 'Admin', status: 'active', joinedAt: new Date('2024-02-01'), }; await teamMembershipRepository.saveMembership(membership2); // When: GetProfileOverviewUseCase.execute() is called with driver ID const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should contain all team memberships expect(result.isOk()).toBe(true); const profile = result.unwrap(); // And: Team memberships should include both teams expect(profile.teamMemberships).toHaveLength(2); expect(profile.teamMemberships[0].team.id).toBe('team-1'); expect(profile.teamMemberships[0].membership.role).toBe('Driver'); expect(profile.teamMemberships[1].team.id).toBe('team-2'); expect(profile.teamMemberships[1].membership.role).toBe('Admin'); // And: Team memberships should be sorted by joined date expect(profile.teamMemberships[0].membership.joinedAt.getTime()).toBeLessThan( profile.teamMemberships[1].membership.joinedAt.getTime() ); }); it('should retrieve profile overview with multiple friends', async () => { // Scenario: Driver with multiple friends // Given: A driver exists const driverId = 'driver-friends'; const driver = Driver.create({ id: driverId, iracingId: '22222', name: 'Social Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has multiple friends const friend1 = Driver.create({ id: 'friend-1', iracingId: '33333', name: 'Friend 1', country: 'US', avatarRef: undefined, }); await driverRepository.create(friend1); const friend2 = Driver.create({ id: 'friend-2', iracingId: '44444', name: 'Friend 2', country: 'UK', avatarRef: undefined, }); await driverRepository.create(friend2); const friend3 = Driver.create({ id: 'friend-3', iracingId: '55555', name: 'Friend 3', country: 'DE', avatarRef: undefined, }); await driverRepository.create(friend3); await socialRepository.seed({ drivers: [driver, friend1, friend2, friend3], friendships: [ { driverId: driverId, friendId: 'friend-1' }, { driverId: driverId, friendId: 'friend-2' }, { driverId: driverId, friendId: 'friend-3' }, ], feedEvents: [], }); // When: GetProfileOverviewUseCase.execute() is called with driver ID const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should contain all friends expect(result.isOk()).toBe(true); const profile = result.unwrap(); // And: Social summary should show 3 friends expect(profile.socialSummary.friendsCount).toBe(3); expect(profile.socialSummary.friends).toHaveLength(3); // And: All friends should be present const friendIds = profile.socialSummary.friends.map(f => f.id); expect(friendIds).toContain('friend-1'); expect(friendIds).toContain('friend-2'); expect(friendIds).toContain('friend-3'); }); }); describe('GetProfileOverviewUseCase - Edge Cases', () => { it('should handle driver with no statistics', async () => { // Scenario: Driver without statistics // Given: A driver exists const driverId = 'driver-no-stats'; const driver = Driver.create({ id: driverId, iracingId: '66666', name: 'No Stats Driver', country: 'CA', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has no statistics // When: GetProfileOverviewUseCase.execute() is called with driver ID const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should contain driver info with null stats expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.driverInfo.driver.id).toBe(driverId); expect(profile.stats).toBeNull(); expect(profile.finishDistribution).toBeNull(); }); it('should handle driver with no team memberships', async () => { // Scenario: Driver without team memberships // Given: A driver exists const driverId = 'driver-no-teams'; const driver = Driver.create({ id: driverId, iracingId: '77777', name: 'Solo Driver', country: 'IT', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver is not a member of any team // When: GetProfileOverviewUseCase.execute() is called with driver ID const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should contain driver info with empty team memberships expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.driverInfo.driver.id).toBe(driverId); expect(profile.teamMemberships).toHaveLength(0); }); it('should handle driver with no friends', async () => { // Scenario: Driver without friends // Given: A driver exists const driverId = 'driver-no-friends'; const driver = Driver.create({ id: driverId, iracingId: '88888', name: 'Lonely Driver', country: 'ES', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has no friends // When: GetProfileOverviewUseCase.execute() is called with driver ID const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should contain driver info with empty social summary expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.driverInfo.driver.id).toBe(driverId); expect(profile.socialSummary.friendsCount).toBe(0); expect(profile.socialSummary.friends).toHaveLength(0); }); }); describe('GetProfileOverviewUseCase - Error Handling', () => { it('should return error when driver does not exist', async () => { // Scenario: Non-existent driver // Given: No driver exists with the given ID const nonExistentDriverId = 'non-existent-driver'; // When: GetProfileOverviewUseCase.execute() is called with non-existent driver ID const result = await getProfileOverviewUseCase.execute({ driverId: nonExistentDriverId }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.code).toBe('DRIVER_NOT_FOUND'); expect(error.details.message).toBe('Driver not found'); }); it('should return error when driver ID is invalid', async () => { // Scenario: Invalid driver ID // Given: An invalid driver ID (empty string) const invalidDriverId = ''; // When: GetProfileOverviewUseCase.execute() is called with invalid driver ID const result = await getProfileOverviewUseCase.execute({ driverId: invalidDriverId }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.code).toBe('DRIVER_NOT_FOUND'); expect(error.details.message).toBe('Driver not found'); }); }); describe('UpdateDriverProfileUseCase - Success Path', () => { it('should update driver bio', async () => { // Scenario: Update driver bio // Given: A driver exists with bio const driverId = 'driver-update-bio'; const driver = Driver.create({ id: driverId, iracingId: '99999', name: 'Update Driver', country: 'US', bio: 'Original bio', avatarRef: undefined, }); await driverRepository.create(driver); // When: UpdateDriverProfileUseCase.execute() is called with new bio const result = await updateDriverProfileUseCase.execute({ driverId, bio: 'Updated bio', }); // Then: The operation should succeed expect(result.isOk()).toBe(true); // And: The driver's bio should be updated const updatedDriver = await driverRepository.findById(driverId); expect(updatedDriver).not.toBeNull(); expect(updatedDriver!.bio?.toString()).toBe('Updated bio'); }); it('should update driver country', async () => { // Scenario: Update driver country // Given: A driver exists with country const driverId = 'driver-update-country'; const driver = Driver.create({ id: driverId, iracingId: '10101', name: 'Country Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // When: UpdateDriverProfileUseCase.execute() is called with new country const result = await updateDriverProfileUseCase.execute({ driverId, country: 'DE', }); // Then: The operation should succeed expect(result.isOk()).toBe(true); // And: The driver's country should be updated const updatedDriver = await driverRepository.findById(driverId); expect(updatedDriver).not.toBeNull(); expect(updatedDriver!.country.toString()).toBe('DE'); }); it('should update multiple profile fields at once', async () => { // Scenario: Update multiple fields // Given: A driver exists const driverId = 'driver-update-multiple'; const driver = Driver.create({ id: driverId, iracingId: '11111', name: 'Multi Update Driver', country: 'US', bio: 'Original bio', avatarRef: undefined, }); await driverRepository.create(driver); // When: UpdateDriverProfileUseCase.execute() is called with multiple updates const result = await updateDriverProfileUseCase.execute({ driverId, bio: 'Updated bio', country: 'FR', }); // Then: The operation should succeed expect(result.isOk()).toBe(true); // And: Both fields should be updated const updatedDriver = await driverRepository.findById(driverId); expect(updatedDriver).not.toBeNull(); expect(updatedDriver!.bio?.toString()).toBe('Updated bio'); expect(updatedDriver!.country.toString()).toBe('FR'); }); }); describe('UpdateDriverProfileUseCase - Validation', () => { it('should reject update with empty bio', async () => { // Scenario: Empty bio // Given: A driver exists const driverId = 'driver-empty-bio'; const driver = Driver.create({ id: driverId, iracingId: '12121', name: 'Empty Bio Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // When: UpdateDriverProfileUseCase.execute() is called with empty bio const result = await updateDriverProfileUseCase.execute({ driverId, bio: '', }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.code).toBe('INVALID_PROFILE_DATA'); expect(error.details.message).toBe('Profile data is invalid'); }); it('should reject update with empty country', async () => { // Scenario: Empty country // Given: A driver exists const driverId = 'driver-empty-country'; const driver = Driver.create({ id: driverId, iracingId: '13131', name: 'Empty Country Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // When: UpdateDriverProfileUseCase.execute() is called with empty country const result = await updateDriverProfileUseCase.execute({ driverId, country: '', }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.code).toBe('INVALID_PROFILE_DATA'); expect(error.details.message).toBe('Profile data is invalid'); }); }); describe('UpdateDriverProfileUseCase - Error Handling', () => { it('should return error when driver does not exist', async () => { // Scenario: Non-existent driver // Given: No driver exists with the given ID const nonExistentDriverId = 'non-existent-driver'; // When: UpdateDriverProfileUseCase.execute() is called with non-existent driver ID const result = await updateDriverProfileUseCase.execute({ driverId: nonExistentDriverId, bio: 'New bio', }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.code).toBe('DRIVER_NOT_FOUND'); expect(error.details.message).toContain('Driver with id'); }); it('should return error when driver ID is invalid', async () => { // Scenario: Invalid driver ID // Given: An invalid driver ID (empty string) const invalidDriverId = ''; // When: UpdateDriverProfileUseCase.execute() is called with invalid driver ID const result = await updateDriverProfileUseCase.execute({ driverId: invalidDriverId, bio: 'New bio', }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.getError(); expect(error.code).toBe('DRIVER_NOT_FOUND'); expect(error.details.message).toContain('Driver with id'); }); }); describe('Profile Data Orchestration', () => { it('should correctly calculate win percentage from race results', async () => { // Scenario: Win percentage calculation // Given: A driver exists const driverId = 'driver-win-percentage'; const driver = Driver.create({ id: driverId, iracingId: '14141', name: 'Win Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has 10 race starts and 3 wins const stats: DriverStats = { totalRaces: 10, wins: 3, podiums: 5, dnfs: 0, avgFinish: 5.0, bestFinish: 1, worstFinish: 10, finishRate: 100, winRate: 30, podiumRate: 50, percentile: 70, rating: 1600, consistency: 85, overallRank: 100, }; await driverStatsRepository.saveDriverStats(driverId, stats); // When: GetProfileOverviewUseCase.execute() is called const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should show win percentage as 30% expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.stats!.winRate).toBe(30); }); it('should correctly calculate podium rate from race results', async () => { // Scenario: Podium rate calculation // Given: A driver exists const driverId = 'driver-podium-rate'; const driver = Driver.create({ id: driverId, iracingId: '15151', name: 'Podium Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has 10 race starts and 5 podiums const stats: DriverStats = { totalRaces: 10, wins: 2, podiums: 5, dnfs: 0, avgFinish: 4.0, bestFinish: 1, worstFinish: 8, finishRate: 100, winRate: 20, podiumRate: 50, percentile: 60, rating: 1550, consistency: 80, overallRank: 150, }; await driverStatsRepository.saveDriverStats(driverId, stats); // When: GetProfileOverviewUseCase.execute() is called const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should show podium rate as 50% expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.stats!.podiumRate).toBe(50); }); it('should correctly calculate finish distribution', async () => { // Scenario: Finish distribution calculation // Given: A driver exists const driverId = 'driver-finish-dist'; const driver = Driver.create({ id: driverId, iracingId: '16161', name: 'Finish Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has 20 race starts with various finishes const stats: DriverStats = { totalRaces: 20, wins: 5, podiums: 8, dnfs: 2, avgFinish: 6.5, bestFinish: 1, worstFinish: 15, finishRate: 90, winRate: 25, podiumRate: 40, percentile: 75, rating: 1700, consistency: 88, overallRank: 75, }; await driverStatsRepository.saveDriverStats(driverId, stats); // When: GetProfileOverviewUseCase.execute() is called const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: The result should show correct finish distribution expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.finishDistribution!.totalRaces).toBe(20); expect(profile.finishDistribution!.wins).toBe(5); expect(profile.finishDistribution!.podiums).toBe(8); expect(profile.finishDistribution!.dnfs).toBe(2); expect(profile.finishDistribution!.topTen).toBeGreaterThan(0); expect(profile.finishDistribution!.other).toBeGreaterThan(0); }); it('should correctly format team affiliation with role', async () => { // Scenario: Team affiliation formatting // Given: A driver exists const driverId = 'driver-team-affiliation'; const driver = Driver.create({ id: driverId, iracingId: '17171', name: 'Team Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver is affiliated with a team const team = Team.create({ id: 'team-affiliation', name: 'Affiliation Team', tag: 'AT', description: 'Team for testing', ownerId: 'owner-1', isRecruiting: true, }); await teamRepository.create(team); const membership: TeamMembership = { teamId: 'team-affiliation', driverId: driverId, role: 'Driver', status: 'active', joinedAt: new Date('2024-01-01'), }; await teamMembershipRepository.saveMembership(membership); // When: GetProfileOverviewUseCase.execute() is called const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: Team affiliation should show team name and role expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.teamMemberships).toHaveLength(1); expect(profile.teamMemberships[0].team.name.toString()).toBe('Affiliation Team'); expect(profile.teamMemberships[0].membership.role).toBe('Driver'); }); it('should correctly identify driver role in each team', async () => { // Scenario: Driver role identification // Given: A driver exists const driverId = 'driver-roles'; const driver = Driver.create({ id: driverId, iracingId: '18181', name: 'Role Driver', country: 'US', avatarRef: undefined, }); await driverRepository.create(driver); // And: The driver has different roles in different teams const team1 = Team.create({ id: 'team-role-1', name: 'Team A', tag: 'TA', description: 'Team A', ownerId: 'owner-1', isRecruiting: true, }); await teamRepository.create(team1); const team2 = Team.create({ id: 'team-role-2', name: 'Team B', tag: 'TB', description: 'Team B', ownerId: 'owner-2', isRecruiting: false, }); await teamRepository.create(team2); const team3 = Team.create({ id: 'team-role-3', name: 'Team C', tag: 'TC', description: 'Team C', ownerId: driverId, isRecruiting: true, }); await teamRepository.create(team3); const membership1: TeamMembership = { teamId: 'team-role-1', driverId: driverId, role: 'Driver', status: 'active', joinedAt: new Date('2024-01-01'), }; await teamMembershipRepository.saveMembership(membership1); const membership2: TeamMembership = { teamId: 'team-role-2', driverId: driverId, role: 'Admin', status: 'active', joinedAt: new Date('2024-02-01'), }; await teamMembershipRepository.saveMembership(membership2); const membership3: TeamMembership = { teamId: 'team-role-3', driverId: driverId, role: 'Owner', status: 'active', joinedAt: new Date('2024-03-01'), }; await teamMembershipRepository.saveMembership(membership3); // When: GetProfileOverviewUseCase.execute() is called const result = await getProfileOverviewUseCase.execute({ driverId }); // Then: Each team should show the correct role expect(result.isOk()).toBe(true); const profile = result.unwrap(); expect(profile.teamMemberships).toHaveLength(3); const teamARole = profile.teamMemberships.find(m => m.team.id === 'team-role-1')?.membership.role; const teamBRole = profile.teamMemberships.find(m => m.team.id === 'team-role-2')?.membership.role; const teamCRole = profile.teamMemberships.find(m => m.team.id === 'team-role-3')?.membership.role; expect(teamARole).toBe('Driver'); expect(teamBRole).toBe('Admin'); expect(teamCRole).toBe('Owner'); }); }); });