Lint & Test Policy¶
Preface¶
This guide will explain how to lint and test an OPA policy. Linting will be performed using Regal whilst testing will utilize opa test
.
Linting¶
Running Lints¶
The Regal linter is used to evaluate a large suite of rules which are designed to detect likely bugs and prevent non-idiomatic or non-performant code. Regal linting can be run using regal lint
. The policy bundle we are linting should be passed in as the first agument.
Example
regal lint policy/
Testing¶
Running Tests¶
OPA tests can be evaluated using the opa test
. The policy bundle we are testing should be passed in as the first argument. As with all commands, the --v1-compatible
flag is recommended.
Example
opa test --v1-compatible policy/
Packages and Imports¶
OPA test packages should be named equivilently to their policy counterpart with the addition of a _test
suffix. Similarly, the file name should match the file name of the package with a _test
suffix.
Example
package system
import rego.v1
# METADATA
# entrypoint: true
main := {"allow": allow}
default allow := false
package system_test
import data.system
import rego.v1
Note
The input
and data
namespaces are imported by default and provide access to request inputs and pre-loaded data respectively.
Writing Tests¶
Test cases should begin with test_
with a name which describes the expected behaviour and the conditions under which it should occur. Typically test cases follow an AND pattern, ensuring the fail if any one of the expressions within fails. Where an expression is expected to fail, you should prefix it with not
.
Note
Whilst it is useful to test that rules pass where expected, it is more important to check that they fail safely and correctly deny access in all instances where it should not be granted.
Example
package system
import rego.v1
# METADATA
# entrypoint: true
main := {"allow": allow}
default allow := false
allow if {
input.action == "get_foo"
"read_foo" in data.subjects[input.subject].permissions
}
package system_test
import data.system
import rego.v1
test_member_allowed if {
system.main.allow with input as {"action": "get_foo", "subject": "bob"}
with data.subjects as {"bob": {"permissions": ["read_foo]}}
}
test_unknown_action_denied if {
not system.main.allow with input.action as "write_bar"
}
test_non_member_denied if {
not system.main.allow with input as {"action": "get_foo", "subject": "bob"}
with data.subjects as {"bob": {"permissions": []}}
}
Continious Integration¶
It is strongly recommeneded you implement automated linting and testing as part of your Continious Integration (CI) pipeline, an example for doing this in GitHub Actions is shown below.
GitHub Actions lint workflow
name: Policy Lint
on:
workflow_call:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4.2.2
- name: Setup Regal
uses: StyraInc/setup-regal@v1.0.0
with:
version: latest
- name: Lint
run: >
regal
lint
--format github
--config-file regal.yaml
./policy
GitHub Actions test workflow
name: Policy Test
on:
workflow_call:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4.2.2
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2.2.0
with:
version: latest
- name: Test
run: opa test ./policy -v