add tests to core

This commit is contained in:
2025-12-23 19:26:59 +01:00
parent 14d390b831
commit 7290fe69b5
72 changed files with 861 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
import { AnalyticsSnapshot, type SnapshotEntityType, type SnapshotPeriod } from '@core/analytics/domain/entities/AnalyticsSnapshot';
describe('AnalyticsSnapshot', () => {
const startDate = new Date('2025-01-01T12:00:00.000Z');
const endDate = new Date('2025-01-02T12:00:00.000Z');
const validMetrics = {
pageViews: 10,
uniqueVisitors: 3,
avgSessionDuration: 180_000,
bounceRate: 25,
engagementScore: 60,
sponsorClicks: 2,
sponsorUrlClicks: 1,
socialShares: 4,
leagueJoins: 0,
raceRegistrations: 0,
exposureValue: 0,
};
const createValid = (overrides?: Partial<Parameters<typeof AnalyticsSnapshot.create>[0]>) => {
return AnalyticsSnapshot.create({
id: 'snapshot-1',
entityType: 'league',
entityId: 'entity_123',
period: 'monthly',
startDate,
endDate,
metrics: validMetrics,
...overrides,
});
};
it('creates a snapshot with trimmed entityId', () => {
const snapshot = createValid({ entityId: ' entity_456 ' });
expect(snapshot.entityId).toBe('entity_456');
});
it('throws if id is missing', () => {
expect(() => createValid({ id: '' })).toThrow(Error);
expect(() => createValid({ id: ' ' })).toThrow(Error);
});
it('throws if entityType is missing', () => {
expect(() => createValid({ entityType: undefined as unknown as SnapshotEntityType })).toThrow(Error);
});
it('throws if entityId is missing', () => {
expect(() => createValid({ entityId: '' })).toThrow(Error);
expect(() => createValid({ entityId: ' ' })).toThrow(Error);
});
it('throws if period is missing', () => {
expect(() => createValid({ period: undefined as unknown as SnapshotPeriod })).toThrow(Error);
});
it('throws if endDate is before startDate', () => {
expect(() => createValid({ startDate: endDate, endDate: startDate })).toThrow(Error);
});
it('createEmpty initializes metrics to zero', () => {
const snapshot = AnalyticsSnapshot.createEmpty(
'snapshot-empty',
'league',
'entity_1',
'weekly',
startDate,
endDate,
);
expect(snapshot.metrics.pageViews).toBe(0);
expect(snapshot.metrics.uniqueVisitors).toBe(0);
expect(snapshot.metrics.sponsorClicks).toBe(0);
expect(snapshot.metrics.sponsorUrlClicks).toBe(0);
expect(snapshot.metrics.socialShares).toBe(0);
});
it('calculates exposure score using weighted metrics', () => {
const snapshot = createValid({
metrics: {
...validMetrics,
pageViews: 10,
uniqueVisitors: 3,
sponsorClicks: 2,
sponsorUrlClicks: 1,
socialShares: 4,
},
});
expect(snapshot.calculateExposureScore()).toBe(81);
});
it('returns trust indicator high/medium/low based on thresholds', () => {
expect(
createValid({
metrics: { ...validMetrics, bounceRate: 25, avgSessionDuration: 180_000, engagementScore: 60 },
}).getTrustIndicator(),
).toBe('high');
expect(
createValid({
metrics: { ...validMetrics, bounceRate: 50, avgSessionDuration: 60_000, engagementScore: 30 },
}).getTrustIndicator(),
).toBe('medium');
expect(
createValid({
metrics: { ...validMetrics, bounceRate: 90, avgSessionDuration: 10_000, engagementScore: 0 },
}).getTrustIndicator(),
).toBe('low');
});
it('formats period labels (monthly includes year)', () => {
expect(createValid({ period: 'monthly' }).getPeriodLabel()).toBe('Jan 1, 2025 - Jan 2, 2025');
expect(createValid({ period: 'weekly' }).getPeriodLabel()).toBe('Jan 1 - Jan 2');
});
});

View File

@@ -0,0 +1,129 @@
import { EngagementEvent, type EngagementAction, type EngagementEntityType } from '@core/analytics/domain/entities/EngagementEvent';
describe('EngagementEvent', () => {
const baseProps = (): Parameters<typeof EngagementEvent.create>[0] => ({
id: 'event-1',
action: 'click_sponsor_logo',
entityType: 'league',
entityId: 'entity_123',
actorType: 'anonymous',
sessionId: 'session-1',
metadata: { source: 'test', count: 1, ok: true },
});
it('creates an event with trimmed entityId', () => {
const event = EngagementEvent.create({
id: 'event-1',
action: 'view_schedule',
entityType: 'league',
entityId: ' entity_456 ',
actorType: 'driver',
actorId: 'driver-1',
sessionId: 'session-1',
});
expect(event.entityId).toBe('entity_456');
});
it('throws if id is missing', () => {
expect(() => EngagementEvent.create({ ...baseProps(), id: '' })).toThrow(Error);
expect(() => EngagementEvent.create({ ...baseProps(), id: ' ' })).toThrow(Error);
});
it('throws if action is missing', () => {
expect(() =>
EngagementEvent.create({ ...baseProps(), action: undefined } as unknown as Parameters<
typeof EngagementEvent.create
>[0]),
).toThrow(Error);
});
it('throws if entityType is missing', () => {
expect(() =>
EngagementEvent.create({ ...baseProps(), entityType: undefined as unknown as EngagementEntityType }),
).toThrow(Error);
});
it('throws if entityId is missing', () => {
expect(() => EngagementEvent.create({ ...baseProps(), entityId: '' })).toThrow(Error);
expect(() => EngagementEvent.create({ ...baseProps(), entityId: ' ' })).toThrow(Error);
});
it('throws if sessionId is missing', () => {
expect(() => EngagementEvent.create({ ...baseProps(), sessionId: '' })).toThrow(Error);
expect(() => EngagementEvent.create({ ...baseProps(), sessionId: ' ' })).toThrow(Error);
});
it('detects sponsor engagement', () => {
expect(EngagementEvent.create(baseProps()).isSponsorEngagement()).toBe(true);
const sponsorEntity = EngagementEvent.create({
id: 'event-2',
action: 'view_schedule',
entityType: 'sponsor',
entityId: 'sponsor-1',
actorType: 'anonymous',
sessionId: 'session-2',
});
expect(sponsorEntity.isSponsorEngagement()).toBe(true);
const nonSponsor = EngagementEvent.create({
id: 'event-3',
action: 'view_standings',
entityType: 'league',
entityId: 'league-1',
actorType: 'anonymous',
sessionId: 'session-3',
});
expect(nonSponsor.isSponsorEngagement()).toBe(false);
});
it('detects conversion events', () => {
const join = EngagementEvent.create({
id: 'event-4',
action: 'join_league',
entityType: 'league',
entityId: 'league-1',
actorType: 'driver',
actorId: 'driver-1',
sessionId: 'session-4',
});
expect(join.isConversionEvent()).toBe(true);
const view = EngagementEvent.create({
id: 'event-5',
action: 'view_schedule',
entityType: 'league',
entityId: 'league-1',
actorType: 'anonymous',
sessionId: 'session-5',
});
expect(view.isConversionEvent()).toBe(false);
});
it('returns configured engagement weights', () => {
const weights: Array<{ action: EngagementAction; expected: number }> = [
{ action: 'click_sponsor_logo', expected: 2 },
{ action: 'click_sponsor_url', expected: 5 },
{ action: 'download_livery_pack', expected: 3 },
{ action: 'join_league', expected: 10 },
{ action: 'register_race', expected: 8 },
{ action: 'view_standings', expected: 1 },
{ action: 'view_schedule', expected: 1 },
{ action: 'share_social', expected: 4 },
{ action: 'contact_sponsor', expected: 15 },
];
for (const { action, expected } of weights) {
const event = EngagementEvent.create({
id: `event-${action}`,
action,
entityType: 'league',
entityId: 'league-1',
actorType: 'anonymous',
sessionId: 'session-1',
});
expect(event.getEngagementWeight()).toBe(expected);
}
});
});

View File

@@ -0,0 +1,76 @@
import { PageView, type EntityType } from '@core/analytics/domain/entities/PageView';
describe('PageView', () => {
const now = new Date('2025-01-01T12:00:00.000Z');
const createValid = (overrides?: Partial<Parameters<typeof PageView.create>[0]>) => {
return PageView.create({
id: 'pv_1',
entityType: 'league',
entityId: 'entity_123',
visitorType: 'anonymous',
sessionId: 'session_1',
timestamp: now,
...overrides,
});
};
it('creates a PageView and exposes value-object values', () => {
const pv = createValid({
id: ' pv_123 ',
entityId: ' entity_456 ',
sessionId: ' session_789 ',
});
expect(pv.id).toBe('pv_123');
expect(pv.entityId).toBe('entity_456');
expect(pv.sessionId).toBe('session_789');
expect(pv.timestamp).toEqual(now);
});
it('throws if id is missing', () => {
expect(() => createValid({ id: '' })).toThrow(Error);
expect(() => createValid({ id: ' ' })).toThrow(Error);
});
it('throws if entityType is missing', () => {
expect(() => createValid({ entityType: undefined as unknown as EntityType })).toThrow(Error);
});
it('throws if entityId is missing', () => {
expect(() => createValid({ entityId: '' })).toThrow(Error);
expect(() => createValid({ entityId: ' ' })).toThrow(Error);
});
it('throws if sessionId is missing', () => {
expect(() => createValid({ sessionId: '' })).toThrow(Error);
expect(() => createValid({ sessionId: ' ' })).toThrow(Error);
});
it('withDuration returns a new PageView with duration', () => {
const pv = createValid({ visitorId: 'visitor-1' });
const pv2 = pv.withDuration(12_345);
expect(pv2).not.toBe(pv);
expect(pv2.durationMs).toBe(12_345);
expect(pv2.id).toBe(pv.id);
expect(pv2.sessionId).toBe(pv.sessionId);
});
it('withDuration throws for negative durations', () => {
const pv = createValid();
expect(() => pv.withDuration(-1)).toThrow(Error);
});
it('isMeaningfulView is true for 5s+ duration', () => {
expect(createValid({ durationMs: 4999 }).isMeaningfulView()).toBe(false);
expect(createValid({ durationMs: 5000 }).isMeaningfulView()).toBe(true);
});
it('isExternalReferral checks referrer domain', () => {
expect(createValid().isExternalReferral()).toBe(false);
expect(createValid({ referrer: 'https://gridpilot.example/path' }).isExternalReferral()).toBe(false);
expect(createValid({ referrer: 'https://example.com/path' }).isExternalReferral()).toBe(true);
});
});