import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { currentCapacity } from '../../src/lib/server-capacity.js'; import { newFixture, type Fixture } from '../helpers/fixture.js'; const now = Date.parse('2026-10-07T12:00:00Z'); const iso = (offset = 0) => new Date(now + offset).toISOString(); const unavailable = { capacity: null, capacity_observed_at: null }; let fixture: Fixture; beforeEach(async () => { vi.resetModules(); vi.useFakeTimers({ toFake: ['Date'] }); vi.setSystemTime(now); fixture = await newFixture([]); }); afterEach(async () => { await fixture.cleanup(); vi.useRealTimers(); vi.restoreAllMocks(); }); async function setup(count = 1) { const { db } = await import('../../src/lib/server/db/index.js'); const database = db(); const names = Array.from({ length: count }, (_, i) => `server-${i}`); for (const name of names) database.prepare(`INSERT INTO servers(name,created_at,game_port,container_state, container_checked_at,last_checked_at,last_seen_at,last_players) VALUES(?, ?, 28960, 'running', ?, ?, ?, 3)`) .run(name, iso(-60_000), iso(), iso(), iso()); const { monitoringStatusWriter } = await import('../../src/lib/server/fleet/health-observations.js'); function publish(name: string, capacity: number | null | undefined = 4, observedAt = iso(), ok = true) { monitoringStatusWriter(name)({ observedAt: new Date().toISOString(), result: { ok: true, state: 'running' } }, { observedAt, result: ok ? { ok: true, endpoint: { host: 'localhost', port: 28960 }, status: { players: 3, capacity, map: 'zombie_theater', gametype: 'zom', roster: [] } } : { ok: false, endpoint: null, reason: 'timeout', detail: 'No reply' } }); } return { database, names, publish, ...await import('../../src/lib/server/fleet/player-capacity.js') }; } describe('current player capacity', () => { it('requires a fresh positive integer from after the latest lifecycle submission', () => { const known = { capacity: 4, capacity_observed_at: iso() }; expect(currentCapacity(known, now)).toBe(4); expect(currentCapacity(known, now + 90_000)).toBe(4); expect(currentCapacity(known, now + 90_001)).toBeNull(); expect(currentCapacity(known, now - 2000)).toBeNull(); expect(currentCapacity({ ...known, telemetry_after: iso(-1) }, now)).toBe(4); for (const telemetry_after of [iso(), iso(1), 'invalid']) expect(currentCapacity({ ...known, telemetry_after }, now)).toBeNull(); for (const capacity of [null, undefined, 0, -1, 4.5, NaN, Infinity]) expect(currentCapacity({ ...known, capacity }, now)).toBeNull(); for (const capacity_observed_at of [null, undefined, 'invalid']) expect(currentCapacity({ ...known, capacity_observed_at }, now)).toBeNull(); }); it('projects the accepted structured sample into REST and initial SSE without probing', async () => { const api = await setup(); api.publish('server-0'); const rcon = vi.spyOn(await import('../../src/lib/server/integrations/rcon.js'), 'rconStatusResult'); const { fleetSnapshot } = await import('../../src/lib/server/fleet/snapshot.js'); expect(fleetSnapshot().servers[0]).toMatchObject({ capacity: 4, capacity_observed_at: iso() }); const { GET } = await import('../../src/routes/api/events/servers/+server.js'); const reader = (await GET()).body!.getReader(); try { await reader.read(); // stream greeting const frame = new TextDecoder().decode((await reader.read()).value); const body = JSON.parse(frame.split('data: ')[1]); expect(body.entries[0]).toMatchObject({ capacity: 4, capacityObservedAt: iso() }); } finally { await reader.cancel(); } expect(rcon).not.toHaveBeenCalled(); }); it('clears missing, invalid, failed and superseded evidence instead of reusing the previous limit', async () => { const api = await setup(); const read = () => api.playerCapacityReader()('server-0'); expect(read()).toEqual(unavailable); api.publish('server-0'); expect(read()).toEqual({ capacity: 4, capacity_observed_at: iso() }); for (const value of [null, 0, -1, 4.5, NaN]) { api.publish('server-0', value); expect(read()).toEqual(unavailable); } api.publish('server-0', 4, iso(), false); expect(read()).toEqual(unavailable); api.publish('server-0'); api.database.prepare("UPDATE servers SET last_checked_at=?,last_error='timeout:newer failure'").run(iso(1)); expect(read()).toEqual(unavailable); }); it('retains the original sample age even when the container is checked again', async () => { const api = await setup(); api.publish('server-0'); vi.setSystemTime(now + 90_001); api.publish('server-0', 4, iso()); expect(api.playerCapacityReader()('server-0')).toEqual(unavailable); }); it.each(['generation', 'lifecycle', 'registration', 'offline'] as const)('rejects capacity after %s changes', async change => { const api = await setup(); const hosts = await import('../../src/lib/server/hosts/index.js'); const host = hosts.createHost({ name: 'Capacity host' }); hosts.enrollHost(hosts.createEnrollment(host.id).token, { protocolVersion: 1 }); api.database.prepare(`INSERT INTO native_instances(id,server,host_id,library_id,image,port,created_at) VALUES('instance','server-0',?,'library','image',28960,?)`).run(host.id, iso()); api.publish('server-0'); expect(api.playerCapacityReader()('server-0').capacity).toBe(4); if (change === 'generation') api.database.prepare('UPDATE native_instances SET generation=2').run(); if (change === 'lifecycle') api.database.prepare("INSERT INTO restart_rollout(server,requested_at) VALUES('server-0',?)").run(iso(1)); if (change === 'registration') api.database.prepare('UPDATE servers SET created_at=?').run(iso(1)); if (change === 'offline') api.database.prepare('UPDATE ec_hosts SET last_heartbeat=?').run(iso(-91_000)); expect(api.playerCapacityReader()('server-0')).toEqual(unavailable); }); it('reads a fleet in one joined query and keeps server-detail reads scoped', async () => { const api = await setup(20); api.names.forEach(name => api.publish(name)); // Initialize schema before measuring the snapshot's actual reads. api.playerCapacityReader(); const prepare = vi.spyOn(api.database, 'prepare'); const fleet = api.playerCapacityReader(); expect(api.names.map(name => fleet(name).capacity)).toEqual(Array(20).fill(4)); expect(prepare).toHaveBeenCalledTimes(1); expect(prepare.mock.calls[0][0]).toContain('WHERE s.destroyed_at IS NULL ORDER BY s.name'); prepare.mockClear(); const scoped = api.playerCapacityReader('server-0'); expect(scoped('server-0').capacity).toBe(4); expect(scoped('server-1')).toEqual(unavailable); expect(prepare).toHaveBeenCalledTimes(1); expect(prepare.mock.calls[0][0]).toContain('WHERE s.name=? AND s.destroyed_at IS NULL'); }); });