import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest'; import { TransferLeagueOwnershipUseCase, type TransferLeagueOwnershipInput, type TransferLeagueOwnershipResult, type TransferLeagueOwnershipErrorCode, } from './TransferLeagueOwnershipUseCase'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; import type { Logger } from '@core/shared/application'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { Result } from '@core/shared/application/Result'; describe('TransferLeagueOwnershipUseCase', () => { let leagueRepository: ILeagueRepository; let membershipRepository: ILeagueMembershipRepository; let logger: Logger & { error: Mock }; let useCase: TransferLeagueOwnershipUseCase; beforeEach(() => { leagueRepository = { findById: vi.fn(), update: vi.fn(), } as unknown as ILeagueRepository; membershipRepository = { getMembership: vi.fn(), saveMembership: vi.fn(), } as unknown as ILeagueMembershipRepository; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger & { error: Mock }; useCase = new TransferLeagueOwnershipUseCase( leagueRepository, membershipRepository, logger, ); }); it('transfers ownership successfully', async () => { const mockLeague = { id: 'league-1', ownerId: { toString: () => 'owner-1' }, update: vi.fn().mockReturnValue({}), } as unknown as { id: string; ownerId: { toString: () => string }; update: Mock }; const mockNewOwnerMembership = { leagueId: 'league-1', driverId: 'owner-2', status: { toString: () => 'active' }, role: 'member', } as unknown as { leagueId: string; driverId: string; status: { toString: () => string }; role: string }; const mockCurrentOwnerMembership = { leagueId: 'league-1', driverId: 'owner-1', status: { toString: () => 'active' }, role: 'owner', } as unknown as { leagueId: string; driverId: string; status: { toString: () => string }; role: string }; (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague); (membershipRepository.getMembership as unknown as Mock) .mockResolvedValueOnce(mockNewOwnerMembership) .mockResolvedValueOnce(mockCurrentOwnerMembership); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result: Result< TransferLeagueOwnershipResult, ApplicationErrorCode > = 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 unknown 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 as unknown as Mock).mockResolvedValue(null); const input: TransferLeagueOwnershipInput = { leagueId: 'non-existent', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result: Result< TransferLeagueOwnershipResult, ApplicationErrorCode > = 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 { id: string; ownerId: { toString: () => string }; update: Mock }; (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result: Result< TransferLeagueOwnershipResult, ApplicationErrorCode > = 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 { id: string; ownerId: { toString: () => string }; update: Mock }; (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague); (membershipRepository.getMembership as unknown as Mock).mockResolvedValue(null); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result: Result< TransferLeagueOwnershipResult, ApplicationErrorCode > = 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 { id: string; ownerId: { toString: () => string }; update: Mock }; (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague); const mockNewOwnerMembership = { leagueId: 'league-1', driverId: 'owner-2', status: { toString: () => 'active' }, role: 'member', } as unknown as { leagueId: string; driverId: string; status: { toString: () => string }; role: string }; (membershipRepository.getMembership as unknown as Mock) .mockResolvedValueOnce(mockNewOwnerMembership) .mockResolvedValueOnce(null); const updateError = new Error('update failed'); (leagueRepository.update as unknown as Mock).mockRejectedValue(updateError); const input: TransferLeagueOwnershipInput = { leagueId: 'league-1', currentOwnerId: 'owner-1', newOwnerId: 'owner-2', }; const result: Result< TransferLeagueOwnershipResult, ApplicationErrorCode > = 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'); }); });