Approval workflows add human oversight to high-risk agent actions. Testing them is tricky because they involve asynchronous human interaction, timeouts, and state transitions. A workflow that works in the happy path but fails during timeout, rejection, or escalation can leave the system in an inconsistent state.
Verify that approval workflows trigger for the correct actions. Submit envelopes that should require approval and verify a pending approval request is created. Submit envelopes that should not require approval and verify they proceed without creating a request.
Simulate an approver granting approval. Verify the action proceeds after approval, a receipt records the approval decision, and the approver's identity is captured in the audit trail.
Simulate an approver rejecting the request. Verify the action is blocked, the agent receives a clear rejection reason, and a receipt records the rejection.
Do not submit an approval response within the timeout window. Verify the request expires, the action is denied (fail-closed), and the agent receives a timeout notification.
If the primary approver does not respond within a threshold, verify the request escalates to the next approver in the chain. Test multi-level escalation paths.
Submit multiple approval requests simultaneously. Verify each is handled independently without interference. Test that approving one request does not affect another.
describe('approval workflow', () => {
it('blocks action until approved', async () => {
const envelope = { action: 'payment.send', amount: 5000 };
const result = await submitAction(envelope);
expect(result.status).toBe('pending_approval');
// Simulate approval
await approveRequest(result.approvalId, { approver: 'admin-1' });
const finalResult = await getActionResult(result.actionId);
expect(finalResult.status).toBe('allowed');
expect(finalResult.receipt.approved_by).toBe('admin-1');
});
it('denies action on timeout', async () => {
const envelope = { action: 'payment.send', amount: 5000 };
const result = await submitAction(envelope);
// Fast-forward past timeout
await advanceClock(result.timeout + 1000);
const finalResult = await getActionResult(result.actionId);
expect(finalResult.status).toBe('denied');
expect(finalResult.receipt.reason).toBe('approval_timeout');
});
});
Approval workflow tests need time control. Use dependency injection to provide a controllable clock to the workflow engine. This lets tests fast-forward through timeout periods without waiting.
Model the approval workflow as a state machine (pending, approved, rejected, expired, escalated). Write tests that cover every valid state transition and verify that invalid transitions are rejected.
Approval workflows are safety-critical code paths. Test them with the same rigor you would test payment processing or authentication.
Explore more guides on AI agent safety, prompt injection, and building secure systems.
View All Guides