Files
electric-cherry/tests/unit/provisioning-cancellation.test.ts
T

58 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { newFixture, type Fixture } from '../helpers/fixture.js';
vi.mock('../../src/lib/server/pipeline/worker.js', () => ({ kickProvisioning: vi.fn(async () => {}) }));
let fixture: Fixture;
beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); });
afterEach(async () => { await fixture.cleanup(); });
describe('provisioning cancellation', () => {
it.each(['create', 'start', 'restart', 'rebuild', 'stop', 'destroy'])('cancels %s in every active phase and keeps cancellation terminal', async action => {
const { db } = await import('../../src/lib/server/db/index.js');
const { cancelProvisioningJob, canCancelProvisioningJob } = await import('../../src/lib/server/provisioning/cancellation.js');
const { finishDeployment, getDeployment, listDeployments } = await import('../../src/lib/server/provisioning/history.js');
for (const state of ['queued', 'waiting', 'running', 'awaiting-health', 'interrupted']) {
const id = `${action}-${state}`;
db().prepare(`INSERT INTO server_deployments(id,server,action,state,stage,started_at,submitted_at,triggered_by)
VALUES(?,'server',?,?, 'In progress','2026-10-05',?,'test')`).run(id, action, state, state === 'waiting' ? null : '2026-10-05');
expect(canCancelProvisioningJob(id)).toBe(true);
expect(getDeployment(id)?.canCancel).toBe(true);
expect(listDeployments().find(row => row.id === id)?.canCancel).toBe(1);
expect(cancelProvisioningJob(id)).toBe(true);
expect(cancelProvisioningJob(id)).toBe(true);
finishDeployment(id, { ok: true });
expect(getDeployment(id)?.deployment).toMatchObject({ state: 'cancelled', stage: 'Cancelled by operator' });
expect(getDeployment(id)?.canCancel).toBe(false);
expect(db().prepare("SELECT COUNT(*) n FROM server_deployment_events WHERE deployment_id=? AND kind='outcome'").get(id)).toEqual({ n: 1 });
}
});
it('releases only the cancelled job’s recovery slot and cancels its pending file changes', async () => {
const { db } = await import('../../src/lib/server/db/index.js');
const { beginDeployment } = await import('../../src/lib/server/provisioning/history.js');
const { queueFileChange } = await import('../../src/lib/server/fleet/staged-files.js');
const { cancelProvisioningJob } = await import('../../src/lib/server/provisioning/cancellation.js');
db().prepare("INSERT INTO servers(name) VALUES('one'),('two')").run();
const id = beginDeployment('one','restart');
const other = beginDeployment('two','restart');
db().prepare("INSERT INTO restart_rollout(server,requested_at,operation_id) VALUES('one','2026-10-05',?),('two','2026-10-05',?)").run(id, other);
db().prepare("INSERT INTO provisioning_sources(operation_id,kind,source_id,server) VALUES(?,'campaign','source','one')").run(id);
queueFileChange({ sourceKind: 'campaign', sourceId: 'source', server: 'one', kind: 'script', relPath: 'test.gsc', content: Buffer.from('queued'), inventorySource: 'test' });
expect(cancelProvisioningJob(id)).toBe(true);
expect(db().prepare('SELECT operation_id FROM restart_rollout').all()).toEqual([{ operation_id: other }]);
expect(db().prepare('SELECT state FROM staged_file_changes').get()).toEqual({ state: 'cancelled' });
});
it('rejects missing, completed and deleted-server jobs through the cancellation API', async () => {
const { db } = await import('../../src/lib/server/db/index.js');
const { POST } = await import('../../src/routes/api/deployments/[id]/cancel/+server.js');
for (const state of ['done','warning','failed']) {
db().prepare(`INSERT INTO server_deployments(id,server,action,state,stage,started_at,triggered_by) VALUES(?,'server','restart',?,'Finished','2026-10-05','test')`).run(state,state);
expect(POST({ params: { id: state } }).status).toBe(409);
}
expect(POST({ params: { id: 'missing' } }).status).toBe(409);
db().prepare(`INSERT INTO server_deployments(id,server,action,state,stage,started_at,triggered_by,server_deleted_at) VALUES('deleted','server','restart','running','Running','2026-10-05','test','2026-10-05')`).run();
expect(POST({ params: { id: 'deleted' } }).status).toBe(409);
});
});