import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; import { ApproveLeagueJoinRequestUseCase, } from './ApproveLeagueJoinRequestUseCase'; import { League } from '../../domain/entities/League'; import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository'; import type { LeagueRepository } from '../../domain/repositories/LeagueRepository'; describe('ApproveLeagueJoinRequestUseCase', () => { let mockLeagueMembershipRepo: { getJoinRequests: Mock; removeJoinRequest: Mock; saveMembership: Mock; getLeagueMembers: Mock; }; let mockLeagueRepo: { findById: Mock; }; beforeEach(() => { mockLeagueMembershipRepo = { getJoinRequests: vi.fn(), removeJoinRequest: vi.fn(), saveMembership: vi.fn(), getLeagueMembers: vi.fn(), }; mockLeagueRepo = { findById: vi.fn(), }; }); it('approve removes request and adds member', async () => { const useCase = new ApproveLeagueJoinRequestUseCase(mockLeagueMembershipRepo as unknown as LeagueMembershipRepository, mockLeagueRepo as unknown as LeagueRepository); const leagueId = 'league-1'; const joinRequestId = 'req-1'; const joinRequests = [{ id: joinRequestId, leagueId, driverId: 'driver-1', requestedAt: new Date(), message: 'msg' }]; mockLeagueRepo.findById.mockResolvedValue( League.create({ id: leagueId, name: 'L', description: 'D', ownerId: 'owner-1', settings: { maxDrivers: 32, visibility: 'unranked' }, participantCount: 0, }), ); mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([]); mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue(joinRequests); const result = await useCase.execute( { leagueId, joinRequestId }, ); expect(result.isOk()).toBe(true); expect(result.unwrap()).toEqual({ success: true, message: expect.any(String) }); expect(mockLeagueMembershipRepo.removeJoinRequest).toHaveBeenCalledWith(joinRequestId); expect(mockLeagueMembershipRepo.saveMembership).toHaveBeenCalledTimes(1); const savedMembership = (mockLeagueMembershipRepo.saveMembership as Mock).mock.calls[0]?.[0] as unknown as { id: string; leagueId: { toString(): string }; driverId: { toString(): string }; role: { toString(): string }; status: { toString(): string }; joinedAt: { toDate(): Date }; }; expect(savedMembership.id).toEqual(expect.any(String)); expect(savedMembership.leagueId.toString()).toBe('league-1'); expect(savedMembership.driverId.toString()).toBe('driver-1'); expect(savedMembership.role.toString()).toBe('member'); expect(savedMembership.status.toString()).toBe('active'); expect(savedMembership.joinedAt.toDate()).toBeInstanceOf(Date); }); it('approve returns error when request missing', async () => { const useCase = new ApproveLeagueJoinRequestUseCase(mockLeagueMembershipRepo as unknown as LeagueMembershipRepository, mockLeagueRepo as unknown as LeagueRepository); mockLeagueRepo.findById.mockResolvedValue( League.create({ id: 'league-1', name: 'L', description: 'D', ownerId: 'owner-1', settings: { maxDrivers: 32, visibility: 'unranked' }, participantCount: 0, }), ); mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([]); mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue([]); const result = await useCase.execute( { leagueId: 'league-1', joinRequestId: 'req-1' }, ); expect(result.isOk()).toBe(false); expect(result.error!.code).toBe('JOIN_REQUEST_NOT_FOUND'); expect(mockLeagueMembershipRepo.removeJoinRequest).not.toHaveBeenCalled(); expect(mockLeagueMembershipRepo.saveMembership).not.toHaveBeenCalled(); }); it('rejects approval when league is at capacity and does not mutate state', async () => { const useCase = new ApproveLeagueJoinRequestUseCase(mockLeagueMembershipRepo as unknown as LeagueMembershipRepository, mockLeagueRepo as unknown as LeagueRepository); const leagueId = 'league-1'; const joinRequestId = 'req-1'; const joinRequests = [{ id: joinRequestId, leagueId, driverId: 'driver-2', requestedAt: new Date(), message: 'msg' }]; mockLeagueRepo.findById.mockResolvedValue( League.create({ id: leagueId, name: 'L', description: 'D', ownerId: 'owner-1', settings: { maxDrivers: 2, visibility: 'unranked' }, participantCount: 2, }), ); mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([ { id: `${leagueId}:owner-1`, leagueId: { toString: () => leagueId }, driverId: { toString: () => 'owner-1' }, role: { toString: () => 'owner' }, status: { toString: () => 'active' }, joinedAt: { toDate: () => new Date() }, }, { id: `${leagueId}:driver-1`, leagueId: { toString: () => leagueId }, driverId: { toString: () => 'driver-1' }, role: { toString: () => 'member' }, status: { toString: () => 'active' }, joinedAt: { toDate: () => new Date() }, }, ]); mockLeagueMembershipRepo.getJoinRequests.mockResolvedValue(joinRequests); const result = await useCase.execute( { leagueId, joinRequestId }, ); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('LEAGUE_AT_CAPACITY'); expect(mockLeagueMembershipRepo.removeJoinRequest).not.toHaveBeenCalled(); expect(mockLeagueMembershipRepo.saveMembership).not.toHaveBeenCalled(); }); });