resolve todos in website

This commit is contained in:
2025-12-20 12:55:07 +01:00
parent 20588e1c0b
commit 92be9d2e1b
56 changed files with 2476 additions and 78 deletions

View File

@@ -0,0 +1,77 @@
import { describe, it, expect, vi, type Mock } from 'vitest';
import { GetAnalyticsMetricsUseCase, type GetAnalyticsMetricsInput } from './GetAnalyticsMetricsUseCase';
import type { IPageViewRepository } from '../../domain/repositories/IPageViewRepository';
import type { Logger } from '@core/shared/application';
describe('GetAnalyticsMetricsUseCase', () => {
let pageViewRepository: {
save: Mock;
findById: Mock;
findByEntityId: Mock;
findBySessionId: Mock;
countByEntityId: Mock;
getUniqueVisitorsCount: Mock;
getAverageSessionDuration: Mock;
getBounceRate: Mock;
};
let logger: Logger;
let useCase: GetAnalyticsMetricsUseCase;
beforeEach(() => {
pageViewRepository = {
save: vi.fn(),
findById: vi.fn(),
findByEntityId: vi.fn(),
findBySessionId: vi.fn(),
countByEntityId: vi.fn(),
getUniqueVisitorsCount: vi.fn(),
getAverageSessionDuration: vi.fn(),
getBounceRate: vi.fn(),
} as unknown as IPageViewRepository as any;
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
useCase = new GetAnalyticsMetricsUseCase(
pageViewRepository as unknown as IPageViewRepository,
logger,
);
});
it('returns default metrics and logs retrieval when no input is provided', async () => {
const result = await useCase.execute();
expect(result).toEqual({
pageViews: 0,
uniqueVisitors: 0,
averageSessionDuration: 0,
bounceRate: 0,
});
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
});
it('uses provided date range and logs error when execute throws', async () => {
const input: GetAnalyticsMetricsInput = {
startDate: new Date('2024-01-01'),
endDate: new Date('2024-01-31'),
};
const erroringUseCase = new GetAnalyticsMetricsUseCase(
pageViewRepository as unknown as IPageViewRepository,
logger,
);
// Simulate an error by temporarily spying on logger.info to throw
(logger.info as unknown as Mock).mockImplementation(() => {
throw new Error('Logging failed');
});
await expect(erroringUseCase.execute(input)).rejects.toThrow('Logging failed');
expect((logger.error as unknown as Mock)).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,32 @@
import { describe, it, expect, vi } from 'vitest';
import { GetDashboardDataUseCase } from './GetDashboardDataUseCase';
import type { Logger } from '@core/shared/application';
describe('GetDashboardDataUseCase', () => {
let logger: Logger;
let useCase: GetDashboardDataUseCase;
beforeEach(() => {
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
useCase = new GetDashboardDataUseCase(logger);
});
it('returns placeholder dashboard metrics and logs retrieval', async () => {
const result = await useCase.execute();
expect(result).toEqual({
totalUsers: 0,
activeUsers: 0,
totalRaces: 0,
totalLeagues: 0,
});
expect((logger.info as unknown as ReturnType<typeof vi.fn>)).toHaveBeenCalled();
});
});

View File

@@ -14,7 +14,7 @@ export class GetDashboardDataUseCase {
private readonly logger: Logger,
) {}
async execute(_input: GetDashboardDataInput = {}): Promise<GetDashboardDataOutput> {
async execute(): Promise<GetDashboardDataOutput> {
try {
// Placeholder implementation - would need repositories from identity and racing domains
const totalUsers = 0;

View File

@@ -0,0 +1,96 @@
import { describe, it, expect, vi, type Mock } from 'vitest';
import { GetEntityAnalyticsQuery, type GetEntityAnalyticsInput } from './GetEntityAnalyticsQuery';
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
import type { IAnalyticsSnapshotRepository } from '@core/analytics/domain/repositories/IAnalyticsSnapshotRepository';
import type { Logger } from '@core/shared/application';
import type { EntityType } from '../../domain/types/PageView';
describe('GetEntityAnalyticsQuery', () => {
let pageViewRepository: {
countByEntityId: Mock;
countUniqueVisitors: Mock;
};
let engagementRepository: {
getSponsorClicksForEntity: Mock;
};
let snapshotRepository: IAnalyticsSnapshotRepository;
let logger: Logger;
let useCase: GetEntityAnalyticsQuery;
beforeEach(() => {
pageViewRepository = {
countByEntityId: vi.fn(),
countUniqueVisitors: vi.fn(),
} as unknown as IPageViewRepository as any;
engagementRepository = {
getSponsorClicksForEntity: vi.fn(),
} as unknown as IEngagementRepository as any;
snapshotRepository = {} as IAnalyticsSnapshotRepository;
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
useCase = new GetEntityAnalyticsQuery(
pageViewRepository as unknown as IPageViewRepository,
engagementRepository as unknown as IEngagementRepository,
snapshotRepository,
logger,
);
});
it('aggregates entity analytics and returns summary and trends', async () => {
const input: GetEntityAnalyticsInput = {
entityType: 'league' as EntityType,
entityId: 'league-1',
period: 'weekly',
};
pageViewRepository.countByEntityId
.mockResolvedValueOnce(100) // current period total page views
.mockResolvedValueOnce(150); // previous period full page views
pageViewRepository.countUniqueVisitors
.mockResolvedValueOnce(40) // current period uniques
.mockResolvedValueOnce(60); // previous period full uniques
engagementRepository.getSponsorClicksForEntity
.mockResolvedValueOnce(10) // current clicks
.mockResolvedValueOnce(5); // for engagement score
const result = await useCase.execute(input);
expect(result.entityId).toBe(input.entityId);
expect(result.entityType).toBe(input.entityType);
expect(result.summary.totalPageViews).toBe(100);
expect(result.summary.uniqueVisitors).toBe(40);
expect(result.summary.sponsorClicks).toBe(10);
expect(typeof result.summary.engagementScore).toBe('number');
expect(result.summary.exposureValue).toBeGreaterThan(0);
expect(result.trends.pageViewsChange).toBeDefined();
expect(result.trends.uniqueVisitorsChange).toBeDefined();
expect(result.period.start).toBeInstanceOf(Date);
expect(result.period.end).toBeInstanceOf(Date);
});
it('propagates repository errors', async () => {
const input: GetEntityAnalyticsInput = {
entityType: 'league' as EntityType,
entityId: 'league-1',
};
pageViewRepository.countByEntityId.mockRejectedValue(new Error('DB error'));
await expect(useCase.execute(input)).rejects.toThrow('DB error');
expect((logger.error as unknown as Mock)).toHaveBeenCalled();
});
});

View File

@@ -5,8 +5,7 @@
* Returns metrics formatted for display to sponsors and admins.
*/
import type { AsyncUseCase } from '@core/shared/application';
import type { Logger } from '@core/shared/application';
import type { AsyncUseCase , Logger } from '@core/shared/application';
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
import type { IAnalyticsSnapshotRepository } from '@core/analytics/domain/repositories/IAnalyticsSnapshotRepository';

View File

@@ -0,0 +1,75 @@
import { describe, it, expect, vi, type Mock } from 'vitest';
import { RecordEngagementUseCase, type RecordEngagementInput } from './RecordEngagementUseCase';
import type { IEngagementRepository } from '../../domain/repositories/IEngagementRepository';
import { EngagementEvent } from '../../domain/entities/EngagementEvent';
import type { Logger } from '@core/shared/application';
import type { EngagementAction, EngagementEntityType } from '../../domain/types/EngagementEvent';
describe('RecordEngagementUseCase', () => {
let engagementRepository: {
save: Mock;
};
let logger: Logger;
let useCase: RecordEngagementUseCase;
beforeEach(() => {
engagementRepository = {
save: vi.fn(),
} as unknown as IEngagementRepository as any;
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
useCase = new RecordEngagementUseCase(
engagementRepository as unknown as IEngagementRepository,
logger,
);
});
it('creates and saves an EngagementEvent and returns its id and weight', async () => {
const input: RecordEngagementInput = {
action: 'view' as EngagementAction,
entityType: 'league' as EngagementEntityType,
entityId: 'league-1',
actorId: 'driver-1',
actorType: 'driver',
sessionId: 'session-1',
metadata: { foo: 'bar' },
};
engagementRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(input);
expect(engagementRepository.save).toHaveBeenCalledTimes(1);
const saved = (engagementRepository.save as unknown as Mock).mock.calls[0][0] as EngagementEvent;
expect(saved).toBeInstanceOf(EngagementEvent);
expect(saved.id).toBeDefined();
expect(saved.entityId).toBe(input.entityId);
expect(saved.entityType).toBe(input.entityType);
expect(result.eventId).toBe(saved.id);
expect(typeof result.engagementWeight).toBe('number');
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
});
it('logs and rethrows when repository save fails', async () => {
const input: RecordEngagementInput = {
action: 'view' as EngagementAction,
entityType: 'league' as EngagementEntityType,
entityId: 'league-1',
actorType: 'anonymous',
sessionId: 'session-1',
};
const error = new Error('DB error');
engagementRepository.save.mockRejectedValue(error);
await expect(useCase.execute(input)).rejects.toThrow('DB error');
expect((logger.error as unknown as Mock)).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,74 @@
import { describe, it, expect, vi, type Mock } from 'vitest';
import { RecordPageViewUseCase, type RecordPageViewInput } from './RecordPageViewUseCase';
import type { IPageViewRepository } from '../../domain/repositories/IPageViewRepository';
import { PageView } from '../../domain/entities/PageView';
import type { Logger } from '@core/shared/application';
import type { EntityType, VisitorType } from '../../domain/types/PageView';
describe('RecordPageViewUseCase', () => {
let pageViewRepository: {
save: Mock;
};
let logger: Logger;
let useCase: RecordPageViewUseCase;
beforeEach(() => {
pageViewRepository = {
save: vi.fn(),
} as unknown as IPageViewRepository as any;
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
useCase = new RecordPageViewUseCase(
pageViewRepository as unknown as IPageViewRepository,
logger,
);
});
it('creates and saves a PageView and returns its id', async () => {
const input: RecordPageViewInput = {
entityType: 'league' as EntityType,
entityId: 'league-1',
visitorId: 'visitor-1',
visitorType: 'anonymous' as VisitorType,
sessionId: 'session-1',
referrer: 'https://example.com',
userAgent: 'jest',
country: 'US',
};
pageViewRepository.save.mockResolvedValue(undefined);
const result = await useCase.execute(input);
expect(pageViewRepository.save).toHaveBeenCalledTimes(1);
const saved = (pageViewRepository.save as unknown as Mock).mock.calls[0][0] as PageView;
expect(saved).toBeInstanceOf(PageView);
expect(saved.id).toBeDefined();
expect(saved.entityId).toBe(input.entityId);
expect(saved.entityType).toBe(input.entityType);
expect(result.pageViewId).toBe(saved.id);
expect((logger.info as unknown as Mock)).toHaveBeenCalled();
});
it('logs and rethrows when repository save fails', async () => {
const input: RecordPageViewInput = {
entityType: 'league' as EntityType,
entityId: 'league-1',
visitorType: 'anonymous' as VisitorType,
sessionId: 'session-1',
};
const error = new Error('DB error');
pageViewRepository.save.mockRejectedValue(error);
await expect(useCase.execute(input)).rejects.toThrow('DB error');
expect((logger.error as unknown as Mock)).toHaveBeenCalled();
});
});