Polar Tests

In your policy (i.e. the collection of rules for your environment), Polar has a built-in test feature that lets you run unit tests of literal-value queries using a set of facts only visible for the lifetime of the test.

FeatureDescription
testIntroduces a test block, which lets you run a set of unit tests
setupDeclares facts to make accessible for the lifetime of your test
assertSucceeds if the expression evaluates to true
assert_notSucceeds if the expression evaluates to false
iffWhen used with a variable, causes an assert to succeed if and only if all values of the variable that satisfy the assertion exist in a list provided to the assertion
allowBy default, Polar includes an alias to has_permission, which is a common shorthand rule
test fixtureDeclare a test fixture block comprising a set of facts that you can reuse across multiple tests
fixtureReference a test fixture inside a setup block to include its facts in the setup block

Here's an annotated pseudo-example demonstrating the syntax:


test fixture fixture_name {
# semicolon-separated list of facts
...
}
test "test name" {
setup {
fixture fixture_name;
# semicolon-separated list of facts
...
}
# semicolon-separated list of tests of form:
# (assert|assert_not) "rule predicate"("comma-separated list of object literals")
...
}

Simple example

Here's a simple example policy including tests, relying only on primitive types.


# Rule: A parent and their children are considered family.
family(a: String, b: String) if
parent(a, b) or parent(b, a);
# Create a test so we can introduce temporary facts and make assertions
test "Polar ref family test" {
# Set up our temporary facts that exist only for our test's lifetime
setup {
# Fact: Bernie has two parents, Pat and Morgan
parent("Bernie", "Pat");
parent("Bernie", "Morgan");
}
# Validate that our policy produces the expected results with our temporary facts
assert family("Bernie", "Pat");
assert family("Morgan", "Bernie");
# Our rule does not state that the parents of a child are family
assert_not family("Pat", "Morgan");
# Rules without matching parameters are false
assert_not family(true, "Morgan");
}

Complex example

Here is a more complex example that leverages Polar's abstract types and more closely mirrors how we expect you to use Oso.


# Actors are the who. Most of the time, this is a User
# https://www.osohq.com/docs/guides/model-your-apps-authz#actors-and-resources
actor User {}
# This is a resource block that is used for grouping authorization
# logic pertaining to a particular type of resource.
# A resource represents an application component that we wish to protect.
# https://www.osohq.com/docs/reference/glossary#resource-blocks
resource Organization {
roles = ["viewer", "owner"];
permissions = ["view", "edit"];
# These are permissions for the Organization resource
"view" if "viewer";
"edit" if "owner";
# Organization owners inherit all permissions that Organization viewers have
"viewer" if "owner";
}
# This is an example of a different resource block
resource Repository {
roles = ["viewer", "owner", "contributor"];
permissions = ["view", "edit", "create"];
# This is an example of how we can define the relationship
# between resources. Relations are set within the resource block.
# This relation is named parent and it says that Repository resource
# is related to Organization.
# https://www.osohq.com/docs/reference/more/resource-blocks#relation-declarations
relations = { parent: Organization };
"view" if "viewer";
"edit" if "contributor";
"create" if "owner";
# contributors are also viewers
# owners are also contributors
"viewer" if "contributor";
"contributor" if "owner";
# roles are inherited from the parent organization
"viewer" if "viewer" on "parent";
"owner" if "owner" on "parent";
}
# These are examples of how to test the Policy logic.
# https://www.osohq.com/docs/guides/policy-tests
test "Organization roles and permissions" {
# Authorization decisions require data. This is where you can
# define the test data. The test data is defined in a format
# that Oso Cloud refers to as Facts.
# https://www.osohq.com/docs/concepts/oso-cloud-data-model#facts
setup {
has_role(User{"alice"}, "viewer", Organization{"example"});
has_role(User{"bob"}, "owner", Organization{"example"});
}
# This is how we assert that a user is authorized
# to perform a particular action or not
assert allow(User{"alice"}, "view", Organization{"example"});
assert allow(User{"bob"}, "view", Organization{"example"});
assert_not allow(User{"alice"}, "edit", Organization{"example"});
assert allow(User{"bob"}, "edit", Organization{"example"});
}
test "Repository roles and permissions" {
setup {
has_role(User{"alice"}, "viewer", Repository{"example"});
has_role(User{"bob"}, "owner", Repository{"example"});
has_role(User{"charlie"}, "contributor", Repository{"example"});
}
assert allow(User{"alice"}, "view", Repository{"example"});
assert allow(User{"bob"}, "view", Repository{"example"});
assert allow(User{"charlie"}, "view", Repository{"example"});
assert_not allow(User{"alice"}, "edit", Repository{"example"});
assert allow(User{"bob"}, "edit", Repository{"example"});
assert allow(User{"charlie"}, "edit", Repository{"example"});
assert_not allow(User{"alice"}, "create", Repository{"example"});
assert allow(User{"bob"}, "create", Repository{"example"});
assert_not allow(User{"charlie"}, "create", Repository{"example"});
}
test "Repository parent relation" {
setup {
has_relation(Repository{"example"}, "parent", Organization{"parentOrganization"});
has_role(User{"alice"}, "viewer", Organization{"parentOrganization"});
has_role(User{"bob"}, "owner", Organization{"parentOrganization"});
}
assert allow(User{"alice"}, "view", Repository{"example"});
assert allow(User{"bob"}, "view", Repository{"example"});
assert_not allow(User{"charlie"}, "view", Repository{"example"});
assert_not allow(User{"dave"}, "view", Repository{"example"});
assert_not allow(User{"alice"}, "edit", Repository{"example"});
assert allow(User{"bob"}, "edit", Repository{"example"});
assert_not allow(User{"charlie"}, "edit", Repository{"example"});
assert_not allow(User{"dave"}, "edit", Repository{"example"});
assert_not allow(User{"alice"}, "create", Repository{"example"});
assert allow(User{"bob"}, "create", Repository{"example"});
}

Reuse data across multiple tests

You can reuse facts across multiple tests by defining a test fixture block. Here's an example:


test fixture acme {
has_role(User{"alice"}, "admin", Organization{"acme"});
has_role(User{"bob"}, "member", Organization{"acme"});
has_relation(Repository{"foo"}, "organization", Organization{"acme"});
is_private(Repository{"foo"});
}
test "by default, members cannot read private repositories" {
setup {
fixture acme;
}
assert allow(User{"alice"}, "read", Repository{"foo"});
assert_not allow(User{"bob"}, "read", Repository{"foo"});
}
test "members can read private repositories if they have direct access" {
setup {
fixture acme;
has_role(User{"bob"}, "reader", Repository{"foo"});
}
assert allow(User{"bob"}, "read", Repository{"foo"});
}

Test multiple permissions in a single assertion

Oso Policy tests support variables in assertions. These allow you to test multiple permissions with a single assert statement. We'll illustrate this with some examples.

You can have at most one variable in any assert statement.

Suppose a Repository resource that defines 3 roles and 4 permissions.


actor User {}
resource Repository {
roles = ["member", "owner", "admin"];
permissions = ["read", "write", "archive", "delete"];
"member" if "owner";
"owner" if "admin";
"read" if "member";
"write" if "member";
"archive" if "owner";
"delete" if "admin";
}

In order to test this policy exhaustively, you need to test 12 total conditions (4 permissions for each of the 3 roles). In a typical test, that looks like this:


test "parent-child permissions" {
setup {
# alice is an admin on Repository "cool-repo"
has_role(User{"alice"}, "admin", Repository{"cool-repo"});
has_role(User{"bob"}, "owner", Repository{"cool-repo"});
has_role(User{"charlie"}, "member", Repository{"cool-repo"});
}
### ASSERTIONS ###
# Alice can perform all actions
assert allow(User{"alice"}, "read", Repository{"cool-repo"});
assert allow(User{"alice"}, "write", Repository{"cool-repo"});
assert allow(User{"alice"}, "archive", Repository{"cool-repo"});
assert allow(User{"alice"}, "delete", Repository{"cool-repo"});
# Bob can perform all actions except for "delete"
assert allow(User{"bob"}, "read", Repository{"cool-repo"});
assert allow(User{"bob"}, "write", Repository{"cool-repo"});
assert allow(User{"bob"}, "archive", Repository{"cool-repo"});
assert_not allow(User{"bob"}, "delete", Repository{"cool-repo"});
# Charlie can perform the "read" and "write actions, but not "archive" or "delete"
assert allow(User{"charlie"}, "read", Repository{"cool-repo"});
assert allow(User{"charlie"}, "write", Repository{"cool-repo"});
assert_not allow(User{"charlie"}, "archive", Repository{"cool-repo"});
assert_not allow(User{"charlie"}, "delete", Repository{"cool-repo"});
}

Instead of explicitly specifying each tested value, you can use variables to consolidate multiple assertions into a single statement. You express a variable in an assertion using the same name: Type syntax that you use for variables elsewhere in Polar.

Using the iff operator

When used with a variable, the iff operator allows you to assert that a statement passes if and only if all of the values of variable that satisfy the statement exist in a list that you supply to the assertion.

For example, you can use the iff operator to reduce the previous test from 12 assertions to 3


test "parent-child permissions" {
setup {
# alice is an admin on Repository "cool-repo"
has_role(User{"alice"}, "admin", Repository{"cool-repo"});
has_role(User{"bob"}, "owner", Repository{"cool-repo"});
has_role(User{"charlie"}, "member", Repository{"cool-repo"});
}
### ASSERTIONS ###
# Alice can perform all actions
assert allow(User{"alice"}, action: String, Repository{"cool-repo"}) iff
action in ["read", "write", "archive", "delete"];
# Bob can perform all actions except for "delete"
assert allow(User{"bob"}, action: String, Repository{"cool-repo"}) iff
action in ["read", "write", "archive"];
# Charlie can perform the "read" and "write actions, but not "archive" or "delete"
assert allow(User{"charlie"}, action: String, Repository{"cool-repo"}) iff
action in ["read", "write"];
}

There are two changes:

  1. The specific actions in the original assertions have been replaced by the variable action: String
  2. The values of action that are expected to satisfy the assertion are defined in a list associated with each iff action in statement

This lets you write a single assertion that confirms:

  • all actions in the list are allowed and
  • all actions that aren't in the list are denied

You can assert that a user has no permissions on a resource by passing an empty list to iff.


assert allow(User{"diane"}, action: String, Repository{"cool-repo"}) iff
action in [];

You can use variables for any types in your policy, not just strings. For example, this assertion includes a variable user to confirm that alice and bob have the archive permission, but charlie does not.


assert allow(user: User, "archive", Repository{"cool-repo"}) iff
user in [User{"alice"}, User{"bob"}];

Using wildcards

You can use a wildcard to assert that any value of a variable satisfies a condition. For example, you may have a global admin role that should always be able to perform any action, whether that action exists today or not. You can use a wildcard to assert this in a single statement, instead of having to remember to add explicit assertions each time you add a new permission.

Here's a version of the above policy that grants the admin role any permission:


actor User {}
resource Repository {
roles = ["member", "owner", "admin"];
permissions = ["read", "write", "archive", "delete"];
"member" if "owner";
"owner" if "admin";
permission if "admin";
"read" if "member";
"write" if "member";
"archive" if "owner";
}

You can use a wildcard variable to validate that the admin role is granted any possible permission.


assert allow(User{"alice"}, _action: String, Repository{"cool-repo"});

Note that the previous iff assertions will now fail, because the new policy allows actions that don't exist in the list. For example, if you invoke the previous assertion for alice:


assert allow(user: User, "archive", Repository{"cool-repo"}) iff
user in [User{"alice"}, User{"bob"}];

The test will fail with the following message:


Expected the assertion to hold if and only if `action` is in ["read", "write",
"archive", "delete"], but it holds when `action` is any String

Up next

  • Patterns to understand common ways to model your applications.

Talk to an Oso engineer

If you want to learn more about Polar, schedule a 1x1 with an Oso engineer. We're happy to help.

Get started with Oso Cloud →