import { describe, it, expect, beforeEach } from 'vitest'; import { JoinLeagueUseCase } from '@gridpilot/racing/application/use-cases/JoinLeagueUseCase'; import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository'; import type { LeagueMembership, MembershipRole, MembershipStatus, } from '@gridpilot/racing/domain/entities/LeagueMembership'; class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepository { private memberships: LeagueMembership[] = []; async getMembership(leagueId: string, driverId: string): Promise { return ( this.memberships.find( (m) => m.leagueId === leagueId && m.driverId === driverId, ) || null ); } async getActiveMembershipForDriver(driverId: string): Promise { return ( this.memberships.find( (m) => m.driverId === driverId && m.status === 'active', ) || null ); } async getLeagueMembers(leagueId: string): Promise { return this.memberships.filter( (m) => m.leagueId === leagueId && m.status === 'active', ); } async getTeamMembers(leagueId: string): Promise { return this.memberships.filter( (m) => m.leagueId === leagueId && m.status === 'active', ); } async saveMembership(membership: LeagueMembership): Promise { const existingIndex = this.memberships.findIndex( (m) => m.leagueId === membership.leagueId && m.driverId === membership.driverId, ); if (existingIndex >= 0) { this.memberships[existingIndex] = membership; } else { this.memberships.push(membership); } return membership; } async removeMembership(leagueId: string, driverId: string): Promise { this.memberships = this.memberships.filter( (m) => !(m.leagueId === leagueId && m.driverId === driverId), ); } async getJoinRequests(): Promise { throw new Error('Not implemented for this test'); } async saveJoinRequest(): Promise { throw new Error('Not implemented for this test'); } async removeJoinRequest(): Promise { throw new Error('Not implemented for this test'); } seedMembership(membership: LeagueMembership): void { this.memberships.push(membership); } getAllMemberships(): LeagueMembership[] { return [...this.memberships]; } } describe('Membership use-cases', () => { describe('JoinLeagueUseCase', () => { let repository: InMemoryLeagueMembershipRepository; let useCase: JoinLeagueUseCase; beforeEach(() => { repository = new InMemoryLeagueMembershipRepository(); useCase = new JoinLeagueUseCase(repository); }); it('creates an active member when driver has no membership', async () => { const leagueId = 'league-1'; const driverId = 'driver-1'; await useCase.execute({ leagueId, driverId }); const membership = await repository.getMembership(leagueId, driverId); expect(membership).not.toBeNull(); expect(membership?.leagueId).toBe(leagueId); expect(membership?.driverId).toBe(driverId); expect(membership?.role as MembershipRole).toBe('member'); expect(membership?.status as MembershipStatus).toBe('active'); expect(membership?.joinedAt).toBeInstanceOf(Date); }); it('throws when driver already has membership for league', async () => { const leagueId = 'league-1'; const driverId = 'driver-1'; repository.seedMembership({ leagueId, driverId, role: 'member', status: 'active', joinedAt: new Date('2024-01-01'), }); await expect( useCase.execute({ leagueId, driverId }), ).rejects.toThrow('Already a member or have a pending request'); }); }); });