refactor
This commit is contained in:
@@ -5,6 +5,8 @@ import { AnalyticsService } from './AnalyticsService';
|
||||
import type { Response } from 'express';
|
||||
import { EntityType, VisitorType } from '@core/analytics/domain/types/PageView';
|
||||
import { EngagementAction, EngagementEntityType } from '@core/analytics/domain/types/EngagementEvent';
|
||||
import type { RecordEngagementOutputDTO } from './dtos/RecordEngagementOutputDTO';
|
||||
import type { RecordPageViewOutputDTO } from './dtos/RecordPageViewOutputDTO';
|
||||
|
||||
describe('AnalyticsController', () => {
|
||||
let controller: AnalyticsController;
|
||||
@@ -42,8 +44,8 @@ describe('AnalyticsController', () => {
|
||||
userAgent: 'Mozilla/5.0',
|
||||
country: 'US',
|
||||
};
|
||||
const presenterMock = { viewModel: { pageViewId: 'pv-123' } };
|
||||
service.recordPageView.mockResolvedValue(presenterMock as any);
|
||||
const dto: RecordPageViewOutputDTO = { pageViewId: 'pv-123' };
|
||||
service.recordPageView.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -54,7 +56,7 @@ describe('AnalyticsController', () => {
|
||||
|
||||
expect(service.recordPageView).toHaveBeenCalledWith(input);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(presenterMock.viewModel);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,8 +71,8 @@ describe('AnalyticsController', () => {
|
||||
actorId: 'actor-789',
|
||||
metadata: { key: 'value' },
|
||||
};
|
||||
const presenterMock = { eventId: 'event-123', engagementWeight: 10 };
|
||||
service.recordEngagement.mockResolvedValue(presenterMock as any);
|
||||
const dto: RecordEngagementOutputDTO = { eventId: 'event-123', engagementWeight: 10 };
|
||||
service.recordEngagement.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -81,45 +83,41 @@ describe('AnalyticsController', () => {
|
||||
|
||||
expect(service.recordEngagement).toHaveBeenCalledWith(input);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.json).toHaveBeenCalledWith((presenterMock as any).viewModel);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDashboardData', () => {
|
||||
it('should return dashboard data', async () => {
|
||||
const presenterMock = {
|
||||
viewModel: {
|
||||
totalUsers: 100,
|
||||
activeUsers: 50,
|
||||
totalRaces: 20,
|
||||
totalLeagues: 5,
|
||||
},
|
||||
const dto = {
|
||||
totalUsers: 100,
|
||||
activeUsers: 50,
|
||||
totalRaces: 20,
|
||||
totalLeagues: 5,
|
||||
};
|
||||
service.getDashboardData.mockResolvedValue(presenterMock as any);
|
||||
service.getDashboardData.mockResolvedValue(dto);
|
||||
|
||||
const result = await controller.getDashboardData();
|
||||
|
||||
expect(service.getDashboardData).toHaveBeenCalled();
|
||||
expect(result).toEqual(presenterMock.viewModel);
|
||||
expect(result).toEqual(dto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAnalyticsMetrics', () => {
|
||||
it('should return analytics metrics', async () => {
|
||||
const presenterMock = {
|
||||
viewModel: {
|
||||
pageViews: 1000,
|
||||
uniqueVisitors: 500,
|
||||
averageSessionDuration: 300,
|
||||
bounceRate: 0.4,
|
||||
},
|
||||
const dto = {
|
||||
pageViews: 1000,
|
||||
uniqueVisitors: 500,
|
||||
averageSessionDuration: 300,
|
||||
bounceRate: 0.4,
|
||||
};
|
||||
service.getAnalyticsMetrics.mockResolvedValue(presenterMock as any);
|
||||
service.getAnalyticsMetrics.mockResolvedValue(dto);
|
||||
|
||||
const result = await controller.getAnalyticsMetrics();
|
||||
|
||||
expect(service.getAnalyticsMetrics).toHaveBeenCalled();
|
||||
expect(result).toEqual(presenterMock.viewModel);
|
||||
expect(result).toEqual(dto);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -27,8 +27,8 @@ export class AnalyticsController {
|
||||
@Body() input: RecordPageViewInput,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.analyticsService.recordPageView(input);
|
||||
res.status(HttpStatus.CREATED).json(presenter.viewModel);
|
||||
const dto = await this.analyticsService.recordPageView(input);
|
||||
res.status(HttpStatus.CREATED).json(dto);
|
||||
}
|
||||
|
||||
@Post('engagement')
|
||||
@@ -39,23 +39,21 @@ export class AnalyticsController {
|
||||
@Body() input: RecordEngagementInput,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.analyticsService.recordEngagement(input);
|
||||
res.status(HttpStatus.CREATED).json(presenter.viewModel);
|
||||
const dto = await this.analyticsService.recordEngagement(input);
|
||||
res.status(HttpStatus.CREATED).json(dto);
|
||||
}
|
||||
|
||||
@Get('dashboard')
|
||||
@ApiOperation({ summary: 'Get analytics dashboard data' })
|
||||
@ApiResponse({ status: 200, description: 'Dashboard data', type: GetDashboardDataOutputDTO })
|
||||
async getDashboardData(): Promise<GetDashboardDataOutputDTO> {
|
||||
const presenter = await this.analyticsService.getDashboardData();
|
||||
return presenter.viewModel;
|
||||
return this.analyticsService.getDashboardData();
|
||||
}
|
||||
|
||||
@Get('metrics')
|
||||
@ApiOperation({ summary: 'Get analytics metrics' })
|
||||
@ApiResponse({ status: 200, description: 'Analytics metrics', type: GetAnalyticsMetricsOutputDTO })
|
||||
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
|
||||
const presenter = await this.analyticsService.getAnalyticsMetrics();
|
||||
return presenter.viewModel;
|
||||
return this.analyticsService.getAnalyticsMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { AnalyticsService } from './AnalyticsService';
|
||||
import { RecordPageViewUseCase } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
||||
import { RecordEngagementUseCase } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
||||
import { GetDashboardDataUseCase } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
||||
import { GetAnalyticsMetricsUseCase } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
||||
import type { IPageViewRepository } from '@core/analytics/domain/repositories/IPageViewRepository';
|
||||
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { RecordPageViewOutput } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
||||
import type { RecordEngagementOutput } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
||||
import type { GetDashboardDataOutput } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
||||
import type { GetAnalyticsMetricsOutput } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
||||
|
||||
const Logger_TOKEN = 'Logger_TOKEN';
|
||||
const IPAGE_VIEW_REPO_TOKEN = 'IPageViewRepository_TOKEN';
|
||||
const IENGAGEMENT_REPO_TOKEN = 'IEngagementRepository_TOKEN';
|
||||
const RECORD_PAGE_VIEW_USE_CASE_TOKEN = 'RecordPageViewUseCase_TOKEN';
|
||||
const RECORD_ENGAGEMENT_USE_CASE_TOKEN = 'RecordEngagementUseCase_TOKEN';
|
||||
const GET_DASHBOARD_DATA_USE_CASE_TOKEN = 'GetDashboardDataUseCase_TOKEN';
|
||||
const GET_ANALYTICS_METRICS_USE_CASE_TOKEN = 'GetAnalyticsMetricsUseCase_TOKEN';
|
||||
|
||||
const RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN = 'RecordPageViewOutputPort_TOKEN';
|
||||
const RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN = 'RecordEngagementOutputPort_TOKEN';
|
||||
const GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN = 'GetDashboardDataOutputPort_TOKEN';
|
||||
const GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN = 'GetAnalyticsMetricsOutputPort_TOKEN';
|
||||
|
||||
import { InMemoryEngagementRepository } from '@adapters/analytics/persistence/inmemory/InMemoryEngagementRepository';
|
||||
import { InMemoryPageViewRepository } from '@adapters/analytics/persistence/inmemory/InMemoryPageViewRepository';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import { RecordPageViewUseCase } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
||||
import { RecordEngagementUseCase } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
||||
import { GetDashboardDataUseCase } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
||||
import { GetAnalyticsMetricsUseCase } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
||||
import { RecordPageViewPresenter } from './presenters/RecordPageViewPresenter';
|
||||
import { RecordEngagementPresenter } from './presenters/RecordEngagementPresenter';
|
||||
import { GetDashboardDataPresenter } from './presenters/GetDashboardDataPresenter';
|
||||
import { GetAnalyticsMetricsPresenter } from './presenters/GetAnalyticsMetricsPresenter';
|
||||
|
||||
export const AnalyticsProviders: Provider[] = [
|
||||
AnalyticsService,
|
||||
RecordPageViewPresenter,
|
||||
RecordEngagementPresenter,
|
||||
GetDashboardDataPresenter,
|
||||
GetAnalyticsMetricsPresenter,
|
||||
{
|
||||
provide: Logger_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
@@ -35,23 +48,43 @@ export const AnalyticsProviders: Provider[] = [
|
||||
useClass: InMemoryEngagementRepository,
|
||||
},
|
||||
{
|
||||
provide: RECORD_PAGE_VIEW_USE_CASE_TOKEN,
|
||||
useFactory: (repo: IPageViewRepository, logger: Logger) => new RecordPageViewUseCase(repo, logger),
|
||||
inject: [IPAGE_VIEW_REPO_TOKEN, Logger_TOKEN],
|
||||
provide: RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN,
|
||||
useExisting: RecordPageViewPresenter,
|
||||
},
|
||||
{
|
||||
provide: RECORD_ENGAGEMENT_USE_CASE_TOKEN,
|
||||
useFactory: (repo: IEngagementRepository, logger: Logger) => new RecordEngagementUseCase(repo, logger),
|
||||
inject: [IENGAGEMENT_REPO_TOKEN, Logger_TOKEN],
|
||||
provide: RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN,
|
||||
useExisting: RecordEngagementPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_DASHBOARD_DATA_USE_CASE_TOKEN,
|
||||
useFactory: (logger: Logger) => new GetDashboardDataUseCase(logger),
|
||||
inject: [Logger_TOKEN],
|
||||
provide: GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetDashboardDataPresenter,
|
||||
},
|
||||
{
|
||||
provide: GET_ANALYTICS_METRICS_USE_CASE_TOKEN,
|
||||
useFactory: (repo: IPageViewRepository, logger: Logger) => new GetAnalyticsMetricsUseCase(repo, logger),
|
||||
inject: [IPAGE_VIEW_REPO_TOKEN, Logger_TOKEN],
|
||||
provide: GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN,
|
||||
useExisting: GetAnalyticsMetricsPresenter,
|
||||
},
|
||||
{
|
||||
provide: RecordPageViewUseCase,
|
||||
useFactory: (repo: IPageViewRepository, logger: Logger, output: UseCaseOutputPort<RecordPageViewOutput>) =>
|
||||
new RecordPageViewUseCase(repo, logger, output),
|
||||
inject: [IPAGE_VIEW_REPO_TOKEN, Logger_TOKEN, RECORD_PAGE_VIEW_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: RecordEngagementUseCase,
|
||||
useFactory: (repo: IEngagementRepository, logger: Logger, output: UseCaseOutputPort<RecordEngagementOutput>) =>
|
||||
new RecordEngagementUseCase(repo, logger, output),
|
||||
inject: [IENGAGEMENT_REPO_TOKEN, Logger_TOKEN, RECORD_ENGAGEMENT_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetDashboardDataUseCase,
|
||||
useFactory: (logger: Logger, output: UseCaseOutputPort<GetDashboardDataOutput>) =>
|
||||
new GetDashboardDataUseCase(logger, output),
|
||||
inject: [Logger_TOKEN, GET_DASHBOARD_DATA_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GetAnalyticsMetricsUseCase,
|
||||
useFactory: (repo: IPageViewRepository, logger: Logger, output: UseCaseOutputPort<GetAnalyticsMetricsOutput>) =>
|
||||
new GetAnalyticsMetricsUseCase(repo, logger, output),
|
||||
inject: [IPAGE_VIEW_REPO_TOKEN, Logger_TOKEN, GET_ANALYTICS_METRICS_OUTPUT_PORT_TOKEN],
|
||||
},
|
||||
];
|
||||
@@ -17,41 +17,52 @@ import { GetAnalyticsMetricsPresenter } from './presenters/GetAnalyticsMetricsPr
|
||||
type RecordPageViewInput = RecordPageViewInputDTO;
|
||||
type RecordEngagementInput = RecordEngagementInputDTO;
|
||||
|
||||
const RECORD_PAGE_VIEW_USE_CASE_TOKEN = 'RecordPageViewUseCase_TOKEN';
|
||||
const RECORD_ENGAGEMENT_USE_CASE_TOKEN = 'RecordEngagementUseCase_TOKEN';
|
||||
const GET_DASHBOARD_DATA_USE_CASE_TOKEN = 'GetDashboardDataUseCase_TOKEN';
|
||||
const GET_ANALYTICS_METRICS_USE_CASE_TOKEN = 'GetAnalyticsMetricsUseCase_TOKEN';
|
||||
|
||||
@Injectable()
|
||||
export class AnalyticsService {
|
||||
constructor(
|
||||
@Inject(RECORD_PAGE_VIEW_USE_CASE_TOKEN) private readonly recordPageViewUseCase: RecordPageViewUseCase,
|
||||
@Inject(RECORD_ENGAGEMENT_USE_CASE_TOKEN) private readonly recordEngagementUseCase: RecordEngagementUseCase,
|
||||
@Inject(GET_DASHBOARD_DATA_USE_CASE_TOKEN) private readonly getDashboardDataUseCase: GetDashboardDataUseCase,
|
||||
@Inject(GET_ANALYTICS_METRICS_USE_CASE_TOKEN) private readonly getAnalyticsMetricsUseCase: GetAnalyticsMetricsUseCase,
|
||||
@Inject(RecordPageViewUseCase) private readonly recordPageViewUseCase: RecordPageViewUseCase,
|
||||
@Inject(RecordEngagementUseCase) private readonly recordEngagementUseCase: RecordEngagementUseCase,
|
||||
@Inject(GetDashboardDataUseCase) private readonly getDashboardDataUseCase: GetDashboardDataUseCase,
|
||||
@Inject(GetAnalyticsMetricsUseCase) private readonly getAnalyticsMetricsUseCase: GetAnalyticsMetricsUseCase,
|
||||
private readonly recordPageViewPresenter: RecordPageViewPresenter,
|
||||
private readonly recordEngagementPresenter: RecordEngagementPresenter,
|
||||
private readonly getDashboardDataPresenter: GetDashboardDataPresenter,
|
||||
private readonly getAnalyticsMetricsPresenter: GetAnalyticsMetricsPresenter,
|
||||
) {}
|
||||
|
||||
async recordPageView(input: RecordPageViewInput): Promise<RecordPageViewPresenter> {
|
||||
const presenter = new RecordPageViewPresenter();
|
||||
await this.recordPageViewUseCase.execute(input, presenter);
|
||||
return presenter;
|
||||
async recordPageView(input: RecordPageViewInput): Promise<RecordPageViewOutputDTO> {
|
||||
const result = await this.recordPageViewUseCase.execute(input);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to record page view');
|
||||
}
|
||||
return this.recordPageViewPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementPresenter> {
|
||||
const presenter = new RecordEngagementPresenter();
|
||||
await this.recordEngagementUseCase.execute(input, presenter);
|
||||
return presenter;
|
||||
async recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementOutputDTO> {
|
||||
const result = await this.recordEngagementUseCase.execute(input);
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to record engagement');
|
||||
}
|
||||
return this.recordEngagementPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getDashboardData(): Promise<GetDashboardDataPresenter> {
|
||||
const presenter = new GetDashboardDataPresenter();
|
||||
await this.getDashboardDataUseCase.execute(undefined, presenter);
|
||||
return presenter;
|
||||
async getDashboardData(): Promise<GetDashboardDataOutputDTO> {
|
||||
const result = await this.getDashboardDataUseCase.execute({});
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get dashboard data');
|
||||
}
|
||||
return this.getDashboardDataPresenter.getResponseModel();
|
||||
}
|
||||
|
||||
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsPresenter> {
|
||||
const presenter = new GetAnalyticsMetricsPresenter();
|
||||
await this.getAnalyticsMetricsUseCase.execute(undefined, presenter);
|
||||
return presenter;
|
||||
async getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
|
||||
const result = await this.getAnalyticsMetricsUseCase.execute({});
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to get analytics metrics');
|
||||
}
|
||||
return this.getAnalyticsMetricsPresenter.getResponseModel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ describe('GetAnalyticsMetricsPresenter', () => {
|
||||
presenter = new GetAnalyticsMetricsPresenter();
|
||||
});
|
||||
|
||||
it('maps use case output to DTO correctly', () => {
|
||||
it('maps output to DTO correctly', () => {
|
||||
const output: GetAnalyticsMetricsOutput = {
|
||||
pageViews: 1000,
|
||||
uniqueVisitors: 500,
|
||||
@@ -19,7 +19,9 @@ describe('GetAnalyticsMetricsPresenter', () => {
|
||||
|
||||
presenter.present(output);
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
const dto = presenter.getResponseModel();
|
||||
|
||||
expect(dto).toEqual({
|
||||
pageViews: 1000,
|
||||
uniqueVisitors: 500,
|
||||
averageSessionDuration: 300,
|
||||
@@ -27,19 +29,7 @@ describe('GetAnalyticsMetricsPresenter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('reset clears state and causes viewModel to throw', () => {
|
||||
const output: GetAnalyticsMetricsOutput = {
|
||||
pageViews: 1000,
|
||||
uniqueVisitors: 500,
|
||||
averageSessionDuration: 300,
|
||||
bounceRate: 0.4,
|
||||
};
|
||||
|
||||
presenter.present(output);
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
it('getResponseModel throws if not presented', () => {
|
||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { GetAnalyticsMetricsOutput } from '@core/analytics/application/use-cases/GetAnalyticsMetricsUseCase';
|
||||
import type { GetAnalyticsMetricsOutputDTO } from '../dtos/GetAnalyticsMetricsOutputDTO';
|
||||
|
||||
export class GetAnalyticsMetricsPresenter {
|
||||
private result: GetAnalyticsMetricsOutputDTO | null = null;
|
||||
export class GetAnalyticsMetricsPresenter implements UseCaseOutputPort<GetAnalyticsMetricsOutput> {
|
||||
private responseModel: GetAnalyticsMetricsOutputDTO | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: GetAnalyticsMetricsOutput): void {
|
||||
this.result = {
|
||||
pageViews: output.pageViews,
|
||||
uniqueVisitors: output.uniqueVisitors,
|
||||
averageSessionDuration: output.averageSessionDuration,
|
||||
bounceRate: output.bounceRate,
|
||||
present(result: GetAnalyticsMetricsOutput): void {
|
||||
this.responseModel = {
|
||||
pageViews: result.pageViews,
|
||||
uniqueVisitors: result.uniqueVisitors,
|
||||
averageSessionDuration: result.averageSessionDuration,
|
||||
bounceRate: result.bounceRate,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): GetAnalyticsMetricsOutputDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
getResponseModel(): GetAnalyticsMetricsOutputDTO {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ describe('GetDashboardDataPresenter', () => {
|
||||
presenter = new GetDashboardDataPresenter();
|
||||
});
|
||||
|
||||
it('maps use case output to DTO correctly', () => {
|
||||
it('maps output to DTO correctly', () => {
|
||||
const output: GetDashboardDataOutput = {
|
||||
totalUsers: 100,
|
||||
activeUsers: 50,
|
||||
@@ -19,7 +19,7 @@ describe('GetDashboardDataPresenter', () => {
|
||||
|
||||
presenter.present(output);
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
expect(presenter.getResponseModel()).toEqual({
|
||||
totalUsers: 100,
|
||||
activeUsers: 50,
|
||||
totalRaces: 20,
|
||||
@@ -27,19 +27,7 @@ describe('GetDashboardDataPresenter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('reset clears state and causes viewModel to throw', () => {
|
||||
const output: GetDashboardDataOutput = {
|
||||
totalUsers: 100,
|
||||
activeUsers: 50,
|
||||
totalRaces: 20,
|
||||
totalLeagues: 5,
|
||||
};
|
||||
|
||||
presenter.present(output);
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
it('getResponseModel throws if not presented', () => {
|
||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { GetDashboardDataOutput } from '@core/analytics/application/use-cases/GetDashboardDataUseCase';
|
||||
import type { GetDashboardDataOutputDTO } from '../dtos/GetDashboardDataOutputDTO';
|
||||
|
||||
export class GetDashboardDataPresenter {
|
||||
private result: GetDashboardDataOutputDTO | null = null;
|
||||
export class GetDashboardDataPresenter implements UseCaseOutputPort<GetDashboardDataOutput> {
|
||||
private responseModel: GetDashboardDataOutputDTO | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: GetDashboardDataOutput): void {
|
||||
this.result = {
|
||||
totalUsers: output.totalUsers,
|
||||
activeUsers: output.activeUsers,
|
||||
totalRaces: output.totalRaces,
|
||||
totalLeagues: output.totalLeagues,
|
||||
present(result: GetDashboardDataOutput): void {
|
||||
this.responseModel = {
|
||||
totalUsers: result.totalUsers,
|
||||
activeUsers: result.activeUsers,
|
||||
totalRaces: result.totalRaces,
|
||||
totalLeagues: result.totalLeagues,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): GetDashboardDataOutputDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
getResponseModel(): GetDashboardDataOutputDTO {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,31 +9,21 @@ describe('RecordEngagementPresenter', () => {
|
||||
presenter = new RecordEngagementPresenter();
|
||||
});
|
||||
|
||||
it('maps use case output to DTO correctly', () => {
|
||||
it('maps output to DTO correctly', () => {
|
||||
const output: RecordEngagementOutput = {
|
||||
eventId: 'event-123',
|
||||
engagementWeight: 10,
|
||||
} as RecordEngagementOutput;
|
||||
};
|
||||
|
||||
presenter.present(output);
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
expect(presenter.getResponseModel()).toEqual({
|
||||
eventId: 'event-123',
|
||||
engagementWeight: 10,
|
||||
});
|
||||
});
|
||||
|
||||
it('reset clears state and causes viewModel to throw', () => {
|
||||
const output: RecordEngagementOutput = {
|
||||
eventId: 'event-123',
|
||||
engagementWeight: 10,
|
||||
} as RecordEngagementOutput;
|
||||
|
||||
presenter.present(output);
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
it('getResponseModel throws if not presented', () => {
|
||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { RecordEngagementOutput } from '@core/analytics/application/use-cases/RecordEngagementUseCase';
|
||||
import type { RecordEngagementOutputDTO } from '../dtos/RecordEngagementOutputDTO';
|
||||
|
||||
export class RecordEngagementPresenter {
|
||||
private result: RecordEngagementOutputDTO | null = null;
|
||||
export class RecordEngagementPresenter implements UseCaseOutputPort<RecordEngagementOutput> {
|
||||
private responseModel: RecordEngagementOutputDTO | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: RecordEngagementOutput): void {
|
||||
this.result = {
|
||||
eventId: output.eventId,
|
||||
engagementWeight: output.engagementWeight,
|
||||
present(result: RecordEngagementOutput): void {
|
||||
this.responseModel = {
|
||||
eventId: result.eventId,
|
||||
engagementWeight: result.engagementWeight,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): RecordEngagementOutputDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
getResponseModel(): RecordEngagementOutputDTO {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,28 +9,19 @@ describe('RecordPageViewPresenter', () => {
|
||||
presenter = new RecordPageViewPresenter();
|
||||
});
|
||||
|
||||
it('maps use case output to DTO correctly', () => {
|
||||
it('maps output to DTO correctly', () => {
|
||||
const output: RecordPageViewOutput = {
|
||||
pageViewId: 'pv-123',
|
||||
} as RecordPageViewOutput;
|
||||
};
|
||||
|
||||
presenter.present(output);
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
expect(presenter.getResponseModel()).toEqual({
|
||||
pageViewId: 'pv-123',
|
||||
});
|
||||
});
|
||||
|
||||
it('reset clears state and causes viewModel to throw', () => {
|
||||
const output: RecordPageViewOutput = {
|
||||
pageViewId: 'pv-123',
|
||||
} as RecordPageViewOutput;
|
||||
|
||||
presenter.present(output);
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
it('getResponseModel throws if not presented', () => {
|
||||
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { RecordPageViewOutput } from '@core/analytics/application/use-cases/RecordPageViewUseCase';
|
||||
import type { RecordPageViewOutputDTO } from '../dtos/RecordPageViewOutputDTO';
|
||||
|
||||
export class RecordPageViewPresenter {
|
||||
private result: RecordPageViewOutputDTO | null = null;
|
||||
export class RecordPageViewPresenter implements UseCaseOutputPort<RecordPageViewOutput> {
|
||||
private responseModel: RecordPageViewOutputDTO | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: RecordPageViewOutput): void {
|
||||
this.result = {
|
||||
pageViewId: output.pageViewId,
|
||||
present(result: RecordPageViewOutput): void {
|
||||
this.responseModel = {
|
||||
pageViewId: result.pageViewId,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): RecordPageViewOutputDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
getResponseModel(): RecordPageViewOutputDTO {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user