import { putFileBlob } from '../../src/lib/server/db/file-blobs.js'; import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { newFixture, type Fixture } from '../helpers/fixture.js'; let fixture: Fixture; const server = 't5_zombie_theater_1'; const at = '2026-09-27T12:00:00.000Z'; beforeEach(async () => { vi.resetModules(); fixture = await newFixture([server]); const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js'); await seedServerRegistry(); }); afterEach(async () => fixture.cleanup()); it('exposes retained campaign and provisioning history without rebinding links to a reused name', async () => { const { db, closeDb } = await import('../../src/lib/server/db/index.js'); const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js'); const { beginDeployment, finishDeployment, getDeployment, listDeployments } = await import('../../src/lib/server/provisioning/history.js'); const operation = beginDeployment(server, 'restart'); finishDeployment(operation, { ok: true }); db().prepare("INSERT INTO deployments (id, action, started_at, finished_at) VALUES ('history-campaign', 'promote', ?, ?)").run(at, at); db().prepare("INSERT INTO deployment_servers (deployment_id, server, files_changed, restart_state) VALUES ('history-campaign', ?, 1, 'done')").run(server); db().prepare("INSERT INTO deployment_files (deployment_id, server, verb, rel_path, content_before_hash, content_after_hash) VALUES ('history-campaign', ?, 'updated', 'script.gsc', ?, ?)") .run(server, putFileBlob(Buffer.from('before'), db()), putFileBlob(Buffer.from('after'), db())); db().prepare('DELETE FROM servers WHERE name = ?').run(server); await seedServerRegistry(); closeDb(); const { GET: campaignGET } = await import('../../src/routes/api/campaigns/[id]/+server.js'); const detail = await (await campaignGET({ params: { id: 'history-campaign' } })).json(); expect(detail.servers).toEqual([expect.objectContaining({ server, restart_state: 'done', server_deleted_at: expect.any(String) })]); expect(detail.files).toEqual([expect.objectContaining({ server, rel_path: 'script.gsc', size_before: 6, size_after: 5 })]); const { GET: listGET } = await import('../../src/routes/api/campaigns/+server.js'); const list = await (await listGET({ url: new URL('http://localhost/api/campaigns') })).json(); expect(list.deployments[0].servers[0].server_deleted_at).toEqual(expect.any(String)); expect(getDeployment(operation)?.deployment).toMatchObject({ server, state: 'done', server_exists: 0, server_deleted_at: expect.any(String) }); expect(listDeployments()).toContainEqual(expect.objectContaining({ id: operation, server_exists: 0 })); const replacement = beginDeployment(server, 'restart'); expect(getDeployment(replacement)?.deployment).toMatchObject({ server_exists: 1, server_deleted_at: null }); }); it('rejects historical maintenance retries and allows a new incident for a reused server name', async () => { const { db } = await import('../../src/lib/server/db/index.js'); const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js'); const job = db().prepare("INSERT INTO maintenance_jobs (server, requested_at, state, detail) VALUES (?, ?, 'failed', 'Original result')").run(server, at).lastInsertRowid; db().prepare("INSERT INTO watchdog_incidents (id, server, kind, state, summary, first_seen_at, last_seen_at) VALUES ('old-incident', ?, 'health', 'attention', 'Original finding', ?, ?)") .run(server, at, at); db().prepare('DELETE FROM servers WHERE name = ?').run(server); await seedServerRegistry(); const { retryMaintenance, plutoniumStatus } = await import('../../src/lib/server/maintenance/runtime.js'); const { updateWatchdogIncident, watchdogDetail } = await import('../../src/lib/server/maintenance/recovery.js'); expect(() => retryMaintenance(Number(job))).toThrow('deleted'); expect(plutoniumStatus().jobs).toContainEqual(expect.objectContaining({ id: Number(job), state: 'failed', detail: 'Original result', server_deleted_at: expect.any(String) })); expect(updateWatchdogIncident('old-incident', 'retry')).toMatchObject({ ok: false, status: 409 }); expect(watchdogDetail('old-incident')?.incident).toMatchObject({ state: 'attention', summary: 'Original finding', server_deleted_at: expect.any(String) }); expect(() => db().prepare("INSERT INTO watchdog_incidents (id, server, kind, state, summary, first_seen_at, last_seen_at) VALUES ('new-incident', ?, 'health', 'open', 'New finding', ?, ?)") .run(server, at, at)).not.toThrow(); });