fix: aggregate presence samples every five minutes with seven-day retention
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import type { AnalyticsStore } from '../src/server/analytics-store.js';
|
||||
import { Analytics } from '../src/server/analytics.js';
|
||||
import { ManualClock } from './helpers.js';
|
||||
|
||||
test('ten-second observations preserve a brief peak in one five-minute write and flush the final partial interval', async () => {
|
||||
const clock = new ManualClock();
|
||||
const writes: Parameters<AnalyticsStore['sample']>[] = [];
|
||||
const store = {
|
||||
sample: async (...args: Parameters<AnalyticsStore['sample']>) => { writes.push(args); },
|
||||
cleanup: async () => {},
|
||||
} as unknown as AnalyticsStore;
|
||||
const analytics = new Analytics(store, clock);
|
||||
try {
|
||||
analytics.states.set('visitor', { visible: true, lastAt: clock.now(), metadata: { device: 'pc', browser: 'Other', source: 'direct_or_unknown', invitation: false } });
|
||||
await clock.advance(290000);
|
||||
assert.equal(writes.length, 0);
|
||||
assert.equal(analytics.live().active, 0);
|
||||
await clock.advance(10000);
|
||||
assert.equal(writes.length, 1);
|
||||
assert.equal(writes[0][1].active, 1);
|
||||
assert.equal(writes[0][4], 30);
|
||||
await clock.advance(300000);
|
||||
assert.equal(writes.length, 2);
|
||||
assert.equal(writes[1][1].active, 0);
|
||||
assert.equal(writes[1][4], 30);
|
||||
await clock.advance(20000);
|
||||
assert.equal(writes.length, 2);
|
||||
await analytics.close();
|
||||
assert.equal(writes.length, 3);
|
||||
assert.equal(writes[2][4], 2);
|
||||
await analytics.close();
|
||||
assert.equal(writes.length, 3);
|
||||
} finally { await analytics.close(); }
|
||||
});
|
||||
+28
-1
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { MongoClient } from 'mongodb';
|
||||
import { createAnalyticsStore } from '../src/server/analytics-store.js';
|
||||
import { kstDay, ratio, retention } from '../src/server/analytics-report.js';
|
||||
import { getAnalyticsReport, kstDay, ratio, retention } from '../src/server/analytics-report.js';
|
||||
import { createStatsStore } from '../src/server/stats-store.js';
|
||||
import { RETENTION_MS, VISIT_SESSION_MS } from '../constants/analytics.js';
|
||||
|
||||
@@ -19,6 +19,33 @@ test('KST boundaries, zero denominators, calendar cohorts', () => {
|
||||
|
||||
});
|
||||
|
||||
test('presence retention shortens legacy expiry and reports count raw observations alongside aggregates', { skip: !process.env.TEST_MONGODB_URI }, async () => {
|
||||
const stats = await createStatsStore({ mongoUri: process.env.TEST_MONGODB_URI!, databaseName: `dragon_ball_test_samples_${randomUUID().replaceAll('-', '')}` });
|
||||
try {
|
||||
const now = new Date(), at = new Date(+now - 60000), old = new Date(+now - 8 * 86400000);
|
||||
const samples = stats.db.collection('analytics_presence_samples');
|
||||
await samples.insertMany([
|
||||
{ occurredAt: at, active: 2, environment: 'production', source: 'activity_reports', expiresAt: new Date(+at + RETENTION_MS) },
|
||||
{ occurredAt: old, active: 99, environment: 'production', source: 'activity_reports', expiresAt: new Date(+old + RETENTION_MS) },
|
||||
]);
|
||||
const store = await createAnalyticsStore(stats.db);
|
||||
assert.equal(+(await samples.findOne({ occurredAt: at }))!.expiresAt, +at + 604800000);
|
||||
await store.sample(at, { active: 4, connected: null, waitingRooms: null, playingPeople: null, matches: null, source: 'activity_reports' }, 'production',
|
||||
{ restartedAt: new Date(+at - 300000), failures: 2, dropped: 1, rateLimited: 3 }, 30);
|
||||
const aggregate = await samples.findOne({ sampleCount: 30 });
|
||||
assert.equal(+aggregate!.expiresAt, +at + 604800000);
|
||||
const guest = await stats.createGuest();
|
||||
await store.insert({ _id: 'old-visit', playerId: guest.playerId, occurredAt: old, eventType: 'page_view', environment: 'production', schemaVersion: 1, appVersion: '0.1.0' });
|
||||
const report = await getAnalyticsReport(stats.db, { start: new Date(+now - 30 * 86400000), end: now }, now);
|
||||
assert.deepEqual(report.sampledPeak, { count: 31, active: 4 });
|
||||
assert.deepEqual(report.historicalCollection, { failures: 2, dropped: 1, rateLimited: 3 });
|
||||
assert.equal(report.overview.mau.visitors, 1);
|
||||
assert.equal(+(await stats.db.collection('analytics_events').findOne({ _id: 'old-visit' as any }))!.expiresAt, +old + RETENTION_MS);
|
||||
await createAnalyticsStore(stats.db);
|
||||
assert.equal(+(await samples.findOne({ sampleCount: 30 }))!.expiresAt, +at + 604800000);
|
||||
} finally { await stats.db.dropDatabase(); await stats.close(); }
|
||||
});
|
||||
|
||||
test('atomic visit session rotation, duplicate collection and safe metadata on actual MongoDB', { skip: !process.env.TEST_MONGODB_URI }, async () => {
|
||||
const databaseName = `dragon_ball_test_analytics_${randomUUID().replaceAll('-', '')}`;
|
||||
const stats = await createStatsStore({ mongoUri: process.env.TEST_MONGODB_URI!, databaseName });
|
||||
|
||||
Reference in New Issue
Block a user