/** * Integration Test: Database Constraints and Error Mapping * * Tests that the application properly handles and maps database constraint violations * using In-Memory adapters for fast, deterministic testing. * * Focus: Business logic orchestration, NOT API endpoints */ import { describe, it, expect, beforeEach } from 'vitest'; // Mock data types that match what the use cases expect interface DriverData { id: string; iracingId: string; name: string; country: string; bio?: string; joinedAt: Date; category?: string; } interface TeamData { id: string; name: string; tag: string; description: string; ownerId: string; leagues: string[]; category?: string; isRecruiting: boolean; createdAt: Date; } interface TeamMembership { teamId: string; driverId: string; role: 'owner' | 'manager' | 'driver'; status: 'active' | 'pending' | 'none'; joinedAt: Date; } // Simple in-memory repositories for testing class TestDriverRepository { private drivers = new Map(); async findById(id: string): Promise { return this.drivers.get(id) || null; } async create(driver: DriverData): Promise { if (this.drivers.has(driver.id)) { throw new Error('Driver already exists'); } this.drivers.set(driver.id, driver); return driver; } clear(): void { this.drivers.clear(); } } class TestTeamRepository { private teams = new Map(); async findById(id: string): Promise { return this.teams.get(id) || null; } async create(team: TeamData): Promise { // Check for duplicate team name/tag for (const existing of this.teams.values()) { if (existing.name === team.name && existing.tag === team.tag) { const error: any = new Error('Team already exists'); error.code = 'DUPLICATE_TEAM'; throw error; } } this.teams.set(team.id, team); return team; } async findAll(): Promise { return Array.from(this.teams.values()); } clear(): void { this.teams.clear(); } } class TestTeamMembershipRepository { private memberships = new Map(); async getMembership(teamId: string, driverId: string): Promise { const teamMemberships = this.memberships.get(teamId) || []; return teamMemberships.find(m => m.driverId === driverId) || null; } async getActiveMembershipForDriver(driverId: string): Promise { for (const teamMemberships of this.memberships.values()) { const active = teamMemberships.find(m => m.driverId === driverId && m.status === 'active'); if (active) return active; } return null; } async saveMembership(membership: TeamMembership): Promise { const teamMemberships = this.memberships.get(membership.teamId) || []; const existingIndex = teamMemberships.findIndex( m => m.driverId === membership.driverId ); if (existingIndex >= 0) { // Check if already active const existing = teamMemberships[existingIndex]; if (existing.status === 'active') { const error: any = new Error('Already a member'); error.code = 'ALREADY_MEMBER'; throw error; } teamMemberships[existingIndex] = membership; } else { teamMemberships.push(membership); } this.memberships.set(membership.teamId, teamMemberships); return membership; } clear(): void { this.memberships.clear(); } } // Mock use case implementations class CreateTeamUseCase { constructor( private teamRepository: TestTeamRepository, private membershipRepository: TestTeamMembershipRepository ) {} async execute(input: { name: string; tag: string; description: string; ownerId: string; leagues: string[]; }): Promise<{ isOk: () => boolean; isErr: () => boolean; error?: any }> { try { // Check if driver already belongs to a team const existingMembership = await this.membershipRepository.getActiveMembershipForDriver(input.ownerId); if (existingMembership) { return { isOk: () => false, isErr: () => true, error: { code: 'VALIDATION_ERROR', details: { message: 'Driver already belongs to a team' } } }; } const teamId = `team-${Date.now()}`; const team: TeamData = { id: teamId, name: input.name, tag: input.tag, description: input.description, ownerId: input.ownerId, leagues: input.leagues, isRecruiting: false, createdAt: new Date(), }; await this.teamRepository.create(team); // Create owner membership const membership: TeamMembership = { teamId: team.id, driverId: input.ownerId, role: 'owner', status: 'active', joinedAt: new Date(), }; await this.membershipRepository.saveMembership(membership); return { isOk: () => true, isErr: () => false, }; } catch (error: any) { return { isOk: () => false, isErr: () => true, error: { code: error.code || 'REPOSITORY_ERROR', details: { message: error.message } } }; } } } class JoinTeamUseCase { constructor( private teamRepository: TestTeamRepository, private membershipRepository: TestTeamMembershipRepository ) {} async execute(input: { teamId: string; driverId: string; }): Promise<{ isOk: () => boolean; isErr: () => boolean; error?: any }> { try { // Check if driver already belongs to a team const existingActive = await this.membershipRepository.getActiveMembershipForDriver(input.driverId); if (existingActive) { return { isOk: () => false, isErr: () => true, error: { code: 'ALREADY_IN_TEAM', details: { message: 'Driver already belongs to a team' } } }; } // Check if already has membership (pending or active) const existingMembership = await this.membershipRepository.getMembership(input.teamId, input.driverId); if (existingMembership) { return { isOk: () => false, isErr: () => true, error: { code: 'ALREADY_MEMBER', details: { message: 'Already a member or have a pending request' } } }; } // Check if team exists const team = await this.teamRepository.findById(input.teamId); if (!team) { return { isOk: () => false, isErr: () => true, error: { code: 'TEAM_NOT_FOUND', details: { message: 'Team not found' } } }; } // Check if driver exists // Note: In real implementation, this would check driver repository // For this test, we'll assume driver exists if we got this far const membership: TeamMembership = { teamId: input.teamId, driverId: input.driverId, role: 'driver', status: 'active', joinedAt: new Date(), }; await this.membershipRepository.saveMembership(membership); return { isOk: () => true, isErr: () => false, }; } catch (error: any) { return { isOk: () => false, isErr: () => true, error: { code: error.code || 'REPOSITORY_ERROR', details: { message: error.message } } }; } } } describe('Database Constraints - Use Case Integration', () => { let driverRepository: TestDriverRepository; let teamRepository: TestTeamRepository; let teamMembershipRepository: TestTeamMembershipRepository; let createTeamUseCase: CreateTeamUseCase; let joinTeamUseCase: JoinTeamUseCase; beforeEach(() => { driverRepository = new TestDriverRepository(); teamRepository = new TestTeamRepository(); teamMembershipRepository = new TestTeamMembershipRepository(); createTeamUseCase = new CreateTeamUseCase(teamRepository, teamMembershipRepository); joinTeamUseCase = new JoinTeamUseCase(teamRepository, teamMembershipRepository); }); describe('Unique Constraint Violations', () => { it('should handle duplicate team creation gracefully', async () => { // Given: A driver exists const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); // And: A team is created successfully const teamResult1 = await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: driver.id, leagues: [], }); expect(teamResult1.isOk()).toBe(true); // When: Attempt to create the same team again (same name/tag) const teamResult2 = await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'Another test team', ownerId: driver.id, leagues: [], }); // Then: Should fail with appropriate error expect(teamResult2.isErr()).toBe(true); if (teamResult2.isErr()) { expect(teamResult2.error.code).toBe('DUPLICATE_TEAM'); } }); it('should handle duplicate membership gracefully', async () => { // Given: A driver and team exist const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); const team: TeamData = { id: 'team-123', name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: 'other-driver', leagues: [], isRecruiting: false, createdAt: new Date(), }; await teamRepository.create(team); // And: Driver joins the team successfully const joinResult1 = await joinTeamUseCase.execute({ teamId: team.id, driverId: driver.id, }); expect(joinResult1.isOk()).toBe(true); // When: Driver attempts to join the same team again const joinResult2 = await joinTeamUseCase.execute({ teamId: team.id, driverId: driver.id, }); // Then: Should fail with appropriate error expect(joinResult2.isErr()).toBe(true); if (joinResult2.isErr()) { expect(joinResult2.error.code).toBe('ALREADY_MEMBER'); } }); }); describe('Foreign Key Constraint Violations', () => { it('should handle non-existent driver in team creation', async () => { // Given: No driver exists with the given ID // When: Attempt to create a team with non-existent owner const result = await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: 'non-existent-driver', leagues: [], }); // Then: Should fail with appropriate error expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.code).toBe('VALIDATION_ERROR'); } }); it('should handle non-existent team in join request', async () => { // Given: A driver exists const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); // When: Attempt to join non-existent team const result = await joinTeamUseCase.execute({ teamId: 'non-existent-team', driverId: driver.id, }); // Then: Should fail with appropriate error expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.code).toBe('TEAM_NOT_FOUND'); } }); }); describe('Data Integrity After Failed Operations', () => { it('should maintain repository state after constraint violations', async () => { // Given: A driver exists const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); // And: A valid team is created const validTeamResult = await createTeamUseCase.execute({ name: 'Valid Team', tag: 'VT', description: 'Valid team', ownerId: driver.id, leagues: [], }); expect(validTeamResult.isOk()).toBe(true); // When: Attempt to create duplicate team (should fail) const duplicateResult = await createTeamUseCase.execute({ name: 'Valid Team', tag: 'VT', description: 'Duplicate team', ownerId: driver.id, leagues: [], }); expect(duplicateResult.isErr()).toBe(true); // Then: Original team should still exist and be retrievable const teams = await teamRepository.findAll(); expect(teams.length).toBe(1); expect(teams[0].name).toBe('Valid Team'); }); it('should handle multiple failed operations without corruption', async () => { // Given: A driver and team exist const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); const team: TeamData = { id: 'team-123', name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: 'other-driver', leagues: [], isRecruiting: false, createdAt: new Date(), }; await teamRepository.create(team); // When: Multiple failed operations occur await joinTeamUseCase.execute({ teamId: 'non-existent', driverId: driver.id }); await joinTeamUseCase.execute({ teamId: team.id, driverId: 'non-existent' }); await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'Duplicate', ownerId: driver.id, leagues: [] }); // Then: Repositories should remain in valid state const drivers = await driverRepository.findById(driver.id); const teams = await teamRepository.findAll(); const membership = await teamMembershipRepository.getMembership(team.id, driver.id); expect(drivers).not.toBeNull(); expect(teams.length).toBe(1); expect(membership).toBeNull(); // No successful joins }); }); describe('Concurrent Operations', () => { it('should handle concurrent team creation attempts safely', async () => { // Given: A driver exists const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); // When: Multiple concurrent attempts to create teams with same name const concurrentRequests = Array(5).fill(null).map((_, i) => createTeamUseCase.execute({ name: 'Concurrent Team', tag: `CT${i}`, description: 'Concurrent creation', ownerId: driver.id, leagues: [], }) ); const results = await Promise.all(concurrentRequests); // Then: Exactly one should succeed, others should fail const successes = results.filter(r => r.isOk()); const failures = results.filter(r => r.isErr()); expect(successes.length).toBe(1); expect(failures.length).toBe(4); // All failures should be duplicate errors failures.forEach(result => { if (result.isErr()) { expect(result.error.code).toBe('DUPLICATE_TEAM'); } }); }); it('should handle concurrent join requests safely', async () => { // Given: A driver and team exist const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); const team: TeamData = { id: 'team-123', name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: 'other-driver', leagues: [], isRecruiting: false, createdAt: new Date(), }; await teamRepository.create(team); // When: Multiple concurrent join attempts const concurrentJoins = Array(3).fill(null).map(() => joinTeamUseCase.execute({ teamId: team.id, driverId: driver.id, }) ); const results = await Promise.all(concurrentJoins); // Then: Exactly one should succeed const successes = results.filter(r => r.isOk()); const failures = results.filter(r => r.isErr()); expect(successes.length).toBe(1); expect(failures.length).toBe(2); // All failures should be already member errors failures.forEach(result => { if (result.isErr()) { expect(result.error.code).toBe('ALREADY_MEMBER'); } }); }); }); describe('Error Mapping and Reporting', () => { it('should provide meaningful error messages for constraint violations', async () => { // Given: A driver exists const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); // And: A team is created await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'Test', ownerId: driver.id, leagues: [], }); // When: Attempt to create duplicate const result = await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'Duplicate', ownerId: driver.id, leagues: [], }); // Then: Error should have clear message expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.details.message).toContain('already exists'); expect(result.error.details.message).toContain('Test Team'); } }); it('should handle repository errors gracefully', async () => { // Given: A driver exists const driver: DriverData = { id: 'driver-123', iracingId: '12345', name: 'Test Driver', country: 'US', joinedAt: new Date(), }; await driverRepository.create(driver); // When: Repository throws an error (simulated by using invalid data) // Note: In real scenario, this would be a database error // For this test, we'll verify the error handling path works const result = await createTeamUseCase.execute({ name: '', // Invalid - empty name tag: 'TT', description: 'Test', ownerId: driver.id, leagues: [], }); // Then: Should handle validation error expect(result.isErr()).toBe(true); }); }); });