refactor use cases

This commit is contained in:
2026-01-08 15:34:51 +01:00
parent d984ab24a8
commit 52e9a2f6a7
362 changed files with 5192 additions and 8409 deletions

View File

@@ -20,17 +20,13 @@ 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[] = [
{
provide: LOGGER_TOKEN,
useClass: ConsoleLogger,
},
{
provide: REVIEW_PROTEST_PRESENTER_TOKEN,
useClass: ReviewProtestPresenter,
},
ReviewProtestPresenter,
// Use cases
{
provide: ReviewProtestUseCase,
@@ -39,14 +35,12 @@ export const ProtestsProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
logger: Logger,
output: ReviewProtestPresenter,
) => new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, output),
) => new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger),
inject: [
PROTEST_REPOSITORY_TOKEN,
RACE_REPOSITORY_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
LOGGER_TOKEN,
REVIEW_PROTEST_PRESENTER_TOKEN,
],
},
];

View File

@@ -40,13 +40,15 @@ describe('ProtestsService', () => {
it('returns DTO with success model on success', async () => {
executeMock.mockImplementation(async (command) => {
presenter.present({ protestId: command.protestId } as ReviewProtestResult);
return Result.ok(undefined);
return Result.ok({
leagueId: 'league-1',
protestId: command.protestId,
status: 'upheld',
});
});
const dto = await service.reviewProtest(baseCommand);
expect(presenter.getResponseModel()).not.toBeNull();
expect(executeMock).toHaveBeenCalledWith(baseCommand);
expect(dto).toEqual<ReviewProtestResponseDTO>({
success: true,
@@ -63,8 +65,7 @@ describe('ProtestsService', () => {
};
executeMock.mockImplementation(async () => {
presenter.presentError(error);
return Result.err<void, ReviewProtestApplicationError>(error);
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
});
const dto = await service.reviewProtest(baseCommand);
@@ -83,8 +84,7 @@ describe('ProtestsService', () => {
};
executeMock.mockImplementation(async () => {
presenter.presentError(error);
return Result.err<void, ReviewProtestApplicationError>(error);
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
});
const dto = await service.reviewProtest(baseCommand);
@@ -103,8 +103,7 @@ describe('ProtestsService', () => {
};
executeMock.mockImplementation(async () => {
presenter.presentError(error);
return Result.err<void, ReviewProtestApplicationError>(error);
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
});
const dto = await service.reviewProtest(baseCommand);
@@ -124,8 +123,7 @@ describe('ProtestsService', () => {
};
executeMock.mockImplementation(async () => {
presenter.presentError(error);
return Result.err<void, ReviewProtestApplicationError>(error);
return Result.err<ReviewProtestResult, ReviewProtestApplicationError>(error);
});
const dto = await service.reviewProtest(baseCommand);

View File

@@ -8,14 +8,14 @@ import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewP
import { ReviewProtestPresenter, type ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
// Tokens
import { LOGGER_TOKEN, REVIEW_PROTEST_PRESENTER_TOKEN } from './ProtestsProviders';
import { LOGGER_TOKEN } from './ProtestsProviders';
@Injectable()
export class ProtestsService {
constructor(
private readonly reviewProtestUseCase: ReviewProtestUseCase,
@Inject(REVIEW_PROTEST_PRESENTER_TOKEN) private readonly reviewProtestPresenter: ReviewProtestPresenter,
private readonly reviewProtestPresenter: ReviewProtestPresenter,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
@@ -27,14 +27,20 @@ export class ProtestsService {
}): Promise<ReviewProtestResponseDTO> {
this.logger.debug('[ProtestsService] Reviewing protest:', command);
// Set the command on the presenter so it can include stewardId and decision in the response
this.reviewProtestPresenter.setCommand({
const result = await this.reviewProtestUseCase.execute(command);
if (result.isErr()) {
const err = result.unwrapErr();
this.reviewProtestPresenter.presentError(err);
return this.reviewProtestPresenter.responseModel;
}
// Present the result with the additional context
this.reviewProtestPresenter.present(result.unwrap(), {
stewardId: command.stewardId,
decision: command.decision,
});
await this.reviewProtestUseCase.execute(command);
return this.reviewProtestPresenter.responseModel;
}
}

View File

@@ -12,27 +12,21 @@ 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');
present(result: ReviewProtestResult, context?: { stewardId: string; decision: 'uphold' | 'dismiss' }): void {
if (!context) {
throw new Error('Context must be provided when presenting result');
}
this.model = {
success: true,
protestId: result.protestId,
stewardId: this.command.stewardId,
decision: this.command.decision,
stewardId: context.stewardId,
decision: context.decision,
};
}