import type { Logger } from '@core/shared/domain/Logger'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; import type { League } from '../../domain/entities/League'; import type { LeagueMembership } from '../../domain/entities/LeagueMembership'; import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository'; import type { LeagueRepository } from '../../domain/repositories/LeagueRepository'; import { TransferLeagueOwnershipUseCase, type TransferLeagueOwnershipErrorCode, type TransferLeagueOwnershipInput } from './TransferLeagueOwnershipUseCase'; describe('TransferLeagueOwnershipUseCase', () => { let leagueRepository: { findById: Mock; update: Mock; }; let membershipRepository: { getMembership: Mock; saveMembership: Mock; }; let logger: Logger & { error: Mock }; let useCase: TransferLeagueOwnershipUseCase; beforeEach(() => { leagueRepository = { findById: vi.fn(), update: vi.fn(), }; membershipRepository = { getMembership: vi.fn(), saveMembership: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger & { error: Mock }; useCase = new TransferLeagueOwnershipUseCase( leagueRepository as unknown as LeagueRepository, membershipRepository as unknown as LeagueMembershipRepository, logger, ); }); it('transfers ownership successfully', async () => { const mockLeague = { id: 'league-1', ownerId: { toString: () => 'owner-1' }, update: vi.fn().mockReturnValue({}), } as unknown as League; const mockNewOwnerMembership = { leagueId: 'league-1', driverId: 'owner-2', status: { toString: () => 'active' }, role: 'member', } as unknown as LeagueMembership; const mockCurrentOwnerMembership = { leagueId: 'league-1', driverId: 'owner-1', status: { toString: () => 'active' }, role: 'owner', } as unknown as LeagueMembership; leagueRepository.findById.mockResolvedValue(mockLeague); membershipRepository.getMembership .mockResolvedValueOnce(mockNewOwnerMembership) .mockResolvedValueOnce(mockCurrentOwnerMembership); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result = await useCase.execute(input); expect(result.isOk()).toBe(true); const successResult = result.unwrap(); expect(successResult).toEqual({ leagueId: 'league-1', previousOwnerId: 'owner-1', newOwnerId: 'owner-2', }); expect(leagueRepository.findById).toHaveBeenCalledWith('league-1'); expect(membershipRepository.getMembership).toHaveBeenCalledWith('league-1', 'owner-2'); expect(membershipRepository.getMembership).toHaveBeenCalledWith('league-1', 'owner-1'); expect(membershipRepository.saveMembership).toHaveBeenCalledTimes(2); const saveMembershipMock = membershipRepository.saveMembership as Mock; const firstSaveCall = saveMembershipMock.mock.calls[0]![0] as { role: string }; const secondSaveCall = saveMembershipMock.mock.calls[1]![0] as { role: string }; expect(firstSaveCall.role).toBe('owner'); expect(secondSaveCall.role).toBe('admin'); expect(mockLeague.update).toHaveBeenCalledWith({ ownerId: 'owner-2' }); expect(leagueRepository.update).toHaveBeenCalledWith(expect.anything()); }); it('returns LEAGUE_NOT_FOUND when league does not exist', async () => { leagueRepository.findById.mockResolvedValue(null); const input: TransferLeagueOwnershipInput = { leagueId: 'non-existent', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode< TransferLeagueOwnershipErrorCode, { message: string } >; expect(error.code).toBe('LEAGUE_NOT_FOUND'); expect(error.details?.message).toContain('non-existent'); }); it('returns NOT_LEAGUE_OWNER when current owner does not match', async () => { const mockLeague = { id: 'league-1', ownerId: { toString: () => 'other-owner' }, update: vi.fn(), } as unknown as League; leagueRepository.findById.mockResolvedValue(mockLeague); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode< TransferLeagueOwnershipErrorCode, { message: string } >; expect(error.code).toBe('NOT_LEAGUE_OWNER'); }); it('returns NEW_OWNER_NOT_MEMBER when new owner is not an active member', async () => { const mockLeague = { id: 'league-1', ownerId: { toString: () => 'owner-1' }, update: vi.fn(), } as unknown as League; leagueRepository.findById.mockResolvedValue(mockLeague); membershipRepository.getMembership.mockResolvedValue(null); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode< TransferLeagueOwnershipErrorCode, { message: string } >; expect(error.code).toBe('NEW_OWNER_NOT_MEMBER'); }); it('wraps repository errors in REPOSITORY_ERROR and logs the error', async () => { const mockLeague = { id: 'league-1', ownerId: { toString: () => 'owner-1' }, update: vi.fn().mockReturnValue({}), } as unknown as League; leagueRepository.findById.mockResolvedValue(mockLeague); const mockNewOwnerMembership = { leagueId: 'league-1', driverId: 'owner-2', status: { toString: () => 'active' }, role: 'member', } as unknown as LeagueMembership; membershipRepository.getMembership .mockResolvedValueOnce(mockNewOwnerMembership) .mockResolvedValueOnce(null); const updateError = new Error('update failed'); leagueRepository.update.mockRejectedValue(updateError); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode< TransferLeagueOwnershipErrorCode, { message: string } >; expect(error.code).toBe('REPOSITORY_ERROR'); expect(error.details?.message).toBe('update failed'); expect(logger.error).toHaveBeenCalled(); const errorMock = logger.error as Mock; const calls = errorMock.mock.calls; expect(calls.length).toBeGreaterThan(0); const loggedMessage = calls[0]?.[0] as string; expect(loggedMessage).toContain('update failed'); }); });