Preserve server status when batch probes miss the deadline

This commit is contained in:
2026-10-05 16:17:29 -04:00
parent ad63b3957d
commit ae703617af
7 changed files with 105 additions and 22 deletions
+29
View File
@@ -47,11 +47,14 @@ async function setup(count = 5, hostCount = 1) {
describe('host status observations', () => {
it('uses one batch per host for a fifteen-server pass and shares overlapping scans', async () => {
const api = await setup(5, 3);
const hosts = await import('../../src/lib/server/hosts/index.js');
const wait = vi.spyOn(hosts, 'waitHostOperationStatus');
const passes = [api.fanoutStatus(), api.fanoutStatus()];
try {
await vi.waitFor(() => expect(api.operations()).toHaveLength(3));
for (const operation of api.operations()) {
expect(operation.kind).toBe('rcon.status-batch');
expect(wait).toHaveBeenCalledWith(operation.id, 25_000);
expect(operation.payload.instances).toHaveLength(5);
expect(api.pendingHostOperations(operation.hostId, 'health').map(op => op.id)).toEqual([operation.id]);
expect(api.pendingHostOperations(operation.hostId, 'write')).toEqual([]);
@@ -115,6 +118,32 @@ describe('host status observations', () => {
expect(api.operations()).toHaveLength(1);
});
it('preserves previous game evidence for deadline skips while recording actual timeouts', async () => {
const api = await setup(3);
const first = api.fanoutStatus();
await vi.waitFor(() => expect(api.operations()).toHaveLength(1));
api.complete(); await first;
const previous = api.getServer('server-0-0')!;
const previousGame = api.monitoringStatus('server-0-0')?.game;
vi.setSystemTime(at + 31_000);
const pass = api.fanoutStatus();
try {
await vi.waitFor(() => expect(api.operations()).toHaveLength(2));
const operation = api.operations().find(op => op.state !== 'completed')!;
api.reportHostOperation(operation.hostId, operation.id, { state: 'completed', result: { ok: false, results: [
{ instanceId: 'instance-0-0', generation: 1, reply: { ok: false, reason: 'deadline', text: '' } },
{ instanceId: 'instance-0-1', generation: 1, reply: { ok: false, reason: 'timeout', text: '' } },
{ instanceId: 'instance-0-2', generation: 1, reply: { ok: true, text: 'map: mp_kino\nnum score ping guid name\n--- ----- ---- ---- ----\n' } }
] } });
} finally { api.complete(); await pass; }
expect(api.getServer('server-0-0')).toMatchObject({ last_checked_at: previous.last_checked_at,
last_seen_at: previous.last_seen_at, last_error: previous.last_error, last_players: previous.last_players,
last_reply_snippet: previous.last_reply_snippet });
expect(api.monitoringStatus('server-0-0')?.game).toEqual(previousGame);
expect(api.getServer('server-0-1')?.last_error).toContain('timeout:');
expect(Date.parse(api.getServer('server-0-2')!.last_seen_at!)).toBeGreaterThanOrEqual(at + 31_000);
});
it('records each failed or omitted result without retrying the batch per server', async () => {
const api = await setup();
const pass = api.fanoutStatus();