Current section
Files
Jump to
Current section
Files
lib/cuerdo.ex
defmodule Cuerdo do
@moduledoc """
Cuerdo is an [Arazzo](https://spec.openapis.org/arazzo/v1.1.0) workflow runner and automatic property-based testing tool written in Elixir.
Arazzo is a community-driven specification within the OpenAPI Initiative, a project part of the
Linux Foundation Collaborative.
OpenAPI describes REST APIs, and Arazzo specifies how to combine multiple individual OpenAPI
endpoints as a "workflow". Workflows consist of one or more steps, where each step consists of
either an HTTP request referencing an OpenAPI endpoint, or another workflow. For more information,
refer to the [Arazzo official site](https://www.openapis.org/arazzo-specification).
Arazzo allows you to define workflows and acceptance criteria in a language-agnostic way. This
means you can test backend services written in different languages using Arazzo specification.
## Usage
Consider the following simple workflow. A single GET request with query parameters that queries
"people" entities, and validates that the returned object matches the input filters
```yaml
# specs/arazzo.yaml
- workflowId: getPeople
summary: Retrieves people from the archive
outputs:
firstMatchingName: $steps.getPeopleStep.outputs.firstMatchingName
inputs:
type: object
additionalProperties: false
required: ["min_age", "name"]
properties:
min_age:
type: integer
minimum: 0
name:
type: string
minLength: 1
steps:
- stepId: getPeopleStep
description: Lists people
operationId: getPeople
parameters:
- name: min_age
in: query
value: $inputs.min_age
- name: name
in: query
value: $inputs.name
successCriteria:
- condition: $statusCode == 200
- type: regex
context: $response.body#/0/name
condition: "^$inputs.name"
- type: jsonpath
context: $response.body
condition: $[?@.age >= $inputs.min_age]
outputs:
firstMatchingName: $response.body#/0/name
```
### Directly in code
You can run the workflow directly with specific inputs via `Cuerdo.Arazzo.run_workflow/3`, for
example
```elixir
inputs = %{"min_age" => 20, "name" => "John"}
arazzo_document = YamlElixir.read_from_file!("path/to/arazzo.yaml")
{:ok, context} = Cuerdo.Arazzo.run_workflow(inputs, "getPeople", arazzo_document)
```
If the workflow executed successfully, a `t:Cuerdo.Arazzo.Context.t/0` is returned, which contains
the workflow outputs, steps outputs and steps request+response strcucts. You can access them using
the `Cuerdo.Arazzo.Context` module
```elixir
iex> Cuerdo.Arazzo.Context.workflow_outputs(context, "getPeople")
%{"firstMatchingName" => "Johnathan"}
iex> Cuerdo.Arazzo.Context.step_outputs(context, "getPeople", "getPeopleStep")
%{"firstMatchingName" => "Johnathan"}
```
### ArazzoCase
You can execute the workflow(s) as part of a test suite, using `Cuerdo.ArazzoCase`.
Consider the same Arazzo workflow from [above](#module-usage), you can define a test module
as follows
```elixir
# test/myapp/get_people_test.exs
defmodule MyApp.GetPeopleTest do
use Cuerdo.ArazzoCase
arazzo_document_test document: YamlElixir.read_from_file!("path/to/arazzo.yaml")
end
```
The `Cuerdo.ArazzoCase.arazzo_document_test/1` macro generates a test for each workflow with random
inputs generated by `RockSolid.from_schema/2`. To exclude specific workflows, apply transformation
functions, and other options refer to [arazzo_document_test](`Cuerdo.ArazzoCase.arazzo_document_test/1`)
## Validations
On top of the validations defined by `successCriteria` field, the following non-Arazzo validations
are performed at every step:
- The request Content-Type header matches any of the Content-Type defined in the OpenAPI operation
- The request body matches the schema defined in the OpenAPI operation.
- All required parameters defined in the OpenAPI operation are present in the step definition
- The response body matches any of the schemas defined in the OpenAPI operation based on the
Content-Type response header and response status code
## Unsupported features and limitations
### AsyncAPI
All steps and workflows are assumed to execute synchronously and in the order they are defined.
AsyncAPI features and fields are unsupported as there is no current way of validating that a
message was pusblished to an out-of-band broker or queue
### Workflow
- `dependsOn`: The field is ignored. If a workflow depends on another workflow then it should
define a step that references the dependency.
- `successActions` and `failureActions` are ignored.
### Step
- `channelPath`, `correlationId`, `action`: Used exclusively by AsyncAPI.
- `onSuccess` and `onFailure`: Same as `successActions` and `failureActions` from [Workflow](#module-workflow)
- `in: "cookie"`
- `dependsOn`: Same as [Workflow](#module-workflow)
### Condition and Expression
- `xpath` and any XML functionality is unsupported
- `type` allows only strings. This means that JSONPath supports RFC-9535 version only,
and JSONPointer RFC-6901 respectively. Non-standad and legacy JSONPath and JSONPointer
versions are unsupported.
"""
end