Files
electric-cherry/tests/unit/server-history-views.test.ts
T

121 lines
5.2 KiB
TypeScript

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();
});