presenter refactoring
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { vi } from 'vitest';
|
||||
import { vi, type MockedFunction } from 'vitest';
|
||||
import { ForbiddenException, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
||||
import { ProtestsController } from './ProtestsController';
|
||||
import { RaceService } from '../race/RaceService';
|
||||
import { ProtestsService } from './ProtestsService';
|
||||
import { ReviewProtestCommandDTO } from '../race/dtos/ReviewProtestCommandDTO';
|
||||
import type { ReviewProtestPresenter } from './presenters/ReviewProtestPresenter';
|
||||
|
||||
describe('ProtestsController', () => {
|
||||
let controller: ProtestsController;
|
||||
let raceService: ReturnType<typeof vi.mocked<RaceService>>;
|
||||
let reviewProtestMock: MockedFunction<ProtestsService['reviewProtest']>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ProtestsController],
|
||||
providers: [
|
||||
{
|
||||
provide: RaceService,
|
||||
provide: ProtestsService,
|
||||
useValue: {
|
||||
reviewProtest: vi.fn(),
|
||||
},
|
||||
@@ -22,18 +24,98 @@ describe('ProtestsController', () => {
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ProtestsController>(ProtestsController);
|
||||
raceService = vi.mocked(module.get(RaceService));
|
||||
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 review protest', async () => {
|
||||
it('should call service and not throw on success', async () => {
|
||||
const protestId = 'protest-123';
|
||||
const body: Omit<ReviewProtestCommandDTO, 'protestId'> = { decision: 'upheld', reason: 'Reason' };
|
||||
raceService.reviewProtest.mockResolvedValue(undefined);
|
||||
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(raceService.reviewProtest).toHaveBeenCalledWith({ 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user