import { test } from 'node:test'; import assert from 'node:assert/strict'; import { randomBytes, randomUUID } from 'node:crypto'; import { MongoClient } from 'mongodb'; import { createGameServer } from '../src/server/create-server.js'; import { ManualClock } from './helpers.js'; test('admin keeps auth, visitor metadata and current game sessions without historical matches', { skip: !process.env.TEST_MONGODB_URI }, async () => { const databaseName = 'dragon_ball_test_admin_' + randomUUID().replaceAll('-', ''), clock = new ManualClock(); const app = await createGameServer({ mongoUri: process.env.TEST_MONGODB_URI!, databaseName, clientDirectory: 'dist/client', adminUser: 'operator', adminPassword: 'test-password-long', clock }); const client = await new MongoClient(process.env.TEST_MONGODB_URI!).connect(); const headers = { authorization: 'Basic ' + Buffer.from('operator:test-password-long').toString('base64') }; try { assert.equal((await app.inject('/api/admin/analytics/report')).statusCode, 401); assert.equal((await app.inject('/admin/analytics')).statusCode, 401); assert.equal((await app.inject('/admin/analytics/index.html')).statusCode, 404); assert.equal((await app.inject({ url: '/api/admin/analytics/report?start=bad', headers })).statusCode, 400); const create = async () => (await app.inject({ method: 'POST', url: '/api/game-sessions', payload: { createRequestId: randomBytes(32).toString('base64url') } })).json().data; const a = await create(), b = await create(); const post = (s: typeof a, url: string, payload: any) => app.inject({ method: 'POST', url, headers: { authorization: 'Bearer ' + s.token, 'x-game-session': s.gameSessionId }, payload }); const data = { eventId: 'page', eventType: 'page_view', screen: 'home', metadata: { device: 'mobile', browser: 'Safari 18', referrer: 'https://example.com/a?guestToken=secret', source: 'made-up' } }; await post(a, '/api/analytics/activity', { visible: true }); for (const s of [a, a, b]) assert.equal((await post(s, '/api/analytics/events', { events: [data] })).statusCode, 200); assert.equal((await post(a, '/api/analytics/events', { events: [{ ...data, eventId: 'bad', code: 'free-text-secret' }] })).statusCode, 400); await post(a, '/api/analytics/events', { events: [{ eventId: 'start', eventType: 'game_started', matchId: 'local-game', mode: 'bot' }] }); const match = (await post(a, '/api/game-session/matches', { requestId: 'register', matchId: 'local-game', mode: 'bot', expectedPreviousSeq: 0 })).json().data; await post(a, '/api/game-session/results', { ...match, outcome: 'win', reason: 'hit' }); clock.time += 100; const report = (await app.inject({ url: '/api/admin/analytics/report', headers })).json(); assert.equal(report.overview.visitors, 2); assert.equal(report.overview.pageViews, 2); assert.equal(report.clientGameplay.players, 1); assert.equal(report.clientGameplay.startedMatches, 1); assert.ok(!('gameplay' in report)); assert.equal(report.live.active, 2); assert.equal(report.live.connected, null); assert.equal(report.live.matches, null); assert.equal(report.live.playingPeople, null); assert.equal(report.live.source, 'activity_reports'); assert.ok(report.live.observedAt); await post(b, '/api/analytics/activity', { visible: false }); assert.equal((await app.inject({ url: '/api/admin/analytics/live', headers })).json().live.active, 1); const detail = (await app.inject({ url: '/api/admin/analytics/users/' + a.playerId, headers })).json(); assert.equal(detail.gameSessions.length, 1); assert.equal(detail.gameSessions[0].stats.bot.wins, 1); assert.ok(!('stats' in detail)); assert.ok(!('matches' in detail)); assert.ok(!('firstMatchAt' in detail)); const body = JSON.stringify(detail); assert.ok(!body.includes(a.token)); assert.ok(!body.includes('tokenHash')); assert.ok(!body.includes('guestTokenHash')); const events = JSON.stringify(await client.db(databaseName).collection('analytics_events').find().toArray()); assert.ok(!events.includes('guestToken')); assert.ok(!events.includes('made-up')); assert.ok(events.includes('example.com')); assert.ok(events.includes('other')); } finally { await app.close(); try { await client.db(databaseName).dropDatabase(); } finally { await client.close(); } } });