import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; import { JoinLeagueUseCase } from '@core/racing/application/use-cases/JoinLeagueUseCase'; import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository'; import type { Logger } from '@core/shared/application'; import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership'; import type { JoinRequest } from '@core/racing/domain/entities/LeagueMembership'; class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepository { private memberships: LeagueMembership[] = []; private joinRequests: JoinRequest[] = []; async getMembership(leagueId: string, driverId: string): Promise { return ( this.memberships.find( (m) => m.leagueId === leagueId && m.driverId === driverId, ) || null ); } async getLeagueMembers(leagueId: string): Promise { return this.memberships.filter( (m) => m.leagueId === leagueId && m.status === 'active', ); } async getJoinRequests(leagueId: string): Promise { return this.joinRequests.filter( (r) => r.leagueId === leagueId, ); } 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 saveJoinRequest(request: JoinRequest): Promise { this.joinRequests.push(request); return request; } async removeJoinRequest(requestId: string): Promise { this.joinRequests = this.joinRequests.filter( (r) => r.id !== requestId, ); } 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; let logger: { debug: Mock; info: Mock; warn: Mock; error: Mock; }; beforeEach(() => { repository = new InMemoryLeagueMembershipRepository(); logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; useCase = new JoinLeagueUseCase( repository as unknown as ILeagueMembershipRepository, logger as unknown as Logger, ); }); it('creates an active member when driver has no membership', async () => { const leagueId = 'league-1'; const driverId = 'driver-1'; const result = await useCase.execute({ leagueId, driverId }); expect(result.isOk()).toBe(true); const membership = result.unwrap(); expect(membership.leagueId).toBe(leagueId); expect(membership.driverId).toBe(driverId); expect(membership.role).toBe('member'); expect(membership.status).toBe('active'); expect(membership.joinedAt).toBeInstanceOf(Date); }); it('returns error when driver already has membership for league', async () => { const leagueId = 'league-1'; const driverId = 'driver-1'; repository.seedMembership(LeagueMembership.create({ leagueId, driverId, role: 'member', status: 'active', joinedAt: new Date('2024-01-01'), })); const result = await useCase.execute({ leagueId, driverId }); expect(result.isErr()).toBe(true); expect(result.unwrapErr()).toEqual({ code: 'ALREADY_MEMBER', details: { message: 'Already a member or have a pending request' }, }); }); }); });