Policy Testing
This guide explains how to test AxonFlow policies to ensure they work as expected before deploying to production.
Overview
Testing policies is critical to:
- Verify pattern matching works correctly
- Ensure actions trigger as expected
- Prevent false positives/negatives
- Validate performance impact
Testing Methods
1. SDK-Based Pattern Testing
Use the SDK's testPattern method to validate regex patterns against test inputs before creating policies:
import { AxonFlow } from '@axonflow/sdk';
const client = new AxonFlow({
endpoint: 'http://localhost:8080',
clientId: 'my-tenant',
});
// Test a pattern against multiple inputs
const result = await client.testPattern({
pattern: '\\b(\\d{3})[- ]?(\\d{2})[- ]?(\\d{4})\\b',
inputs: [
'My SSN is 123-45-6789',
'No SSN here',
'Multiple: 111-22-3333 and 444-55-6666',
],
});
for (const match of result.matches) {
console.log(`Input: "${match.input}" — Matched: ${match.matched}`);
}
from axonflow import AxonFlow
client = AxonFlow(
endpoint="http://localhost:8080",
client_id="my-tenant",
)
result = await client.test_pattern(
pattern=r"\b(\d{3})[- ]?(\d{2})[- ]?(\d{4})\b",
inputs=[
"My SSN is 123-45-6789",
"No SSN here",
"Multiple: 111-22-3333 and 444-55-6666",
],
)
for match in result.matches:
print(f"Input: '{match.input}' — Matched: {match.matched}")
2. REST API Testing
Test patterns directly via the Agent REST API on port 8080:
# Test a regex pattern against sample inputs
curl -X POST http://localhost:8080/api/v1/static-policies/test-pattern \
-H "Content-Type: application/json" \
-H "X-Org-ID: my-tenant" \
-d '{
"pattern": "\\b(\\d{3})[- ]?(\\d{2})[- ]?(\\d{4})\\b",
"inputs": [
"My SSN is 123-45-6789",
"No SSN here",
"Multiple: 111-22-3333 and 444-55-6666"
]
}'
Response:
{
"pattern": "\\b(\\d{3})[- ]?(\\d{2})[- ]?(\\d{4})\\b",
"matches": [
{ "input": "My SSN is 123-45-6789", "matched": true, "matchedText": "123-45-6789" },
{ "input": "No SSN here", "matched": false },
{ "input": "Multiple: 111-22-3333 and 444-55-6666", "matched": true, "matchedText": "111-22-3333" }
]
}
3. End-to-End Query Testing
Validate that policies trigger correctly by sending a real query through the Agent's executeQuery endpoint:
# Send a query that should be blocked by PII policies
curl -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Org-ID: my-tenant" \
-d '{
"query": "My SSN is 123-45-6789"
}'
If the SSN policy is active, the response will indicate the query was blocked:
{
"blocked": true,
"policy_id": "sys_pii_ssn",
"message": "Request blocked by policy: sys_pii_ssn"
}
Unit Testing Policies
Go Test Examples
Write Go tests that validate policy patterns using the Agent's policy evaluation API. This approach is used in AxonFlow's own test suite:
package policy_test
import (
"regexp"
"testing"
)
func TestSSNPattern(t *testing.T) {
pattern := regexp.MustCompile(`\b(\d{3})[- ]?(\d{2})[- ]?(\d{4})\b`)
tests := []struct {
name string
input string
matched bool
}{
{"SSN with dashes", "My SSN is 123-45-6789", true},
{"SSN with spaces", "SSN: 123 45 6789", true},
{"SSN no separators", "123456789", true},
{"No SSN present", "No sensitive data here", false},
{"Invalid SSN format", "12-345-6789", false},
{"Multiple SSNs", "SSN1: 111-22-3333, SSN2: 444-55-6666", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := pattern.MatchString(tt.input)
if got != tt.matched {
t.Errorf("pattern.MatchString(%q) = %v, want %v", tt.input, got, tt.matched)
}
})
}
}
func TestPANPattern(t *testing.T) {
pattern := regexp.MustCompile(`\b[A-Z]{3}[PCHABGJLFT][A-Z][0-9]{4}[A-Z]\b`)
tests := []struct {
name string
input string
matched bool
}{
{"Valid PAN", "PAN: ABCPD1234E", true},
{"Lowercase PAN (no match)", "PAN: abcpd1234e", false},
{"Invalid format", "ABC1234567", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := pattern.MatchString(tt.input)
if got != tt.matched {
t.Errorf("pattern.MatchString(%q) = %v, want %v", tt.input, got, tt.matched)
}
})
}
}
SDK-Based Unit Tests (TypeScript)
import { AxonFlow } from '@axonflow/sdk';
describe('Custom Policy Patterns', () => {
const client = new AxonFlow({
endpoint: 'http://localhost:8080',
clientId: 'test-tenant',
});
it('should detect SSN patterns', async () => {
const result = await client.testPattern({
pattern: '\\b(\\d{3})[- ]?(\\d{2})[- ]?(\\d{4})\\b',
inputs: ['My SSN is 123-45-6789'],
});
expect(result.matches[0].matched).toBe(true);
});
it('should not match invalid SSN format', async () => {
const result = await client.testPattern({
pattern: '\\b(\\d{3})[- ]?(\\d{2})[- ]?(\\d{4})\\b',
inputs: ['12-345-6789'],
});
expect(result.matches[0].matched).toBe(false);
});
});
Integration Testing
Validate policies in the full request pipeline by sending queries through the Agent (port 8080).
Test Block Action
# This query contains a SQL injection pattern — should be blocked
curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Org-ID: my-tenant" \
-d '{"query": "SELECT * FROM users UNION SELECT * FROM passwords"}' \
| jq .
# Expected: blocked=true, policy_id contains "sqli"
Test Warn Action
# Create a policy with action=warn, then test it
curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Org-ID: my-tenant" \
-d '{"query": "Show me data for [email protected]"}' \
| jq .
# Expected: request proceeds but policy_evaluations includes a warning
Test Log Action
# Queries matching log-action policies should pass through
curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Org-ID: my-tenant" \
-d '{"query": "Look up IP address 192.168.1.1"}' \
| jq .
# Expected: request succeeds; check audit logs for the logged match
Verifying Policy Evaluation with executeQuery (SDK)
// Send a query through the SDK and inspect policy results
const response = await client.executeQuery({
query: 'My SSN is 123-45-6789',
});
if (response.blocked) {
console.log(`Blocked by policy: ${response.policyId}`);
} else {
console.log('Query was allowed');
}
Performance Testing
Benchmarking with Go
Use Go's built-in benchmark framework to measure policy pattern evaluation latency:
package policy_test
import (
"regexp"
"testing"
)
var ssnPattern = regexp.MustCompile(`\b(\d{3})[- ]?(\d{2})[- ]?(\d{4})\b`)
func BenchmarkSSNPattern(b *testing.B) {
input := "My SSN is 123-45-6789"
for i := 0; i < b.N; i++ {
ssnPattern.MatchString(input)
}
}
Run with:
go test -bench=BenchmarkSSNPattern -benchmem -count=5 ./...
AxonFlow's system policies are designed for sub-5ms evaluation. Pattern-based policies evaluated by the Agent typically complete in under 1ms.
Load Testing with curl
For basic throughput testing against a running Agent:
# Send 1000 requests and measure timing
for i in $(seq 1 1000); do
curl -s -o /dev/null -w "%{time_total}\n" \
-X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Org-ID: my-tenant" \
-d '{"query": "My SSN is 123-45-6789"}'
done | awk '{sum+=$1; count++} END {print "Avg:", sum/count*1000, "ms"}'
Staging Validation
Test New Policies with Warn Before Block
Before enforcing a new policy in production, create it with action: warn to monitor for false positives:
// Step 1: Deploy with warn action
const policy = await client.createStaticPolicy({
name: 'Block Internal IPs',
description: 'Prevent exposure of internal IP addresses',
category: 'security-admin',
pattern: '\\b10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b',
action: 'warn', // Monitor first
severity: 'medium',
enabled: true,
});
// Step 2: After validating no false positives, switch to block
await client.updateStaticPolicy(policy.id, {
action: 'block',
});
This is the recommended rollout strategy for all custom policies.
CI/CD Integration
GitHub Actions
Run policy pattern tests as part of your CI pipeline using Go tests:
# .github/workflows/policy-tests.yml
name: Policy Tests
on:
push:
paths:
- 'policies/**'
- 'tests/policy/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: '1.25'
- name: Run policy pattern tests
run: go test ./tests/policy/... -v -cover
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: test-results
path: coverage.out
Integration Test with Docker Compose
For full end-to-end validation, spin up the AxonFlow stack and run integration tests:
# In your CI workflow
- name: Start AxonFlow
run: docker compose up -d
- name: Wait for Agent to be ready
run: |
for i in $(seq 1 30); do
curl -sf http://localhost:8080/health && break
sleep 2
done
- name: Run integration tests
run: |
# Test that SSN is blocked
RESPONSE=$(curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Org-ID: test-tenant" \
-d '{"query": "My SSN is 123-45-6789"}')
echo "$RESPONSE" | jq -e '.blocked == true'
# Test that safe queries pass
RESPONSE=$(curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Org-ID: test-tenant" \
-d '{"query": "What is the weather today?"}')
echo "$RESPONSE" | jq -e '.blocked == false or .blocked == null'
Test Data Patterns
Common PII Test Inputs
Use these test inputs when validating policy patterns:
| Category | Valid (should match) | Invalid (should not match) |
|---|---|---|
| SSN | 123-45-6789, 123 45 6789, 123456789 | 12-345-6789, 1234-56-789 |
| Credit Card (Visa) | 4111111111111111 | 41111111 (too short) |
| Credit Card (MC) | 5500000000000004 | 5500000000 (too short) |
| PAN (India) | ABCPD1234E, XYZC12345A | ABC1234567, 123PD1234E |
| Aadhaar (India) | 2345 6789 0123 | 0123 4567 8901 (starts with 0) |
[email protected] | user@ (incomplete) |
Go Table-Driven Test Template
func TestCustomPolicyPattern(t *testing.T) {
pattern := regexp.MustCompile(`YOUR_PATTERN_HERE`)
tests := []struct {
name string
input string
matched bool
}{
// Add positive cases
{"should match valid input", "valid test input", true},
// Add negative cases
{"should not match invalid input", "safe input", false},
// Add edge cases
{"edge case", "boundary input", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := pattern.MatchString(tt.input)
if got != tt.matched {
t.Errorf("pattern.MatchString(%q) = %v, want %v", tt.input, got, tt.matched)
}
})
}
}
Troubleshooting
Pattern Not Matching
- Check for case sensitivity (add
(?i)flag for case-insensitive matching) - Verify escape sequences (double-escape backslashes in JSON:
\\bnot\b) - Test pattern in isolation with the
testPattern()SDK method - Ensure word boundaries
\bare placed correctly
False Positives
- Make pattern more specific
- Add negative lookahead/lookbehind
- Add context conditions
- Test with more samples
Performance Issues
- Optimize regex patterns
- Avoid backtracking
- Use non-capturing groups
- Consider pattern compilation
Best Practices
- Test early and often - Write tests alongside policies
- Cover edge cases - Test boundary conditions
- Use realistic data - Test with production-like inputs
- Automate testing - Include in CI/CD pipeline
- Monitor in production - Track false positive rates
- Version control tests - Keep tests with policies
Related
- Policy Examples - Ready-to-use policy templates
- Policy Syntax - Policy field reference
- SDK Methods - Full API reference for policy CRUD
- System Policies - Complete list of 63 system policies
