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

@@ -3,7 +3,6 @@ import { DashboardModule } from './DashboardModule';
import { DashboardController } from './DashboardController';
import { DashboardService } from './DashboardService';
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
import { DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN } from './DashboardProviders';
describe('DashboardModule', () => {
let module: TestingModule;
@@ -30,8 +29,8 @@ describe('DashboardModule', () => {
expect(service).toBeInstanceOf(DashboardService);
});
it('should bind DashboardOverviewPresenter as the output port for the use case', () => {
const presenter = module.get<DashboardOverviewPresenter>(DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN);
it('should provide DashboardOverviewPresenter', () => {
const presenter = module.get<DashboardOverviewPresenter>(DashboardOverviewPresenter);
expect(presenter).toBeDefined();
expect(presenter).toBeInstanceOf(DashboardOverviewPresenter);
});

View File

@@ -21,7 +21,6 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
import {
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
DRIVER_REPOSITORY_TOKEN,
IMAGE_SERVICE_TOKEN,
@@ -36,7 +35,6 @@ import {
// Re-export tokens for convenience (legacy imports)
export {
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
DRIVER_REPOSITORY_TOKEN,
IMAGE_SERVICE_TOKEN,
@@ -60,10 +58,6 @@ export const DashboardProviders: Provider[] = [
useFactory: (logger: Logger) => new InMemoryImageServiceAdapter(logger),
inject: [LOGGER_TOKEN],
},
{
provide: DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
useExisting: DashboardOverviewPresenter,
},
{
provide: DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
useFactory: (
@@ -77,7 +71,6 @@ export const DashboardProviders: Provider[] = [
feedRepo: IFeedRepository,
socialRepo: ISocialGraphRepository,
imageService: ImageServicePort,
output: DashboardOverviewPresenter,
) =>
new DashboardOverviewUseCase(
driverRepo,
@@ -91,7 +84,6 @@ export const DashboardProviders: Provider[] = [
socialRepo,
async (driverId: string) => imageService.getDriverAvatar(driverId),
() => null,
output,
),
inject: [
DRIVER_REPOSITORY_TOKEN,
@@ -104,7 +96,6 @@ export const DashboardProviders: Provider[] = [
SOCIAL_FEED_REPOSITORY_TOKEN,
SOCIAL_GRAPH_REPOSITORY_TOKEN,
IMAGE_SERVICE_TOKEN,
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
],
},
];

View File

@@ -4,8 +4,9 @@ import { DashboardService } from './DashboardService';
describe('DashboardService', () => {
it('getDashboardOverview returns presenter model on success', async () => {
const presenter = { getResponseModel: vi.fn(() => ({ feed: [] })) };
const useCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const mockResult = { currentDriver: null, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 0, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [] }, friends: [] };
const presenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ feed: [] })) };
const useCase = { execute: vi.fn(async () => Result.ok(mockResult)) };
const service = new DashboardService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
@@ -15,13 +16,14 @@ describe('DashboardService', () => {
await expect(service.getDashboardOverview('d1')).resolves.toEqual({ feed: [] });
expect(useCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
expect(presenter.present).toHaveBeenCalledWith(mockResult);
});
it('getDashboardOverview throws with details message on error', async () => {
const service = new DashboardService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
{ getResponseModel: vi.fn() } as any,
{ present: vi.fn(), getResponseModel: vi.fn() } as any,
);
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: boom');
@@ -31,7 +33,7 @@ describe('DashboardService', () => {
const service = new DashboardService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ getResponseModel: vi.fn() } as any,
{ present: vi.fn(), getResponseModel: vi.fn() } as any,
);
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: Unknown error');

View File

@@ -1,5 +1,5 @@
import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
@@ -8,7 +8,6 @@ import type { Logger } from '@core/shared/application/Logger';
// Tokens (standalone to avoid circular imports)
import {
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
LOGGER_TOKEN,
} from './DashboardTokens';
@@ -18,7 +17,7 @@ export class DashboardService {
constructor(
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
@Inject(DASHBOARD_OVERVIEW_USE_CASE_TOKEN) private readonly dashboardOverviewUseCase: DashboardOverviewUseCase,
@Inject(DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN) private readonly presenter: DashboardOverviewPresenter,
private readonly presenter: DashboardOverviewPresenter,
) {}
async getDashboardOverview(driverId: string): Promise<DashboardOverviewDTO> {
@@ -29,9 +28,11 @@ export class DashboardService {
if (result.isErr()) {
const error = result.error;
const message = error?.details?.message || 'Unknown error';
throw new Error(`Failed to get dashboard overview: ${message}`);
throw new NotFoundException(`Failed to get dashboard overview: ${message}`);
}
// Present the result
this.presenter.present(result.unwrap());
return this.presenter.getResponseModel();
}
}

View File

@@ -12,5 +12,4 @@ export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const DASHBOARD_OVERVIEW_USE_CASE_TOKEN = 'DashboardOverviewUseCase';
export const DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN = 'DashboardOverviewOutputPort';

View File

@@ -1,4 +1,3 @@
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type {
DashboardOverviewResult,
} from '@core/racing/application/use-cases/DashboardOverviewUseCase';
@@ -13,7 +12,7 @@ import {
DashboardFriendSummaryDTO,
} from '../dtos/DashboardOverviewDTO';
export class DashboardOverviewPresenter implements UseCaseOutputPort<DashboardOverviewResult> {
export class DashboardOverviewPresenter {
private responseModel: DashboardOverviewDTO | null = null;
present(data: DashboardOverviewResult): void {