Files
gridpilot.gg/apps/api/src/domain/protests/ProtestsController.test.ts
2025-12-20 17:06:11 +01:00

122 lines
3.9 KiB
TypeScript

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<ProtestsService['reviewProtest']>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ProtestsController],
providers: [
{
provide: ProtestsService,
useValue: {
reviewProtest: vi.fn(),
},
},
],
}).compile();
controller = module.get<ProtestsController>(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<ReviewProtestCommandDTO, 'protestId'> = {
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<ReviewProtestCommandDTO, 'protestId'> = {
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<ReviewProtestCommandDTO, 'protestId'> = {
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<ReviewProtestCommandDTO, 'protestId'> = {
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);
});
});
});