# `Cuerdo.ArazzoCase`

Provides an `ExUnit.CaseTemplate` for automatically generating tests from Arazzo workflows.

Each workflow in the document is executed as a test, with automatically generated inputs derived
from the workflow's input schema.

## Basic Usage
Define `use Cuerdo.ArazzoCase` in your test module, and add `arazzo_document_test` macro
referencing the document you want to test
```elixir
defmodule MyArazzoTest do
  use Cuerdo.ArazzoCase

  arazzo_document_test document: YamlElixir.read_from_file!("spec/to/arazzo.yaml")
end
```

## Filtering workflows
You can opt-in and opt-out from executing specific workflows via the `:only` and `:exclude`
options respectively. For example:

```elixir
# Executes "workflow1" and "workflow2"
arazzo_document_test only: ["workflow1", "workflow2"], document: ...

# Executes every workflow defined in the document except for "workflow1"
arazzo_document_test exclude: ["workflow1"], document: ...

# Executes "workflow1"
arazzo_document_test only: ["workflow1", "workflow2"], exclude: ["workflow2"], document: ...
```

## Customizing Generated Inputs
Consider for example a "book" input containing the book's title, author and ISBN:
```json
  {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "title": {"type": "string", "minLength": 1},
      "authorName": {"type": "string", "minLength": 1},
      "isbn": {"type": "string", "pattern": "^97(8|9)[0-9]{10}$"}
    }
  }
```
Valid ISBNs cannot be generated from JSON schemas. Values might match the regular expression
while failing the checksum validation. We can work around this issue via
the `:transform_inputs` option.

Define a function that generates valid ISBN identifiers:
```elixir
defmodule MyModule do
  def valid_isbn do
    StreamData.bind(MoreStreamData.from_regex("^97(8|9)[0-9]{10}$"), fn invalid_isbn ->
      {digits, _wrong_check_digit} = String.split_at(invalid_isbn, 12)
      StreamData.constant(digits <> to_string(check_digit(digits)))
    end)
  end

  defp check_digit(digits) do
    digits
    |> String.codepoints()
    |> Enum.with_index()
    |> Enum.sum_by(fn {digit, idx} ->
      digit = String.to_integer(digit)
      if(rem(idx, 2) == 0, do: digit, else: 3 * digit)
    end)
    |> then(fn total -> 10 - rem(total, 10) end)
  end
```

Define a transformation function that replaces the generated ISBN with a valid one. Notice
that the function **must** return a `t:StreamData.t/1` generator:
```elixir
def with_valid_isbn(book) do
  StreamData.bind(valid_isbn(), fn isbn ->
    StreamData.constant(Map.put(book, "isbn", isbn))
  end)
end
```

Pass the transformation function through `:transform_inputs` option, as an MFA tuple.
The function will be called for every input generated for the specified workflow, before
starting to execute the first workflow step:
```elixir
defmodule MyModuleTest do
  use Cuerdo.ArazzoCase

  arazzo_document_test transform_inputs: %{
    "createBookWorkflow" => {MyModule, :with_valid_isbn, []}
  },
  document: ...
end
```

## Running the same document multiple times
Declaring multiple `arazzo_document_test` allows for different execution strategies for
the same document, such as:
- Running expensive/slower workflows fewer times
- Running workflows with different input transformations

```elixir
defmodule MyArazzoTest do
  use Cuerdo.ArazzoCase

  arazzo_document_test transform_inputs: %{"workflowId" => {Module, :function, []}},
                       document: YamlElixir.read_from_file!("path/to/arazzo.yaml")

  arazzo_document_test document: YamlElixir.read_from_file!("path/to/arazzo.yaml")
end
```

Keep in mind that the test names are generated based on each workflow's `workflowId` field. If
the Arazzo documents contains workflows with the same name then you **must** pass the `:prefix`
option, otherwise the test generation will fail:
```elixir
defmodule MyArazzoTest do
  use Cuerdo.ArazzoCase

  arazzo_document_test prefix: "custom_inputs",
                       transform_inputs: %{"workflowId" => {Module, :function, []}},
                       document: YamlElixir.read_from_file!("path/to/arazzo.yaml")

  arazzo_document_test prefix: "default_inputs",
                       document: YamlElixir.read_from_file!("path/to/arazzo.yaml")
end
```

# `arazzo_document_test`
*macro* 

Generates property tests for every workflow in the Arazzo document.

## Options

  - `:document` (`t:map/0`) - Arazzo document
  - `:only` (`list(String.t())`) - List of `workflowId` to execute from the document. If
  provided, workflows that are not in the `:only` option are not tested. Defaults to
  executing all workflows in the document
  - `:exclude` (`list(String.t())`) - List of `workflowId` to exclude from the document.
  If both `:only` and `:exclude` are passed then the workflows from `:only` that are not in
  `:excluded` are executed
  - `:max_runs` (`t:pos_integer/0`) - The number of cases to run. Defaults to `1`
  - `:max_shrink_steps` (`t:pos_integer/0`) - Maximum number of shrinking steps to apply.
  Defaults to `0`
  - `:transform_inputs` - A map of `%{workflowId => transformation}`, where `transformation` is
  a function that generates `t:StreamData.t/1` based on the initially generated value, specified
  as `{Module, :function_name}`, where `:function_name` is a 1-arity function
  - `:json_schema_resolver` - The resolver to use for fetching JSON Schemas. Defaults to
  a do-nothing resolver. Use this option if any OpenAPI document in your workflow references
  remote schemas. Refer to [JSV Resolvers](`e:jsv:resolvers.html`) section for more information
  - `:prefix` - (`t:String.t/0`) - The test name prefix

---

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