Testing Policies
Unit test, simulate, and CI-gate your Rego policies before deploying.
Testing Policies
CoreSDK ships first-class tooling for policy testing: a CLI for fast iteration and SDK-native assertions for unit tests. Keep policies in version control and gate them in CI like any other code.
coresdk policy test CLI
The coresdk policy test command evaluates a policy directory against a JSON input and prints the result. No service required.
# Basic allow/deny check
coresdk policy test ./policies --input '{
"tenant_id": "acme",
"subject": "usr_2xK9",
"action": "orders:read",
"resource": "ord_7mP3",
"resource_owner": "usr_2xK9",
"context": { "roles": ["member"] }
}'
# → allow: true
# Expect a deny and assert a reason
coresdk policy test ./policies \
--input '{
"tenant_id": "acme",
"subject": "usr_2xK9",
"action": "data:export",
"resource": "export_1",
"resource_owner": "usr_2xK9",
"context": { "roles": ["member"], "tenant_plan": "starter" }
}' \
--expect deny \
--expect-reason "enterprise plan required"
# → allow: false ✓
# → deny_reasons: ["enterprise plan required"] ✓
# Run all *.input.json fixtures in a directory
coresdk policy test ./policies --fixtures ./tests/fixtures/
# → 12 passed, 0 failedFixture files
Store test cases as JSON alongside your policies. CoreSDK discovers files matching *.input.json and a corresponding *.expect.json:
tests/fixtures/
member-read-own-tenant.input.json
member-read-own-tenant.expect.json
admin-cross-tenant.input.json
admin-cross-tenant.expect.jsonmember-read-own-tenant.input.json:
{
"tenant_id": "acme",
"subject": "usr_2xK9",
"action": "orders:read",
"resource": "ord_7mP3",
"resource_owner": "usr_2xK9",
"context": { "roles": ["member"] }
}member-read-own-tenant.expect.json:
{
"allow": true,
"deny_reasons": []
}Unit testing policies in code
Use _sdk.evaluate_policy() (Python) or engine.policy().evaluate() (Rust) to write policy assertions in your test suite. This runs the full regorus evaluator in-process — no network call, no sidecar required.
#[cfg(test)]
mod tests {
use coresdk_engine::{Engine, EngineConfig, policy::decision::PolicyInput, audit::AuditSink};
fn test_engine() -> Engine {
Engine::new(EngineConfig {
policy_dir: Some("./policies".to_string()),
audit_sink: AuditSink::Discard, // don't pollute audit log in tests
..Default::default()
}).unwrap()
}
#[test]
fn member_can_read_own_tenant() {
let engine = test_engine();
let allowed = tokio::task::block_in_place(|| {
engine.policy().evaluate("data.authz.allow", PolicyInput {
tenant_id: "acme".to_string(),
subject: "usr_2xK9".to_string(),
action: "orders:read".to_string(),
resource: "ord_7mP3".to_string(),
resource_owner: Some("usr_2xK9".to_string()),
context: serde_json::json!({ "roles": ["member"] }),
})
}).unwrap();
assert!(allowed);
}
#[test]
fn member_cannot_cross_tenant() {
let engine = test_engine();
let allowed = tokio::task::block_in_place(|| {
engine.policy().evaluate("data.authz.allow", PolicyInput {
tenant_id: "acme".to_string(),
subject: "usr_2xK9".to_string(),
action: "orders:read".to_string(),
resource: "ord_9999".to_string(),
resource_owner: Some("usr_other".to_string()),
context: serde_json::json!({ "roles": ["member"] }),
})
}).unwrap();
assert!(!allowed);
}
}import pytest
from coresdk import CoreSDKClient, SDKConfig
@pytest.fixture
def sdk():
return CoreSDKClient(SDKConfig(
sidecar_addr="http://127.0.0.1:7233",
tenant_id="acme",
service_name="test",
dev_mode=True, # skip mTLS in tests
))
def test_member_can_read_own_tenant(sdk):
allowed = sdk.evaluate_policy("data.authz.allow", {
"tenant_id": "acme",
"subject": "usr_2xK9",
"action": "orders:read",
"resource": "ord_7mP3",
"resource_owner": "usr_2xK9",
"context": {"roles": ["member"]},
})
assert allowed
def test_member_cannot_cross_tenant(sdk):
allowed = sdk.evaluate_policy("data.authz.allow", {
"tenant_id": "acme",
"subject": "usr_2xK9",
"action": "orders:read",
"resource": "ord_9999",
"resource_owner": "usr_other",
"context": {"roles": ["member"]},
})
assert not allowed
def test_admin_can_delete_any_document(sdk):
allowed = sdk.evaluate_policy("data.authz.allow", {
"tenant_id": "acme",
"subject": "usr_admin",
"action": "document:delete",
"resource": "doc_4rT1",
"resource_owner": "usr_other",
"context": {"roles": ["admin"]},
})
assert allowed
def test_assert_no_pii_in_spans(sdk):
"""PII must never appear in OTEL spans — zero tolerance."""
import re
decision = sdk.validate_token("test-token")
# Verify claims string contains no email-shaped data
claims_str = str(decision.claims)
assert not re.search(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+', claims_str), \
"PII (email) found in claims — check masking pipeline"Go and TypeScript
Phase 2. Go and TypeScript SDKs ship in Phase 2. The API shown is the planned surface.
// Go (Phase 2)
package policy_test
import (
"context"
"testing"
sdk "github.com/coresdk-dev/sdk-go"
)
func testSDK(t *testing.T) *sdk.Client {
t.Helper()
s, err := sdk.New(sdk.Config{
SidecarAddr: "http://127.0.0.1:7233",
TenantID: "acme",
ServiceName: "test",
DevMode: true,
})
if err != nil {
t.Fatal(err)
}
return s
}
func TestMemberCanReadOwnTenant(t *testing.T) {
allowed, err := testSDK(t).Policy().Evaluate(context.Background(), "data.authz.allow", sdk.PolicyInput{
TenantID: "acme",
Subject: "usr_2xK9",
Action: "orders:read",
Resource: "ord_7mP3",
ResourceOwner: "usr_2xK9",
Context: map[string]any{"roles": []string{"member"}},
})
if err != nil {
t.Fatal(err)
}
if !allowed {
t.Fatal("expected allow, got deny")
}
}// TypeScript (Phase 2)
import { CoreSDKClient } from "@coresdk/node";
import { describe, it, expect, beforeAll } from "vitest";
let sdk: CoreSDKClient;
beforeAll(() => {
sdk = new CoreSDKClient({
sidecarAddr: "http://127.0.0.1:7233",
tenantId: "acme",
serviceName: "test",
devMode: true,
});
});
describe("orders:read", () => {
it("allows members to read their own tenant", async () => {
const allowed = await sdk.policy().evaluate("data.authz.allow", {
tenantId: "acme",
subject: "usr_2xK9",
action: "orders:read",
resource: "ord_7mP3",
resourceOwner: "usr_2xK9",
context: { roles: ["member"] },
});
expect(allowed).toBe(true);
});
it("denies members from reading another tenant's resource", async () => {
const allowed = await sdk.policy().evaluate("data.authz.allow", {
tenantId: "acme",
subject: "usr_2xK9",
action: "orders:read",
resource: "ord_9999",
resourceOwner: "usr_other",
context: { roles: ["member"] },
});
expect(allowed).toBe(false);
});
});CI integration
Add policy tests to your CI pipeline using coresdk policy test with the --ci flag. Exit code is non-zero on any failure.
#!/usr/bin/env bash
set -euo pipefail
# Lint policies
coresdk policy lint ./policies
# Run all fixture-based tests
coresdk policy test ./policies --fixtures ./tests/policy-fixtures/ --ci
# Benchmark — fail if p99 exceeds 2 ms (CI gate threshold)
coresdk policy bench ./policies --p99-max-us 2000 --ciExample GitHub Actions step:
- name: Test authorization policies
run: |
coresdk policy lint ./policies
coresdk policy test ./policies --fixtures ./tests/policy-fixtures/ --ci
coresdk policy bench ./policies --p99-max-us 2000 --ci
env:
CORESDK_ENV: test
CORESDK_POLICY_DIR: ./policiesNext steps
- Writing Rego Policies — policy language reference and loading options
- Attribute-Based Access Control — test ABAC rules with resource and claim attributes
- Audit Log — review policy decisions in production