import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { newFixture, type Fixture } from '../helpers/fixture.js'; let fixture: Fixture; beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); }); afterEach(async () => { await fixture.cleanup(); }); describe('historical metrics', () => { it('weights occupancy by time and excludes failed checks and collection gaps', async () => { const { recordMetrics, readMetrics } = await import('../../src/lib/server/fleet/metrics.js'); const sample = { server: 'one', game: 't5', map: 'Moon', players: 2, capacity: 4 }; const at = Date.UTC(2026, 0, 2, 12); recordMetrics([sample], at); recordMetrics([{ ...sample, players: 0 }], at + 30_000); recordMetrics([{ ...sample, players: null }], at + 60_000); const result = readMetrics(1, at + 3600_000); expect(result.summary.active * 3600).toBeCloseTo(30); expect(result.summary.idle * 3600).toBeCloseTo(30); expect(result.summary.unknown * 3600).toBeCloseTo(45); expect(result.summary.playerHours * 3600).toBeCloseTo(60); expect(result.points.reduce((sum, p) => sum + p.observed, 0)).toBeCloseTo(result.summary.observed); }); it('only counts map saturation when every lobby has known capacity and is full', async () => { const { recordMetrics, readMetrics } = await import('../../src/lib/server/fleet/metrics.js'); const at = Date.UTC(2026, 0, 2, 12); const sample = { server: 'one', game: 't5', map: 'Moon', players: 4, capacity: 4 }; recordMetrics([sample, { ...sample, server: 'two' }], at); recordMetrics([sample, { ...sample, server: 'two', players: 2 }], at + 30_000); recordMetrics([sample, { ...sample, server: 'two', capacity: null }], at + 60_000); recordMetrics([sample, { ...sample, server: 'two', players: null }], at + 90_000); const result = readMetrics(1, at + 120_000); expect(result.maps[0].full * 3600).toBeCloseTo(30); expect(result.maps[0].capacityObserved * 3600).toBeCloseTo(60); }); it('clips the reporting window and splits observations across UTC chart boundaries', async () => { const { recordMetrics, readMetrics } = await import('../../src/lib/server/fleet/metrics.js'); const midnight = Date.UTC(2026, 0, 2); const sample = { server: 'one', game: 't5', map: 'Moon', players: 1, capacity: 4 }; recordMetrics([sample], midnight - 15_000); let result = readMetrics(1, midnight + 15_000); expect(result.points.filter(p => p.active > 0).map(p => p.active * 3600)).toEqual([15, 15]); result = readMetrics(1, midnight + 86400_000); expect(result.summary.active * 3600).toBeCloseTo(30); expect(result.points.reduce((sum, p) => sum + p.active, 0)).toBeCloseTo(result.summary.active); }); it('retains history without a live server and prunes observations older than 31 days', async () => { const { recordMetrics, readMetrics } = await import('../../src/lib/server/fleet/metrics.js'); const { db } = await import('../../src/lib/server/db/index.js'); const at = Date.UTC(2026, 0, 2); const sample = { server: 'removed', game: 't6', map: 'Town', players: 1, capacity: null }; recordMetrics([sample], at); expect(readMetrics(1, at + 30_000).servers[0].server).toBe('removed'); recordMetrics([sample], at + 32 * 86400_000); expect(db().prepare('SELECT COUNT(*) AS n FROM metric_samples').get()).toEqual({ n: 1 }); }); });