← Back to Learn
approval-workflowsbest-practicestutorial

Testing Approval Workflows

Authensor

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.

What to Test

Trigger Conditions

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.

Approval Flow

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.

Rejection Flow

Simulate an approver rejecting the request. Verify the action is blocked, the agent receives a clear rejection reason, and a receipt records the rejection.

Timeout Handling

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.

Escalation

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.

Concurrent Approvals

Submit multiple approval requests simultaneously. Verify each is handled independently without interference. Test that approving one request does not affect another.

Test Implementation

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

Time Manipulation

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.

State Machine Verification

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.

Keep learning

Explore more guides on AI agent safety, prompt injection, and building secure systems.

View All Guides