Skip to main content
In a Polar policy, you can run built-in unit tests that evaluate literal-value queries against a set of temporary facts visible only during the test.

Test features

FeatureDescription
testDefines a test block containing unit tests.
setupDeclares facts available for the lifetime of the test.
assertPasses if the expression evaluates to true.
assert_notPasses if the expression evaluates to false.
iffWhen used with a variable, passes if and only if all variable values that satisfy the expression are in a provided list.
allowAlias for has_permission (common shorthand).
test fixtureDefines a reusable set of facts for multiple tests.
fixtureIncludes a test fixture’s facts in a setup block.

Test 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  

  #  (assert|assert_not) statements  

  ...
}

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:
actor User {}

resource Organization {
  roles = ["viewer", "owner"];
  permissions = ["view", "edit"];

  "view" if "viewer";
  "edit" if "owner";

  "viewer" if "owner";
}

# This is an example of a different resource block 
resource Repository {
  roles = ["viewer", "owner", "contributor"];
  permissions = ["view", "edit", "create"];

  relations = { parent: Organization };

  "view" if "viewer";
  "edit" if "contributor";
  "create" if "owner";

  "viewer" if "contributor";
  "contributor" if "owner";

  # roles are inherited from the parent organization
  "viewer" if "viewer" on "parent";
  "owner" if "owner" on "parent";
}

test "Organization roles and permissions" {
  # authorization decisions require data which we refer to as 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 facts across tests

You can reuse facts across multiple tests by defining a test fixture block:
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"});
}

Variables in assertions

Variables let you test multiple permissions in a single assertion.
Info: You can have at most one variable in any assert statement.
Suppose a Repository resource that defines three roles and four 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 twelve total conditions (four permissions for each of the three 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 writing a separate assert for each value, you can use variables to test multiple values in a single statement. In an assertion, declare a variable using the same name: Type syntax used for variables in the rest of Polar.

Using the iff operator

The iff operator lets you assert that a statement holds if and only if all matching values for a variable are exactly the ones you list in the test. When combined with a variable, iff checks two things in one assertion:
  • Every listed value passes the assertion.
  • No value outside the list passes the assertion.
This makes it possible to replace multiple explicit assertions with a single variable-based assertion. For example, the previous test can be reduced from twelve assertions to three:
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 delete  
  assert allow(User{"bob"}, action: String, Repository{"cool-repo"}) iff
  action in ["read", "write", "archive"];

  # Charlie can only read and write  

  assert allow(User{"charlie"}, action: String, Repository{"cool-repo"}) iff
  action in ["read", "write"];
}
What changed from explicit assertions:
  1. Specific actions were replaced with a variable: action: String.
  2. The allowed actions are listed in the iff action in [...] expression.
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 also use iff to confirm that no permissions are granted by passing an empty list:
assert allow(User{"diane"}, action: String, Repository{"cool-repo"}) iff
action in [];
Variables aren’t limited to strings; you can use any type in your policy. For example, check that only alice and bob have the archive permission:
assert allow(user: User, "archive", Repository{"cool-repo"}) iff
user in [User{"alice"}, User{"bob"}];

Using wildcards

A wildcard variable asserts that any value of that variable satisfies the condition—useful for roles or permissions that should always be allowed, even as new permissions are added over time. For example, suppose your policy grants the admin role every possible permission:
actor User {}

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

  "member" if "owner";
  "owner" if "admin";
  # admin gets all permissions  

  permission if "admin";

  "read" if "member";
  "write" if "member";
  "archive" if "owner";
}
You can test that admin has all possible actions with a single wildcard variable:
assert allow(User{"alice"}, _action: String, Repository{"cool-repo"});
This will pass if alice can perform any action of type String on "cool-repo", whether that action exists now or is added later. Important: Wildcard assertions will fail any previous iff tests for the same role, because iff checks that only the listed actions are allowed. For example, if you previously had:
assert allow(user: User, "archive", Repository{"cool-repo"}) iff
user in [User{"alice"}, User{"bob"}];
The test will fail with the following message:
assert allow(User{"alice"}, action: String, Repository{"cool-repo"}) iff  
  action in ["read", "write", "archive", "delete"];

Testing deny logic

You can test denial rules either by:
  • Using assert_not to check a specific permission is denied.
  • Using assert with iff to confirm that all permissions are denied.
    For example, the following rule denies all permissions to users with the is_banned fact:
# Deny all permissions to banned users  
allow(actor: Actor, action: String, resource: Resource) if
  # Actor has permission to perform the requested action on the resource ...  
  has_permission(actor, action, resource) and
  # ... and the actor isn't banned  
  not is_banned(actor);
Test cases might look like this:
test "denial logic" {
  setup {
    # Charlie is banned
    is_banned(User{"charlie"});
  }

  # Deny a specific permission  

  assert_not allow(User{"charlie"}, "read", Repository{"cool-repo"});
  # Deny all permissions 
  assert allow(User{"charlie"}, action: String, Repository{"cool-repo"}) iff action in [];
}