import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { ForbiddenException } from '@nestjs/common'; import { KillSwitchService } from '../src/kill-switch/kill-switch.service.js'; const viewer = { actor: 'viewer-1', role: 'viewer', project: 'default', tenant: 'default' }; const operator = { actor: 'operator-1', role: 'operator', project: 'default', tenant: 'default' }; const admin = { actor: 'admin-1', role: 'org-admin', project: 'default', tenant: 'default' }; function withTempKillSwitch(fn: () => void) { const prev = process.env.CASAN_KILLSWITCH_DIR; process.env.CASAN_KILLSWITCH_DIR = mkdtempSync(join(tmpdir(), 'cp-ks-')); try { fn(); } finally { if (prev === undefined) delete process.env.CASAN_KILLSWITCH_DIR; else process.env.CASAN_KILLSWITCH_DIR = prev; } } test('kill-switch status is readable by viewer', () => { withTempKillSwitch(() => { const svc = new KillSwitchService(); const status = svc.status(viewer); assert.equal(status.count, 0); assert.deepEqual(status.engaged, []); }); }); test('viewer cannot engage kill-switch', () => { withTempKillSwitch(() => { const svc = new KillSwitchService(); assert.throws( () => svc.engage({ scope: 'project', id: 'p1', reason: 'viewer should fail' }, viewer), ForbiddenException, ); }); }); test('operator can engage and org-admin can clear kill-switch', () => { withTempKillSwitch(() => { const svc = new KillSwitchService(); const engaged = svc.engage({ scope: 'project', id: 'p1', reason: 'incident drill' }, operator) as any; assert.equal(engaged.status.count, 1); assert.equal(engaged.status.engaged[0].scope, 'project'); assert.equal(engaged.status.engaged[0].actor, 'operator-1'); assert.throws( () => svc.clear({ scope: 'project', id: 'p1', reason: 'viewer clear should fail' }, viewer), ForbiddenException, ); const cleared = svc.clear({ scope: 'project', id: 'p1', reason: 'resolved' }, admin) as any; assert.equal(cleared.status.count, 0); }); });