# `Licet.Validate`
[🔗](https://github.com/netoum/licet/blob/v0.1.0/lib/licet/validate.ex#L1)

Tests a permission model with a SpiceDB validation file.

A validation file bundles a schema, some relationships, and the answers you
expect. Running it tells you whether the model behaves as intended, without a
database. Use it in your test suite, or from the command line with
`mix licet.validate`. [Testing](testing.md) shows a complete example.

The file is the YAML that [play.authzed.com](https://play.authzed.com) exports
and `zed validate` reads:

* `schema`, the `.zed` text
* `relationships`, one relationship per line, with `//` comments
* `assertions`, with `assertTrue` and `assertFalse` lists of checks
* `validation`, the expected relations: for each `resource#permission`, every
  subject and the relations that hold it, as `[user:tom] is <document:1#viewer>`

Checks run at the current time, so a relationship whose `[expiration:...]`
has passed does not count. Relationships are held in memory; nothing is
written to Postgres.

# `expected`

```elixir
@type expected() :: [{{String.t(), String.t(), String.t()}, [map()]}]
```

The parsed `validation` section: each `{type, id, relation}` resource with
its expected subject lines.

# `failure`

```elixir
@type failure() ::
  %{assertion: String.t(), expected: :allowed | :denied, got: term()}
  | %{relation: String.t(), message: String.t()}
```

# `t`

```elixir
@type t() :: %Licet.Validate{
  assert_false: [String.t()],
  assert_true: [String.t()],
  relationships: [Licet.Relationship.t()],
  schema: Licet.Schema.t(),
  validation: expected() | nil
}
```

# `expected_relations`

```elixir
@spec expected_relations(String.t()) :: {:ok, String.t()} | {:error, Licet.Error.t()}
```

Computes the `validation` section from the schema and relationships.

For each `resource#permission` key the file already lists, the result lists
every subject and the relations that grant it, in the same YAML that the
SpiceDB playground writes when it updates expected relations. Review the
output once, then commit it to the file.

## Examples

    iex> Licet.Validate.expected_relations("""
    ...> schema: |-
    ...>   definition user {}
    ...>   definition document {
    ...>     relation viewer: user
    ...>     permission view = viewer
    ...>   }
    ...> relationships: |-
    ...>   document:1#viewer@user:ada
    ...> validation:
    ...>   document:1#view: []
    ...> """)
    {:ok, "document:1#view:\n    - '[user:ada] is <document:1#viewer>'\n"}

# `explain`

```elixir
@spec explain(String.t(), String.t()) ::
  {:ok, :allowed | :denied, map()} | {:error, Licet.Error.t()}
```

Explains one check against the schema and relationships in the validation YAML.

`line` is an assertion string such as `"document:1#view@user:ada"`. The check
runs in memory, the same way as `run/1`, and returns the answer with a trace
of the relations evaluated beneath it. Use it from the playground, or when
debugging a failing assertion without starting Postgres.

## Examples

    iex> {:ok, :allowed, trace} =
    ...>   Licet.Validate.explain(
    ...>     """
    ...>     schema: |-
    ...>       definition user {}
    ...>       definition document {
    ...>         relation viewer: user
    ...>         permission view = viewer
    ...>       }
    ...>     relationships: |-
    ...>       document:1#viewer@user:ada
    ...>     """,
    ...>     "document:1#view@user:ada"
    ...>   )
    iex> {trace.resource, trace.permission, trace.result}
    {"document:1", "view", :allowed}

# `parse`

```elixir
@spec parse(String.t()) :: {:ok, t()} | {:error, Licet.Error.t()}
```

Parses validation YAML without running it.

## Examples

    iex> {:ok, file} =
    ...>   Licet.Validate.parse("""
    ...>   schema: "definition user {}"
    ...>   assertions:
    ...>     assertFalse:
    ...>       - user:ada#member@user:bob
    ...>   """)
    iex> file.assert_false
    ["user:ada#member@user:bob"]
    iex> file.relationships
    []

# `run`

```elixir
@spec run(String.t()) :: :ok | {:error, Licet.Error.t() | [failure()]}
```

Runs validation YAML: every assertion, then every expected relation.

Returns `:ok`, a list of failures, or an error when the file itself is
invalid. An assertion failure is a map with the `:assertion`, the
`:expected` answer, and the answer it `:got`. An expected-relations failure
has the `:relation` and SpiceDB's `:message`.

## Examples

    iex> Licet.Validate.run("""
    ...> schema: |-
    ...>   definition user {}
    ...>   definition document {
    ...>     relation viewer: user
    ...>     permission view = viewer
    ...>   }
    ...> relationships: |-
    ...>   document:1#viewer@user:ada
    ...> assertions:
    ...>   assertTrue:
    ...>     - document:1#view@user:ada
    ...>   assertFalse:
    ...>     - document:1#view@user:ada
    ...> """)
    {:error, [%{assertion: "document:1#view@user:ada", expected: :denied, got: {:ok, :allowed}}]}

# `run_file`

```elixir
@spec run_file(Path.t()) :: :ok | {:error, Licet.Error.t() | [failure()]}
```

Reads the file at `path` and runs it with `run/1`.

## Examples

    test "document permissions" do
      assert :ok = Licet.Validate.run_file("test/permissions/documents.yaml")
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
