71 lines
5.5 KiB
TypeScript
71 lines
5.5 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { randomUUID } from 'node:crypto';
|
|
import Fastify from 'fastify';
|
|
import { createStatsStore } from '../src/server/stats-store.js';
|
|
import { createGameSessionStore } from '../src/server/game-session-store.js';
|
|
import { createAnalyticsStore } from '../src/server/analytics-store.js';
|
|
import { Analytics } from '../src/server/analytics.js';
|
|
import { registerGameApi, registerAnalyticsApi } from '../src/server/game-api.js';
|
|
import { ManualClock } from './helpers.js';
|
|
import { getAnalyticsReport } from '../src/server/analytics-report.js';
|
|
|
|
test('HTTP analytics batches deduplicate, throttle activity writes and keep client game summaries separate', { skip: !process.env.TEST_MONGODB_URI }, async () => {
|
|
const stats = await createStatsStore({ mongoUri: process.env.TEST_MONGODB_URI!, databaseName: `dragon_ball_test_http_${randomUUID().replaceAll('-', '')}` });
|
|
const app = Fastify(), clock = new ManualClock();
|
|
const store = await createAnalyticsStore(stats.db);
|
|
let touches = 0; const originalTouch = store.touch;
|
|
store.touch = async (...args) => { touches++; return originalTouch(...args); };
|
|
const analytics = new Analytics(store, clock, 'test');
|
|
try {
|
|
const sessions = await createGameSessionStore(stats.db);
|
|
registerGameApi(app, sessions, stats); registerAnalyticsApi(app, sessions, analytics);
|
|
const session = (await app.inject({ method: 'POST', url: '/api/game-sessions', payload: { createRequestId: 'b'.repeat(43) } })).json().data;
|
|
const headers = { authorization: `Bearer ${session.token}`, 'x-game-session': session.gameSessionId };
|
|
const post = (url: string, payload: any) => app.inject({ method: 'POST', url, headers, payload });
|
|
const events = [{ eventId: 'page', eventType: 'page_view', screen: 'home', metadata: { device: 'mobile' } }, ...Array.from({ length: 19 }, (_, i) => ({ eventId: `screen${i}`, eventType: 'screen_view', screen: 'match' }))];
|
|
assert.equal((await post('/api/analytics/events', { events })).statusCode, 200);
|
|
assert.equal((await post('/api/analytics/events', { events })).statusCode, 200);
|
|
assert.equal(await stats.db.collection('analytics_events').countDocuments(), 20);
|
|
assert.equal(touches, 1);
|
|
for (let i = 0; i < 10; i++) await post('/api/analytics/activity', { visible: true });
|
|
assert.equal(touches, 1);
|
|
assert.equal(analytics.live().active, 1);
|
|
clock.time += 60000;
|
|
await post('/api/analytics/activity', { visible: true }); assert.equal(touches, 2);
|
|
for (let i = 0; i < 10; i++) await post('/api/analytics/activity', { visible: false });
|
|
assert.equal(touches, 3); assert.equal(analytics.live().active, 0);
|
|
const summary = await post('/api/analytics/events', { events: [{ eventId: 'start', eventType: 'game_started', matchId: 'm', mode: 'friend' }] });
|
|
assert.equal(summary.statusCode, 200);
|
|
const event = await stats.db.collection('analytics_events').findOne({ eventType: 'game_started' });
|
|
assert.equal(event?.source, 'client'); assert.equal(event?.gameSessionId, session.gameSessionId);
|
|
assert.equal(await stats.db.collection('matches').countDocuments(), 0);
|
|
assert.equal((await post('/api/analytics/events', { events: Array(21).fill(events[0]) })).statusCode, 400);
|
|
assert.equal((await post('/api/analytics/events', { events: [{ eventId: 'fake', eventType: 'match_started' }] })).statusCode, 400);
|
|
assert.equal((await app.inject({ method: 'POST', url: '/api/analytics/activity', payload: { visible: true } })).statusCode, 401);
|
|
} finally { await app.close(); await analytics.close(); try { await stats.db.dropDatabase(); } finally { await stats.close(); } }
|
|
});
|
|
|
|
test('current client summaries remain available without legacy server metrics', { skip: !process.env.TEST_MONGODB_URI }, async () => {
|
|
const stats = await createStatsStore({ mongoUri: process.env.TEST_MONGODB_URI!, databaseName: `dragon_ball_test_source_${randomUUID().replaceAll('-', '')}` });
|
|
const clock = new ManualClock(), store = await createAnalyticsStore(stats.db);
|
|
const analytics = new Analytics(store, clock);
|
|
try {
|
|
const guest = await stats.createGuest(), p = { playerId: guest.playerId, gameSessionId: 'game-session' };
|
|
await analytics.clientEvent(p, { eventId: 'page', eventType: 'page_view', screen: 'home' });
|
|
await analytics.clientEvent(p, { eventId: 'start', eventType: 'game_started', matchId: 'match', mode: 'friend' });
|
|
await analytics.clientEvent(p, { eventId: 'finish', eventType: 'game_finished', matchId: 'match', mode: 'friend', outcome: 'win', reason: 'hit' });
|
|
await analytics.clientEvent(p, { eventId: 'finish-after-refresh', eventType: 'game_finished', matchId: 'match', mode: 'friend', outcome: 'win', reason: 'hit' });
|
|
clock.time++;
|
|
const report = await getAnalyticsReport(stats.db, { start: new Date(clock.now() - 86400000), end: new Date(clock.now()) }, new Date(clock.now()));
|
|
assert.equal(report.clientGameplay.source, 'client');
|
|
assert.equal(report.clientGameplay.players, 1); assert.equal(report.clientGameplay.startedMatches, 1);
|
|
assert.equal(report.clientGameplay.resultReports, 1); assert.equal(report.clientGameplay.actions, null);
|
|
assert.equal(report.clientGameplay.turns, null); assert.equal(report.clientGameplay.serverVerifiedCompletion, null);
|
|
assert.ok(!('gameplay' in report));
|
|
assert.ok(!('sessionConversion' in report.overview));
|
|
assert.ok(!('visitOnly' in report.overview));
|
|
assert.ok(!('conversion' in report.acquisition.source[0]));
|
|
} finally { await analytics.close(); try { await stats.db.dropDatabase(); } finally { await stats.close(); } }
|
|
});
|