refactor api modules
This commit is contained in:
@@ -13,11 +13,15 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
// Import use cases
|
||||
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
|
||||
// Import presenters
|
||||
import { ReviewProtestPresenter } from './presenters/ReviewProtestPresenter';
|
||||
|
||||
// Define injection tokens
|
||||
export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository';
|
||||
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
||||
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
export const REVIEW_PROTEST_PRESENTER_TOKEN = 'ReviewProtestPresenter';
|
||||
|
||||
export const ProtestsProviders: Provider[] = [
|
||||
ProtestsService, // Provide the service itself
|
||||
@@ -40,6 +44,26 @@ export const ProtestsProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
{
|
||||
provide: REVIEW_PROTEST_PRESENTER_TOKEN,
|
||||
useClass: ReviewProtestPresenter,
|
||||
},
|
||||
// Use cases
|
||||
ReviewProtestUseCase,
|
||||
{
|
||||
provide: ReviewProtestUseCase,
|
||||
useFactory: (
|
||||
protestRepo: any,
|
||||
raceRepo: any,
|
||||
leagueMembershipRepo: any,
|
||||
logger: Logger,
|
||||
output: ReviewProtestPresenter,
|
||||
) => new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, output),
|
||||
inject: [
|
||||
PROTEST_REPOSITORY_TOKEN,
|
||||
RACE_REPOSITORY_TOKEN,
|
||||
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
REVIEW_PROTEST_PRESENTER_TOKEN,
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -3,10 +3,10 @@ import { Result } from '@core/shared/application/Result';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type {
|
||||
ReviewProtestUseCase,
|
||||
ReviewProtestResult,
|
||||
ReviewProtestApplicationError,
|
||||
} from '@core/racing/application/use-cases/ReviewProtestUseCase';
|
||||
import { ProtestsService } from './ProtestsService';
|
||||
import type { ReviewProtestPresenter } from './presenters/ReviewProtestPresenter';
|
||||
import type { ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
|
||||
|
||||
describe('ProtestsService', () => {
|
||||
@@ -17,6 +17,21 @@ describe('ProtestsService', () => {
|
||||
beforeEach(() => {
|
||||
executeMock = vi.fn();
|
||||
const reviewProtestUseCase = { execute: executeMock } as unknown as ReviewProtestUseCase;
|
||||
const reviewProtestPresenter = {
|
||||
reset: vi.fn(),
|
||||
setCommand: vi.fn(),
|
||||
present: vi.fn(),
|
||||
presentError: vi.fn(),
|
||||
getResponseModel: vi.fn(),
|
||||
get responseModel() {
|
||||
return {
|
||||
success: true,
|
||||
protestId: 'test',
|
||||
stewardId: 'test',
|
||||
decision: 'uphold' as const,
|
||||
};
|
||||
},
|
||||
} as unknown as ReviewProtestPresenter;
|
||||
logger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
@@ -24,7 +39,7 @@ describe('ProtestsService', () => {
|
||||
error: vi.fn(),
|
||||
} as unknown as Logger;
|
||||
|
||||
service = new ProtestsService(reviewProtestUseCase, logger);
|
||||
service = new ProtestsService(reviewProtestUseCase, reviewProtestPresenter, logger);
|
||||
});
|
||||
|
||||
const baseCommand = {
|
||||
@@ -35,15 +50,7 @@ describe('ProtestsService', () => {
|
||||
};
|
||||
|
||||
it('returns DTO with success model on success', async () => {
|
||||
const coreResult: ReviewProtestResult = {
|
||||
leagueId: 'league-1',
|
||||
protestId: baseCommand.protestId,
|
||||
status: 'upheld',
|
||||
stewardId: baseCommand.stewardId,
|
||||
decision: baseCommand.decision,
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.ok<ReviewProtestResult, ReviewProtestApplicationError>(coreResult));
|
||||
executeMock.mockResolvedValue(Result.ok(undefined));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -62,7 +69,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Protest not found' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -79,7 +86,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Race not found for protest' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -96,7 +103,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Steward is not authorized to review this protest' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
@@ -114,7 +121,7 @@ describe('ProtestsService', () => {
|
||||
details: { message: 'Failed to review protest' },
|
||||
};
|
||||
|
||||
executeMock.mockResolvedValue(Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error));
|
||||
executeMock.mockResolvedValue(Result.err<void, ReviewProtestApplicationError>(error));
|
||||
|
||||
const dto = await service.reviewProtest(baseCommand);
|
||||
|
||||
|
||||
@@ -8,17 +8,14 @@ import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewP
|
||||
import { ReviewProtestPresenter, type ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
|
||||
|
||||
// Tokens
|
||||
import { LOGGER_TOKEN } from './ProtestsProviders';
|
||||
import { LOGGER_TOKEN, REVIEW_PROTEST_PRESENTER_TOKEN } from './ProtestsProviders';
|
||||
|
||||
import type { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository';
|
||||
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||
import type { ILeagueMembershipRepository } from '@core/racing/domain/repositories/ILeagueMembershipRepository';
|
||||
import { PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN } from './ProtestsProviders';
|
||||
|
||||
@Injectable()
|
||||
export class ProtestsService {
|
||||
constructor(
|
||||
private readonly reviewProtestUseCase: ReviewProtestUseCase,
|
||||
@Inject(REVIEW_PROTEST_PRESENTER_TOKEN) private readonly reviewProtestPresenter: ReviewProtestPresenter,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -30,11 +27,14 @@ export class ProtestsService {
|
||||
}): Promise<ReviewProtestResponseDTO> {
|
||||
this.logger.debug('[ProtestsService] Reviewing protest:', command);
|
||||
|
||||
const result = await this.reviewProtestUseCase.execute(command);
|
||||
const presenter = new ReviewProtestPresenter();
|
||||
// Set the command on the presenter so it can include stewardId and decision in the response
|
||||
this.reviewProtestPresenter.setCommand({
|
||||
stewardId: command.stewardId,
|
||||
decision: command.decision,
|
||||
});
|
||||
|
||||
presenter.present(result);
|
||||
await this.reviewProtestUseCase.execute(command);
|
||||
|
||||
return presenter.responseModel;
|
||||
return this.reviewProtestPresenter.responseModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,27 @@ export interface ReviewProtestResponseDTO {
|
||||
|
||||
export class ReviewProtestPresenter implements UseCaseOutputPort<ReviewProtestResult> {
|
||||
private model: ReviewProtestResponseDTO | null = null;
|
||||
private command: { stewardId: string; decision: 'uphold' | 'dismiss' } | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
this.command = null;
|
||||
}
|
||||
|
||||
setCommand(command: { stewardId: string; decision: 'uphold' | 'dismiss' }): void {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
present(result: ReviewProtestResult): void {
|
||||
if (!this.command) {
|
||||
throw new Error('Command must be set before presenting result');
|
||||
}
|
||||
|
||||
this.model = {
|
||||
success: true,
|
||||
protestId: result.protestId,
|
||||
decision: result.status === 'upheld' ? 'uphold' : 'dismiss',
|
||||
stewardId: this.command.stewardId,
|
||||
decision: this.command.decision,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user