Skip to main content

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:

CategoryValid (should match)Invalid (should not match)
SSN123-45-6789, 123 45 6789, 12345678912-345-6789, 1234-56-789
Credit Card (Visa)411111111111111141111111 (too short)
Credit Card (MC)55000000000000045500000000 (too short)
PAN (India)ABCPD1234E, XYZC12345AABC1234567, 123PD1234E
Aadhaar (India)2345 6789 01230123 4567 8901 (starts with 0)
Email[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

  1. Check for case sensitivity (add (?i) flag for case-insensitive matching)
  2. Verify escape sequences (double-escape backslashes in JSON: \\b not \b)
  3. Test pattern in isolation with the testPattern() SDK method
  4. Ensure word boundaries \b are placed correctly

False Positives

  1. Make pattern more specific
  2. Add negative lookahead/lookbehind
  3. Add context conditions
  4. Test with more samples

Performance Issues

  1. Optimize regex patterns
  2. Avoid backtracking
  3. Use non-capturing groups
  4. Consider pattern compilation

Best Practices

  1. Test early and often - Write tests alongside policies
  2. Cover edge cases - Test boundary conditions
  3. Use realistic data - Test with production-like inputs
  4. Automate testing - Include in CI/CD pipeline
  5. Monitor in production - Track false positive rates
  6. Version control tests - Keep tests with policies