Skip to main content

mpe test

Test policy decisions and mappers with various inputs.

Synopsis

mpe test decision --bundle <file> --input <file>
mpe test decisions --bundle <file> --input <file>
mpe test mapper --bundle <file> --input <file>
mpe test envoy --bundle <file> --input <file>

Subcommands

SubcommandDescription
decisionTest a single policy decision with PORC input
decisionsRun a suite of policy decision tests from a YAML file
mapperTest mapper transformations
envoyTest full Envoy-to-decision pipeline

test decision

Evaluate a policy decision based on a PORC expression.

Options

OptionAliasDescription
--bundle-bPolicyDomain bundle file(s)
--input-iPORC input file or - for stdin
--testSpecific test to run

Example

# Using a file
mpe test decision -b my-domain.yml -i porc-input.json

# Using stdin
echo '{"principal":{"sub":"user1"},"operation":"api:test","resource":{"id":"test"}}' | \
mpe test decision -b my-domain.yml -i -

# Multiple bundles
mpe test decision -b base.yml -b override.yml -i input.json

Input Format

{
"principal": {
"sub": "user@example.com",
"mroles": ["mrn:iam:role:admin"]
},
"operation": "api:users:read",
"resource": {
"id": "mrn:app:users:123",
"group": "mrn:iam:resource-group:default"
},
"context": {}
}

Output

The command outputs a JSON-encoded Access Record (defined in protos/manetu/policyengine/events/v1/message.proto). This record contains detailed information about the decision process.

Key fields in the Access Record:

FieldDescription
decisionThe final outcome: "GRANT", "DENY", or "UNSPECIFIED"
principalThe authenticated principal (subject and realm)
operationThe operation from the PORC expression
resourceThe resource MRN from the PORC expression
referencesList of policy bundles consulted, each with its own decision and phase
porcThe fully realized PORC JSON
system_overrideWhether an operation phase (Phase 1) decision bypass was applied
grant_reason / deny_reasonReason for any bypass (e.g., PUBLIC, JWT_REQUIRED)

Using jq to extract specific fields:

# Get just the decision (returns "GRANT", "DENY", or "UNSPECIFIED")
mpe test decision -b my-domain.yml -i input.json | jq .decision

# View all policy references consulted
mpe test decision -b my-domain.yml -i input.json | jq .references

# Check for any bypass reasons
mpe test decision -b my-domain.yml -i input.json | jq '{grant_reason, deny_reason, system_override}'

test decisions

Run a suite of policy decision tests from a YAML file. This command is designed for automated testing and CI/CD pipelines, allowing you to define multiple test cases with expected outcomes in a single file.

Options

OptionAliasDescription
--bundle-bPolicyDomain bundle file(s)
--input-iTest suite YAML file (required)
--testRun only tests matching this glob pattern (can be repeated)

Example

# Run all tests in a suite
mpe test decisions -b my-domain.yml -i tests.yaml

# Run a specific test for debugging
mpe test decisions -b my-domain.yml -i tests.yaml --test my-failing-test

# Run tests matching a pattern
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-*"

Input Format

The test suite is a YAML file containing a list of test cases. Each test case specifies:

  • name: A unique identifier for the test
  • description: What the test verifies
  • porc: The PORC expression to evaluate
  • result.allow: The expected outcome (true for GRANT, false for DENY)
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: Unauthenticated users cannot access protected resources
porc:
principal: {}
operation: api:protected:read
resource:
id: mrn:app:resource:1
group: mrn:iam:resource-group:default
result:
allow: false

Output

The command outputs the result of each test, followed by a summary:

admin-can-read: PASS
viewer-cannot-delete: PASS
unauthenticated-denied: PASS

3/3 tests passed

On failure, the output shows what was expected vs. what was received:

admin-can-read: PASS
viewer-cannot-delete: FAIL (expected allow=false, got allow=true)

1/2 tests passed

Exit Codes

CodeDescription
0All tests passed
1One or more tests failed, or an error occurred
CI/CD Integration

The test decisions command is designed for CI/CD pipelines. The exit code directly reflects test outcomes, making integration straightforward:

# In your CI pipeline
mpe test decisions -b domain.yml -i tests.yaml
# Exit code 0 = all tests passed
# Exit code 1 = at least one test failed

Debugging Failed Tests

When investigating why a test is failing, use the --trace flag to see the full AccessRecord and OPA trace output:

mpe --trace test decisions -b domain.yml -i tests.yaml --test failing-test-name

This outputs:

  • OPA trace: Shows rule evaluation order, variable bindings, and decision path
  • AccessRecord: Shows which policies were evaluated, their decisions, and phase information

The trace output goes to stderr, so it won't interfere with test result parsing.

test mapper

Test mapper transformation of external input to PORC.

Options

OptionAliasDescription
--bundle-bPolicyDomain bundle file(s)
--input-iExternal input file or - for stdin
--name-nDomain name when using multiple bundles
--opa-flagsAdditional OPA flags
--no-opa-flagsDisable OPA flags

Example

mpe test mapper -b my-domain.yml -i envoy-input.json

Input Format (Envoy-style)

{
"request": {
"http": {
"method": "GET",
"path": "/api/users/123",
"headers": {
"authorization": "Bearer eyJhbGciOiJIUzI1NiJ9..."
}
}
},
"destination": {
"principal": "spiffe://cluster.local/ns/default/sa/api-server"
}
}

Output

The command outputs a JSON-encoded PORC expression showing how the mapper transformed the external input. This is useful for debugging mappers and verifying that external requests are correctly translated to PORC.

{
"principal": {
"sub": "user@example.com"
},
"operation": "api-server:http:get",
"resource": {
"id": "http://api-server/api/users/123",
"group": "mrn:iam:resource-group:default"
},
"context": { ... }
}

Using jq to inspect specific fields:

# Extract just the principal
mpe test mapper -b my-domain.yml -i envoy-input.json | jq .principal

# Check the generated operation
mpe test mapper -b my-domain.yml -i envoy-input.json | jq .operation

# View resource details
mpe test mapper -b my-domain.yml -i envoy-input.json | jq .resource

test envoy

Execute the complete pipeline: Envoy input → mapper → PORC → decision.

Options

OptionAliasDescription
--bundle-bPolicyDomain bundle file(s)
--input-iEnvoy input file or - for stdin
--name-nDomain name when using multiple bundles
--opa-flagsAdditional OPA flags
--no-opa-flagsDisable OPA flags

Example

mpe test envoy -b my-domain.yml -i envoy-request.json

Output

Like mpe test decision, this command outputs a JSON-encoded Access Record containing the full decision details. See the test decision output section for the complete field reference.

Using jq to extract specific fields:

# Get just the decision
mpe test envoy -b my-domain.yml -i envoy-request.json | jq .decision

# View the PORC that was generated from the Envoy input
mpe test envoy -b my-domain.yml -i envoy-request.json | jq .porc

# See which policy bundles were evaluated
mpe test envoy -b my-domain.yml -i envoy-request.json | jq .references

Trace Output

Enable detailed OPA trace logging:

mpe --trace test decision -b my-domain.yml -i input.json

This shows:

  • Rule evaluation order
  • Data lookups
  • Variable bindings
  • Decision path

Testing Scenarios

Test Authentication

{
"principal": {},
"operation": "api:secure:read",
"resource": {"id": "test", "group": "mrn:iam:resource-group:default"}
}

Expected:

DENY
(no principal — fails at operation phase before identity evaluation)

Test Role Access

{
"principal": {
"sub": "user1",
"mroles": ["mrn:iam:role:viewer"]
},
"operation": "api:data:write",
"resource": {"id": "data", "group": "mrn:iam:resource-group:default"}
}

Expected:

DENY
(viewer can't write)

Test Owner Access

{
"principal": {"sub": "owner123"},
"operation": "api:resource:delete",
"resource": {
"id": "mrn:app:item:456",
"group": "mrn:iam:resource-group:default",
"owner": "owner123"
}
}

Expected:

GRANT
(owner access)

Exit Codes

CodeDescription
0Command executed successfully
1Error (invalid input, missing files, etc.)

Note: Exit code 0 doesn't mean GRANT—check the output for the decision.

CI/CD Integration with jq halt_error

For CI pipelines that need to fail based on the decision outcome, use jq's halt_error function:

# Fail CI if access is denied
mpe test decision -b domain.yml -i input.json | \
jq 'if .decision == "DENY" then "Access denied" | halt_error(1) else . end'

# Fail CI if access is granted (for negative test cases)
mpe test decision -b domain.yml -i input.json | \
jq 'if .decision == "GRANT" then "Expected DENY" | halt_error(1) else . end'

This pipes the AccessRecord through jq, which exits with code 1 and prints the error message if the condition is met.