This guide covers the full range of testing capabilities provided by the mpe test command, helping you verify your policies work correctly during development and before deployment.
| Command | Description |
|---|---|
mpe test decision | Test a single policy decision with PORC input |
mpe test decisions | Run a suite of tests from a YAML file |
mpe test mapper | Test mapper transformations |
mpe test envoy | Test full Envoy-to-decision pipeline |
The mpe test decision and mpe test envoy commands output an AccessRecord—a JSON document that captures everything about the policy evaluation. The most important field is decision, which will be either "GRANT" or "DENY".
{
"decision": "GRANT",
"principal": { "subject": "user123", "realm": "" },
"operation": "api:resource:read",
"resource": "mrn:app:resource:123",
"references": [ ... ],
"porc": "{ ... }"
}
Key fields you'll see:
| Field | Description |
|---|---|
decision | The final outcome: "GRANT" or "DENY" |
principal | Who made the request (extracted from PORC) |
operation | What action was attempted |
resource | What resource was accessed |
references | Details about each policy evaluated (useful for debugging) |
To quickly extract just the decision, pipe the output through jq:
mpe test decision -b my-domain.yml -i input.json | jq .decision
# Output: "GRANT" or "DENY"
For human-readable output during debugging, add the --pretty-log flag:
mpe test decision -b my-domain.yml -i input.json --pretty-log
This produces indented JSON with the porc field expanded, making it easier to inspect. For a complete guide to interpreting AccessRecords, see Reading Access Records.
The mpe test mapper command outputs a PORC expression—the standardized format that policies evaluate. This shows how your mapper transforms external input (like an Envoy request) into the Principal, Operation, Resource, and Context structure:
{
"principal": { "sub": "user@example.com", "mroles": [...] },
"operation": "my-service:http:get",
"resource": { "id": "mrn:http:my-service/api/users/123", ... },
"context": { ... }
}
This is useful for verifying that your mapper correctly extracts identity information and constructs the operation and resource fields.
Test a policy with a PORC expression:
# Create a PORC input file
cat > input.json << 'EOF'
{
"principal": {
"sub": "user123",
"mroles": ["mrn:iam:role:admin"]
},
"operation": "api:resource:read",
"resource": {
"id": "mrn:app:resource:123",
"group": "mrn:iam:resource-group:default"
},
"context": {}
}
EOF
# Test the decision
mpe test decision -b my-domain.yml -i input.json
Authenticated User with Admin Role:
{
"principal": {
"sub": "admin-user",
"mroles": ["mrn:iam:role:admin"],
"mgroups": ["mrn:iam:group:admins"]
},
"operation": "api:users:delete",
"resource": {
"id": "mrn:app:user:456",
"group": "mrn:iam:resource-group:default"
}
}
Unauthenticated Request (should be denied):
{
"principal": {},
"operation": "api:data:read",
"resource": {
"id": "mrn:app:data:secret",
"group": "mrn:iam:resource-group:default"
}
}
Resource with Classification:
{
"principal": {
"sub": "user123",
"mroles": ["mrn:iam:role:analyst"],
"mclearance": "HIGH"
},
"operation": "vault:secret:read",
"resource": {
"id": "mrn:vault:secret:123",
"group": "mrn:iam:resource-group:classified",
"classification": "MODERATE",
"owner": "user456"
}
}
Mappers transform external inputs into PORC expressions. Test them separately to verify the transformation logic:
# Create an Envoy-style input
cat > envoy-input.json << 'EOF'
{
"request": {
"http": {
"method": "GET",
"path": "/api/users/123",
"headers": {
"authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
},
"destination": {
"principal": "spiffe://cluster.local/ns/default/sa/my-service"
}
}
EOF
# Test the mapper
mpe test mapper -b my-domain.yml -i envoy-input.json
The output is a PORC expression showing how the mapper transformed the input. You can inspect specific fields with jq:
# Check what operation was generated
mpe test mapper -b my-domain.yml -i envoy-input.json | jq .operation
# Verify the principal was extracted correctly
mpe test mapper -b my-domain.yml -i envoy-input.json | jq .principal
Test the complete Envoy input to decision pipeline:
mpe test envoy -b my-domain.yml -i envoy-input.json
This runs both stages and outputs an AccessRecord (same format as mpe test decision):
# Get the final decision
mpe test envoy -b my-domain.yml -i envoy-input.json | jq .decision
For automated testing and CI/CD pipelines, define multiple test cases in a YAML file and run them with mpe test decisions:
# tests.yaml
tests:
- name: admin-can-read
description: Admin role can perform read operations
porc:
principal:
sub: admin@example.com
mroles:
- mrn:iam:role:admin
operation: api:documents:read
resource:
id: mrn:app:document:123
group: mrn:iam:resource-group:default
result:
allow: true
- name: viewer-cannot-delete
description: Viewer role cannot delete resources
porc:
principal:
sub: viewer@example.com
mroles:
- mrn:iam:role:viewer
operation: api:documents:delete
resource:
id: mrn:app:document:123
group: mrn:iam:resource-group:default
result:
allow: false
- name: unauthenticated-denied
description: Requests without a principal are denied
porc:
principal: {}
operation: api:documents:read
resource:
id: mrn:app:document:123
group: mrn:iam:resource-group:default
result:
allow: false
Run the entire suite:
mpe test decisions -b my-domain.yml -i tests.yaml
Output shows pass/fail status for each test:
admin-can-read: PASS
viewer-cannot-delete: PASS
unauthenticated-denied: PASS
3/3 tests passed
Use --test to run only tests matching a glob pattern:
# Run only admin-related tests
mpe test decisions -b my-domain.yml -i tests.yaml --test "admin-*"
# Run multiple patterns
mpe test decisions -b my-domain.yml -i tests.yaml --test "admin-*" --test "viewer-*"
role-admin-*, phase-operation-*You can load multiple PolicyDomain bundles:
mpe test decision \
-b base-policies.yml \
-b app-specific.yml \
-i input.json
Policies from all bundles are available for evaluation.
When tests fail unexpectedly, enable OPA trace output to see step-by-step evaluation:
mpe --trace test decision -b my-domain.yml -i input.json
Combine with --pretty-log for easier reading:
mpe --trace --pretty-log test decision -b my-domain.yml -i input.json
The trace shows each rule entry, exit, and failure. For a complete guide to interpreting trace output, see Debugging Policies.
Read input from stdin:
echo '{"principal": {"sub": "test", "mroles": ["mrn:iam:role:user"]}, "operation": "test:op", "resource": {"id": "test", "group": "mrn:iam:resource-group:default"}}' | \
mpe test decision -b my-domain.yml -i -
:::tip[stdin is the default]
You may omit -i - when you wish to use stdin-based input because it is the default.
:::
{
"principal": {},
"operation": "api:secure:read",
"resource": {
"id": "mrn:app:data:secret",
"group": "mrn:iam:resource-group:default"
}
}
Expected: (no principal)
{
"principal": {
"sub": "user1",
"mroles": ["mrn:iam:role:viewer"]
},
"operation": "api:data:write",
"resource": {
"id": "mrn:app:data:123",
"group": "mrn:iam:resource-group:default"
}
}
Expected: (viewer role typically lacks write permissions)
{
"principal": {
"sub": "user123",
"mroles": ["mrn:iam:role:user"]
},
"operation": "api:item:delete",
"resource": {
"id": "mrn:app:item:456",
"owner": "user123",
"group": "mrn:iam:resource-group:owner-access"
}
}
Expected: (owner access, assuming role and resource-group policies permit owner operations)
--trace (see Debugging Policies)--pretty-log for debugging: Human-readable output makes it easier to understand evaluation resultsCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |