Cuerdo (cuerdo v0.1.0)

Copy Markdown

Cuerdo is an Arazzo 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.

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

# 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

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 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

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, you can define a test module as follows

# 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

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
  • in: "cookie"
  • dependsOn: Same as 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.