Skip to main content
CoreSDK
Authorization & Policy

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 failed

Fixture 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.json

member-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 --ci

Example 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: ./policies

Next steps

On this page