import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest'; import { RejectTeamJoinRequestUseCase, type RejectTeamJoinRequestInput, type RejectTeamJoinRequestResult, type RejectTeamJoinRequestErrorCode, } from './RejectTeamJoinRequestUseCase'; import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository'; import type { ITeamRepository } from '../../domain/repositories/ITeamRepository'; import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; interface TeamRepositoryMock { findById: Mock; } interface TeamMembershipRepositoryMock { getMembership: Mock; getJoinRequests: Mock; removeJoinRequest: Mock; } describe('RejectTeamJoinRequestUseCase', () => { let teamRepository: TeamRepositoryMock; let membershipRepository: TeamMembershipRepositoryMock; let logger: Logger & { info: Mock; warn: Mock; error: Mock; debug: Mock }; let output: UseCaseOutputPort & { present: Mock }; let useCase: RejectTeamJoinRequestUseCase; beforeEach(() => { teamRepository = { findById: vi.fn(), }; membershipRepository = { getMembership: vi.fn(), getJoinRequests: vi.fn(), removeJoinRequest: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger & { info: Mock; warn: Mock; error: Mock; debug: Mock }; output = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; useCase = new RejectTeamJoinRequestUseCase( teamRepository as unknown as ITeamRepository, membershipRepository as unknown as ITeamMembershipRepository, logger, output, ); }); it('rejects a pending join request successfully and presents result', async () => { const input: RejectTeamJoinRequestInput = { teamId: 'team-1', managerId: 'manager-1', requestId: 'req-1', }; teamRepository.findById.mockResolvedValue({ id: 'team-1' }); membershipRepository.getMembership.mockResolvedValue({ teamId: 'team-1', driverId: 'manager-1', role: 'owner', status: 'active', joinedAt: new Date(), }); membershipRepository.getJoinRequests.mockResolvedValue([ { id: 'req-1', teamId: 'team-1', driverId: 'driver-1', requestedAt: new Date(), message: 'please', status: 'pending', }, ]); membershipRepository.removeJoinRequest.mockResolvedValue(undefined); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); const presented = output.present.mock.calls[0]![0] as RejectTeamJoinRequestResult; expect(presented.teamId).toBe('team-1'); expect(presented.requestId).toBe('req-1'); expect(presented.status).toBe('rejected'); expect(membershipRepository.removeJoinRequest).toHaveBeenCalledWith('req-1'); }); it('returns TEAM_NOT_FOUND when team does not exist', async () => { const input: RejectTeamJoinRequestInput = { teamId: 'missing-team', managerId: 'manager-1', requestId: 'req-1', }; teamRepository.findById.mockResolvedValue(null); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< RejectTeamJoinRequestErrorCode, { message: string } >; expect(err.code).toBe('TEAM_NOT_FOUND'); expect(output.present).not.toHaveBeenCalled(); expect(membershipRepository.removeJoinRequest).not.toHaveBeenCalled(); }); it('returns UNAUTHORIZED when manager is not authorized', async () => { const input: RejectTeamJoinRequestInput = { teamId: 'team-1', managerId: 'user-1', requestId: 'req-1', }; teamRepository.findById.mockResolvedValue({ id: 'team-1' }); membershipRepository.getMembership.mockResolvedValue(null); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< RejectTeamJoinRequestErrorCode, { message: string } >; expect(err.code).toBe('UNAUTHORIZED'); expect(output.present).not.toHaveBeenCalled(); expect(membershipRepository.removeJoinRequest).not.toHaveBeenCalled(); }); it('returns REQUEST_NOT_FOUND when join request does not exist', async () => { const input: RejectTeamJoinRequestInput = { teamId: 'team-1', managerId: 'manager-1', requestId: 'missing-req', }; teamRepository.findById.mockResolvedValue({ id: 'team-1' }); membershipRepository.getMembership.mockResolvedValue({ teamId: 'team-1', driverId: 'manager-1', role: 'owner', status: 'active', joinedAt: new Date(), }); membershipRepository.getJoinRequests.mockResolvedValue([]); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< RejectTeamJoinRequestErrorCode, { message: string } >; expect(err.code).toBe('REQUEST_NOT_FOUND'); expect(output.present).not.toHaveBeenCalled(); expect(membershipRepository.removeJoinRequest).not.toHaveBeenCalled(); }); it('returns INVALID_REQUEST_STATE when join request is not pending', async () => { const input: RejectTeamJoinRequestInput = { teamId: 'team-1', managerId: 'manager-1', requestId: 'req-1', }; teamRepository.findById.mockResolvedValue({ id: 'team-1' }); membershipRepository.getMembership.mockResolvedValue({ teamId: 'team-1', driverId: 'manager-1', role: 'owner', status: 'active', joinedAt: new Date(), }); membershipRepository.getJoinRequests.mockResolvedValue([ { id: 'req-1', teamId: 'team-1', driverId: 'driver-1', requestedAt: new Date(), message: 'please', status: 'approved', }, ]); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< RejectTeamJoinRequestErrorCode, { message: string } >; expect(err.code).toBe('INVALID_REQUEST_STATE'); expect(output.present).not.toHaveBeenCalled(); expect(membershipRepository.removeJoinRequest).not.toHaveBeenCalled(); }); it('returns REPOSITORY_ERROR when repository throws', async () => { const input: RejectTeamJoinRequestInput = { teamId: 'team-1', managerId: 'manager-1', requestId: 'req-1', }; const repoError = new Error('Repository failure'); teamRepository.findById.mockRejectedValue(repoError); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< RejectTeamJoinRequestErrorCode, { message: string } >; expect(err.code).toBe('REPOSITORY_ERROR'); expect(err.details.message).toBe('Repository failure'); expect(output.present).not.toHaveBeenCalled(); expect(membershipRepository.removeJoinRequest).not.toHaveBeenCalled(); }); });