import { afterEach, beforeEach, 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 () => { vi.restoreAllMocks(); await fixture.cleanup(); }); async function setup() { const database = await import('../../src/lib/server/db/index.js'); database.db().prepare(`INSERT INTO servers(name,game_port,last_error,last_reply_snippet) VALUES ('server',28960,'bad-reply:unrecognized status','private reply 192.0.2.123:28960')`).run(); return database; } it('excludes both snippet fields from list/detail/page snapshots and initial and live SSE frames', async () => { await setup(); const { GET: list } = await import('../../src/routes/api/servers/+server.js'); const { GET: detail } = await import('../../src/routes/api/servers/[name]/+server.js'); const { load } = await import('../../src/routes/servers/[name]/+page.server.js'); const { GET: stream } = await import('../../src/routes/api/events/servers/+server.js'); const { getServer } = await import('../../src/lib/server/fleet/servers.js'); const { statusEntry } = await import('../../src/lib/server/pipeline/worker.js'); const { emit } = await import('../../src/lib/server/pipeline/bus.js'); const safe = (text: string) => { for (const value of ['replySnippet', 'last_reply_snippet', 'private reply', '192.0.2.123']) expect(text).not.toContain(value); }; safe(await list().text()); safe(await detail({ params: { name: 'server' } }).text()); safe(JSON.stringify(load({ params: { name: 'server' } }))); const response = await stream(); const reader = response.body!.getReader(); const decoder = new TextDecoder(); try { for (let index = 0; index < 3; index++) safe(decoder.decode((await reader.read()).value)); emit({ type: 'server-status', entries: [statusEntry(getServer('server')!)] }); const live = decoder.decode((await reader.read()).value); expect(live).toContain('event: server-status'); safe(live); } finally { await reader.cancel(); } }); it('serves saved failure diagnostics without caching or probing and omits successful or missing evidence', async () => { const { db } = await setup(); const rcon = await import('../../src/lib/server/integrations/rcon.js'); const probe = vi.spyOn(rcon, 'rconStatusResult'); const { GET } = await import('../../src/routes/api/servers/[name]/rcon-diagnostics/+server.js'); const response = GET({ params: { name: 'server' } }); expect(response.headers.get('Cache-Control')).toBe('no-store'); expect(await response.json()).toEqual({ replySnippet: 'private reply [redacted]' }); expect(probe).not.toHaveBeenCalled(); db().prepare("UPDATE servers SET last_error=NULL WHERE name='server'").run(); expect(await GET({ params: { name: 'server' } }).json()).toEqual({ replySnippet: null }); expect(GET({ params: { name: 'missing' } }).status).toBe(404); db().prepare("UPDATE servers SET destroyed_at='2026-10-08' WHERE name='server'").run(); expect(GET({ params: { name: 'server' } }).status).toBe(404); }); it('sanitizes manually probed failures before persistence and clears them on success', async () => { const { db } = await setup(); const rcon = await import('../../src/lib/server/integrations/rcon.js'); const probe = vi.spyOn(rcon, 'rconStatusResult'); probe.mockResolvedValue({ ok: false, endpoint: null, reason: 'bad-reply', detail: 'unrecognized status', rawReply: 'unrecognized player 192.0.2.123:28960' }); const { POST } = await import('../../src/routes/api/servers/[name]/probe-rcon/+server.js'); await POST({ params: { name: 'server' } } as never); expect(db().prepare("SELECT last_reply_snippet FROM servers WHERE name='server'").get()) .toEqual({ last_reply_snippet: 'unrecognized player [redacted]' }); probe.mockResolvedValue({ ok: true, endpoint: { host: 'agent', port: 1 }, status: { players: 0, map: null, gametype: null, roster: [] } }); expect(await (await POST({ params: { name: 'server' } } as never)).json()).not.toHaveProperty('rawReply'); expect(db().prepare("SELECT last_reply_snippet FROM servers WHERE name='server'").get()).toEqual({ last_reply_snippet: null }); }); it('discards legacy plain text and hex snippets and unsafe error details once during upgrade', async () => { const { db, closeDb } = await setup(); db().exec(`DELETE FROM settings WHERE key='rcon_private_diagnostics_v1'; UPDATE servers SET last_error='bad-reply:print reply not recognized: "player 192.0.2.123:28960"'; INSERT INTO servers(name,last_reply_snippet) VALUES ('legacy-hex','0000 31 39 32 2e 30 2e 32 2e 31 32 33 3a 32 38 39 36 192.0.2.123:2896');`); closeDb(); expect(db().prepare('SELECT last_reply_snippet FROM servers').all()).toEqual([{ last_reply_snippet: null }, { last_reply_snippet: null }]); expect(db().prepare("SELECT last_error FROM servers WHERE name='server'").get()).toEqual({ last_error: 'bad-reply:unparseable print reply' }); db().prepare("UPDATE servers SET last_reply_snippet='new sanitized failure' WHERE name='server'").run(); closeDb(); expect(db().prepare("SELECT last_reply_snippet FROM servers WHERE name='server'").get()).toEqual({ last_reply_snippet: 'new sanitized failure' }); });