Search documentation

Search documentation

Testing

The TypeScript SDK ships with testing utilities at @pillar-ai/server/testing that make it easy to generate valid signatures and mock tool contexts. For Python, you can construct signatures manually using the standard library.

TypeScript

Setup

Import the testing helpers:

typescript
import { generateSignature, createToolContext } from '@pillar-ai/server/testing';

generateSignature

Generates a valid X-Pillar-Signature header for a request body:

typescript
const body = JSON.stringify({
action: 'tool_call',
tool_name: 'lookup_customer',
arguments: { email: 'sarah@acme.com' },
// ...
});
const signature = generateSignature(body, 'your_test_secret');
// Returns: "t=1710000000,v1=abc123..."

Pass an optional third argument to set the timestamp (useful for testing expiration):

typescript
const signature = generateSignature(body, secret, 1710000000);

createToolContext

Builds a ToolContext object from a raw webhook payload, useful for unit-testing tool handlers directly:

typescript
const ctx = createToolContext({
caller: { channel: 'web', email: 'sarah@acme.com' },
conversation_id: 'conv_1',
call_id: 'tc_test',
});
const result = await myTool.execute({ email: 'sarah@acme.com' }, ctx);

Full integration test

This example tests a tool end-to-end by calling pillar.handle() with a signed request:

pillar-tools.test.ts
import { describe, it, expect } from 'vitest';
import { generateSignature, createToolContext } from '@pillar-ai/server/testing';
import { pillar } from './pillar-tools';
const SECRET = 'plr_test_secret';
describe('lookup_customer', () => {
it('returns customer data', async () => {
const body = JSON.stringify({
action: 'tool_call',
call_id: 'tc_test_123',
tool_name: 'lookup_customer',
arguments: { email: 'sarah@acme.com' },
caller: { channel: 'web', email: 'sarah@acme.com' },
conversation_id: 'conv_1',
});
const signature = generateSignature(body, SECRET);
const [result, status] = await pillar.handle(body, {
'x-pillar-signature': signature,
});
expect(status).toBe(200);
expect(result.success).toBe(true);
expect(result.result.name).toBe('Sarah');
});
});

Python

The Python SDK doesn't ship separate testing utilities, but you can generate signatures with the standard library:

test_pillar_tools.py
import json
import hmac
import hashlib
import time
import pytest
from myapp.pillar_tools import pillar
SECRET = "plr_test_secret"
def _generate_signature(body: bytes, secret: str) -> str:
timestamp = str(int(time.time()))
payload = f"{timestamp}.".encode() + body
digest = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return f"t={timestamp},v1={digest}"
@pytest.mark.asyncio
async def test_lookup_customer():
body = json.dumps({
"action": "tool_call",
"call_id": "tc_test_123",
"tool_name": "lookup_customer",
"arguments": {"email": "sarah@acme.com"},
"caller": {"channel": "web", "email": "sarah@acme.com"},
"conversation_id": "conv_1",
}).encode()
signature = _generate_signature(body, SECRET)
result, status = await pillar.handle(body, {
"X-Pillar-Signature": signature,
})
assert status == 200
assert result["success"] is True
assert result["result"]["name"] == "Sarah"

Testing confirmation flows

To test a tool that uses confirmations, make two calls — one without confirmed and one with the tool_confirm action:

python
@pytest.mark.asyncio
async def test_confirmation_flow():
# First call — should return confirmation_required
body = json.dumps({
"action": "tool_call",
"call_id": "tc_1",
"tool_name": "delete_member",
"arguments": {"user_id": "usr_123"},
"caller": {"channel": "web"},
"conversation_id": "conv_1",
}).encode()
result, status = await pillar.handle(body, {
"X-Pillar-Signature": _generate_signature(body, SECRET),
})
assert result["result"]["confirmation_required"] is True
# Second call — user confirmed
confirm_body = json.dumps({
"action": "tool_confirm",
"call_id": "tc_1",
"tool_name": "delete_member",
"confirm_payload": {"user_id": "usr_123"},
"caller": {"channel": "web"},
"conversation_id": "conv_1",
}).encode()
result, status = await pillar.handle(confirm_body, {
"X-Pillar-Signature": _generate_signature(confirm_body, SECRET),
})
assert result["success"] is True
assert result["result"]["removed"] is True

Tips

  • Use a fixed test secret (e.g., plr_test_secret) instead of your production secret
  • For unit tests, call the tool's execute function directly with a createToolContext (TypeScript) or a manually constructed ToolContext (Python)
  • For integration tests, call pillar.handle() with a signed request body to test the full pipeline including signature verification and tool dispatch

Next steps