122 lines
3.9 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|