approvals.test.ts
1 import assert from 'node:assert/strict' 2 import fs from 'node:fs' 3 import os from 'node:os' 4 import path from 'node:path' 5 import { after, before, describe, it } from 'node:test' 6 7 const originalEnv = { 8 DATA_DIR: process.env.DATA_DIR, 9 WORKSPACE_DIR: process.env.WORKSPACE_DIR, 10 SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE, 11 } 12 13 let tempDir = '' 14 let approvals: typeof import('./approvals') 15 16 before(async () => { 17 tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-approvals-')) 18 process.env.DATA_DIR = path.join(tempDir, 'data') 19 process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace') 20 process.env.SWARMCLAW_BUILD_MODE = '1' 21 approvals = await import('./approvals') 22 }) 23 24 after(() => { 25 if (originalEnv.DATA_DIR === undefined) delete process.env.DATA_DIR 26 else process.env.DATA_DIR = originalEnv.DATA_DIR 27 if (originalEnv.WORKSPACE_DIR === undefined) delete process.env.WORKSPACE_DIR 28 else process.env.WORKSPACE_DIR = originalEnv.WORKSPACE_DIR 29 if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE 30 else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE 31 fs.rmSync(tempDir, { recursive: true, force: true }) 32 }) 33 34 describe('approvals', () => { 35 it('creates a pending approval with correct fields', () => { 36 const result = approvals.requestApproval({ 37 category: 'human_loop', 38 title: 'Confirm deployment', 39 data: { question: 'Deploy to prod?' }, 40 agentId: 'agent-1', 41 sessionId: 'session-1', 42 }) 43 44 assert.equal(result.status, 'pending') 45 assert.equal(result.category, 'human_loop') 46 assert.equal(result.agentId, 'agent-1') 47 assert.equal(result.sessionId, 'session-1') 48 assert.ok(result.id.length > 0) 49 assert.ok(result.createdAt > 0) 50 assert.equal(result.createdAt, result.updatedAt) 51 }) 52 53 it('lists only pending approvals and can filter by category', async () => { 54 const human = approvals.requestApproval({ 55 category: 'human_loop', 56 title: 'Human approval', 57 data: { question: 'Proceed?' }, 58 }) 59 const other = approvals.requestApproval({ 60 category: 'human_loop', 61 title: 'Another approval', 62 data: { question: 'Continue?' }, 63 }) 64 65 await approvals.submitDecision(other.id, true) 66 67 const pending = approvals.listPendingApprovals() 68 const humanPending = approvals.listPendingApprovals('human_loop') 69 70 assert.equal(pending.some((entry) => entry.id === human.id), true) 71 assert.equal(pending.some((entry) => entry.id === other.id), false) 72 assert.equal(humanPending.some((entry) => entry.id === human.id), true) 73 assert.equal(humanPending.every((entry) => entry.category === 'human_loop'), true) 74 }) 75 76 it('approves a pending request', async () => { 77 const req = approvals.requestApproval({ 78 category: 'human_loop', 79 title: 'Confirm deployment', 80 data: { question: 'Deploy to prod?' }, 81 sessionId: null, 82 agentId: null, 83 }) 84 85 const updated = await approvals.submitDecision(req.id, true) 86 87 assert.equal(updated.status, 'approved') 88 assert.equal(approvals.listPendingApprovals().some((entry) => entry.id === req.id), false) 89 }) 90 91 it('rejects a pending request', async () => { 92 const req = approvals.requestApproval({ 93 category: 'human_loop', 94 title: 'Confirm deletion', 95 data: { question: 'Delete everything?' }, 96 sessionId: null, 97 agentId: null, 98 }) 99 100 const updated = await approvals.submitDecision(req.id, false) 101 102 assert.equal(updated.status, 'rejected') 103 assert.equal(approvals.listPendingApprovals().some((entry) => entry.id === req.id), false) 104 }) 105 106 it('throws for non-existent approval id', async () => { 107 await assert.rejects( 108 () => approvals.submitDecision('nonexistent-xyz', true), 109 /not found/i, 110 ) 111 }) 112 113 it('is idempotent for repeated decisions', async () => { 114 const req = approvals.requestApproval({ 115 category: 'human_loop', 116 title: 'Idempotent test', 117 data: { question: 'yes?' }, 118 }) 119 120 const approved = await approvals.submitDecision(req.id, true) 121 const repeated = await approvals.submitDecision(req.id, true) 122 123 assert.equal(approved.status, 'approved') 124 assert.equal(repeated.status, 'approved') 125 }) 126 })