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.
Feature | Description |
---|---|
test | Introduces a test block, which lets you run a set of unit tests |
setup | Declares facts to make accessible for the lifetime of your test |
assert | Succeeds if the expression evaluates to true |
assert_not | Succeeds if the expression evaluates to false |
allow | By default, Polar includes an alias to has_permission , which is a common shorthand rule |
test fixture | Declare a test fixture block comprising a set of facts that you can reuse across multiple tests |
fixture | Reference 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 assertionstest "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-resourcesactor 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-blocksresource 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-teststest "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.