Files
gridpilot.gg/core/analytics/application/use-cases/GetEntityAnalyticsQuery.test.ts
2025-12-20 12:55:07 +01:00

97 lines
3.3 KiB
TypeScript

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();
});
});