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
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"});
}

Reusing 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"});
}

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 →