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

@@ -6,13 +6,12 @@ import {
type GetCurrentUserSocialResult,
} from './GetCurrentUserSocialUseCase';
import type { ISocialGraphRepository } from '../../domain/repositories/ISocialGraphRepository';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
import { Driver } from '@core/racing/domain/entities/Driver';
describe('GetCurrentUserSocialUseCase', () => {
let socialGraphRepository: ISocialGraphRepository & { getFriends: Mock };
let logger: Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };
let output: UseCaseOutputPort<GetCurrentUserSocialResult> & { present: Mock };
let useCase: GetCurrentUserSocialUseCase;
beforeEach(() => {
@@ -29,14 +28,10 @@ describe('GetCurrentUserSocialUseCase', () => {
error: vi.fn(),
} as unknown as Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };
output = {
present: vi.fn(),
} as unknown as UseCaseOutputPort<GetCurrentUserSocialResult> & { present: Mock };
useCase = new GetCurrentUserSocialUseCase(socialGraphRepository, logger, output);
useCase = new GetCurrentUserSocialUseCase(socialGraphRepository, logger);
});
it('presents current user social with mapped friends', async () => {
it('returns current user social with mapped friends', async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
@@ -55,20 +50,17 @@ describe('GetCurrentUserSocialUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const socialResult = result.unwrap();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = (output.present as Mock).mock.calls[0]![0] as GetCurrentUserSocialResult;
expect(presented.currentUser).toEqual({
expect(socialResult.currentUser).toEqual({
driverId: 'driver-1',
displayName: '',
avatarUrl: '',
countryCode: '',
});
expect(presented.friends).toHaveLength(1);
expect(presented.friends[0]).toEqual({
expect(socialResult.friends).toHaveLength(1);
expect(socialResult.friends[0]).toEqual({
driverId: 'friend-1',
displayName: 'Friend One',
avatarUrl: '',
@@ -82,17 +74,17 @@ describe('GetCurrentUserSocialUseCase', () => {
vi.useRealTimers();
});
it('warns and presents empty friends list when no friends exist', async () => {
it('returns empty friends list when no friends exist', async () => {
socialGraphRepository.getFriends.mockResolvedValue([]);
const input: GetCurrentUserSocialInput = { driverId: 'driver-1' };
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(output.present).toHaveBeenCalledTimes(1);
const socialResult = result.unwrap();
const presented = (output.present as Mock).mock.calls[0]![0] as GetCurrentUserSocialResult;
expect(presented.friends).toEqual([]);
expect(socialResult.friends).toEqual([]);
expect(socialResult.currentUser.driverId).toBe('driver-1');
expect(logger.warn).toHaveBeenCalledTimes(1);
expect((logger.warn as Mock).mock.calls[0]![0]).toBe(
@@ -112,7 +104,6 @@ describe('GetCurrentUserSocialUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('DB error');
expect(output.present).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,4 +1,4 @@
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { ISocialGraphRepository } from '../../domain/repositories/ISocialGraphRepository';
@@ -26,18 +26,18 @@ export type GetCurrentUserSocialApplicationError = ApplicationErrorCode<
* Application-level use case to retrieve the current user's social context.
*
* Keeps orchestration in the social bounded context while delegating
* data access to domain repositories and presenting via an output port.
* data access to domain repositories.
* Returns Result directly without calling presenter.
*/
export class GetCurrentUserSocialUseCase {
constructor(
private readonly socialGraphRepository: ISocialGraphRepository,
private readonly logger: Logger,
private readonly output: UseCaseOutputPort<GetCurrentUserSocialResult>,
) {}
async execute(
input: GetCurrentUserSocialInput,
): Promise<Result<void, GetCurrentUserSocialApplicationError>> {
): Promise<Result<GetCurrentUserSocialResult, GetCurrentUserSocialApplicationError>> {
this.logger.debug('GetCurrentUserSocialUseCase.execute: Starting execution', { input });
try {
@@ -81,12 +81,11 @@ export class GetCurrentUserSocialUseCase {
friends,
};
this.output.present(result);
this.logger.info(
'GetCurrentUserSocialUseCase.execute: Successfully presented current user social data',
'GetCurrentUserSocialUseCase.execute: Successfully retrieved current user social data',
);
return Result.ok(undefined);
return Result.ok(result);
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));

View File

@@ -7,12 +7,11 @@ import {
} from './GetUserFeedUseCase';
import type { IFeedRepository } from '../../domain/repositories/IFeedRepository';
import type { FeedItem } from '../../domain/types/FeedItem';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
describe('GetUserFeedUseCase', () => {
let feedRepository: IFeedRepository & { getFeedForDriver: Mock };
let logger: Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };
let output: UseCaseOutputPort<GetUserFeedResult> & { present: Mock };
let useCase: GetUserFeedUseCase;
beforeEach(() => {
@@ -28,14 +27,10 @@ describe('GetUserFeedUseCase', () => {
error: vi.fn(),
} as unknown as Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };
output = {
present: vi.fn(),
} as unknown as UseCaseOutputPort<GetUserFeedResult> & { present: Mock };
useCase = new GetUserFeedUseCase(feedRepository, logger, output);
useCase = new GetUserFeedUseCase(feedRepository, logger);
});
it('presents feed items when repository returns items', async () => {
it('returns feed items when repository returns items', async () => {
const items: FeedItem[] = [
{
id: 'item-1',
@@ -65,29 +60,26 @@ describe('GetUserFeedUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const feedResult = result.unwrap();
expect(feedRepository.getFeedForDriver).toHaveBeenCalledTimes(1);
expect(feedRepository.getFeedForDriver).toHaveBeenCalledWith('driver-1', 10);
expect(output.present).toHaveBeenCalledTimes(1);
const presented = (output.present as Mock).mock.calls[0]![0] as GetUserFeedResult;
expect(presented.items).toEqual(items);
expect(feedResult.items).toEqual(items);
expect(logger.warn).not.toHaveBeenCalled();
});
it('warns and presents empty list when no items exist', async () => {
it('returns empty list when no items exist', async () => {
feedRepository.getFeedForDriver.mockResolvedValue([]);
const input: GetUserFeedInput = { driverId: 'driver-1' };
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(output.present).toHaveBeenCalledTimes(1);
const feedResult = result.unwrap();
const presented = (output.present as Mock).mock.calls[0]![0] as GetUserFeedResult;
expect(presented.items).toEqual([]);
expect(feedResult.items).toEqual([]);
expect(logger.warn).toHaveBeenCalledTimes(1);
expect((logger.warn as Mock).mock.calls[0]![0]).toBe(
@@ -107,7 +99,6 @@ describe('GetUserFeedUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('DB error');
expect(output.present).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,4 +1,4 @@
import type { Logger , UseCaseOutputPort } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { IFeedRepository } from '../../domain/repositories/IFeedRepository';
@@ -26,12 +26,11 @@ export class GetUserFeedUseCase {
constructor(
private readonly feedRepository: IFeedRepository,
private readonly logger: Logger,
private readonly output: UseCaseOutputPort<GetUserFeedResult>,
) {}
async execute(
input: GetUserFeedInput,
): Promise<Result<void, GetUserFeedApplicationError>> {
): Promise<Result<GetUserFeedResult, GetUserFeedApplicationError>> {
const { driverId, limit } = input;
this.logger.debug('GetUserFeedUseCase.execute started', { driverId, limit });
@@ -51,9 +50,7 @@ export class GetUserFeedUseCase {
items,
};
this.output.present(result);
return Result.ok(undefined);
return Result.ok(result);
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));