import { Test, TestingModule } from '@nestjs/testing'; import { vi, type MockedFunction } from 'vitest'; import { ForbiddenException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { ProtestsController } from './ProtestsController'; import { ProtestsService } from './ProtestsService'; import { ReviewProtestCommandDTO } from '../race/dtos/ReviewProtestCommandDTO'; import type { ReviewProtestPresenter } from './presenters/ReviewProtestPresenter'; describe('ProtestsController', () => { let controller: ProtestsController; let reviewProtestMock: MockedFunction; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ProtestsController], providers: [ { provide: ProtestsService, useValue: { reviewProtest: vi.fn(), }, }, ], }).compile(); controller = module.get(ProtestsController); const service = module.get(ProtestsService); reviewProtestMock = vi.mocked(service.reviewProtest); }); const successPresenter = (viewModel: ReviewProtestPresenter['viewModel']): ReviewProtestPresenter => ({ get viewModel() { return viewModel; }, getViewModel: () => viewModel, reset: vi.fn(), presentSuccess: vi.fn(), presentError: vi.fn(), } as unknown as ReviewProtestPresenter); describe('reviewProtest', () => { it('should call service and not throw on success', async () => { const protestId = 'protest-123'; const body: Omit = { stewardId: 'steward-1', decision: 'uphold', decisionNotes: 'Reason', }; reviewProtestMock.mockResolvedValue( successPresenter({ success: true, protestId, stewardId: body.stewardId, decision: body.decision, }), ); await controller.reviewProtest(protestId, body); expect(reviewProtestMock).toHaveBeenCalledWith({ protestId, ...body }); }); it('should throw NotFoundException when protest is not found', async () => { const protestId = 'protest-123'; const body: Omit = { stewardId: 'steward-1', decision: 'uphold', decisionNotes: 'Reason', }; reviewProtestMock.mockResolvedValue( successPresenter({ success: false, errorCode: 'PROTEST_NOT_FOUND', message: 'Protest not found', }), ); await expect(controller.reviewProtest(protestId, body)).rejects.toBeInstanceOf(NotFoundException); }); it('should throw ForbiddenException when steward is not league admin', async () => { const protestId = 'protest-123'; const body: Omit = { stewardId: 'steward-1', decision: 'uphold', decisionNotes: 'Reason', }; reviewProtestMock.mockResolvedValue( successPresenter({ success: false, errorCode: 'NOT_LEAGUE_ADMIN', message: 'Not authorized', }), ); await expect(controller.reviewProtest(protestId, body)).rejects.toBeInstanceOf(ForbiddenException); }); it('should throw InternalServerErrorException for unexpected error codes', async () => { const protestId = 'protest-123'; const body: Omit = { stewardId: 'steward-1', decision: 'uphold', decisionNotes: 'Reason', }; reviewProtestMock.mockResolvedValue( successPresenter({ success: false, errorCode: 'UNEXPECTED_ERROR', message: 'Unexpected', }), ); await expect(controller.reviewProtest(protestId, body)).rejects.toBeInstanceOf(InternalServerErrorException); }); }); });