Examples

Copy from examples that already map to real endpoints.

The repo examples do double duty: onboarding material and parser/schema regression coverage.

Init scaffolds

Local demo-server suites

Public API and feature demos

Run them locally

$ PORT=3000 cargo run -p demo-server &
$ cargo run -p tarn -- run examples/demo-server/
$ tarn fmt examples/demo-server/ --check
$ tarn run examples/demo-server/ --select examples/demo-server/auth-user-lifecycle.tarn.yaml::login
$ tarn run examples/demo-server/ --ndjson --format json=reports/run.json

Use --select FILE[::TEST[::STEP]] to run a single test or step, --ndjson to stream events line-by-line while also writing a full report, and tarn fmt --check in CI to fail when example files drift from canonical YAML.

Streaming and selective execution

These three patterns cover the common automation shapes: streaming events to an event-driven consumer, scoping a rerun to a single test, and gating CI on canonical formatting.

Pipe --ndjson into jq (or any line-oriented consumer) and pull just the event type out of each record:

$ tarn run --ndjson | jq -r '.event'
# file_started step_finished step_finished test_finished file_finished done

Each line is a complete JSON object — file_started, step_finished, test_finished, file_finished, and a final done — so live dashboards, CI progress annotations, and LLM retry loops can all read the stream without parsing a full report.

Scope a rerun to a single named test with --select FILE::TEST (repeatable, ANDs with --tag):

$ tarn run --select tests/users.tarn.yaml::login --format json --json-mode compact
# runs just the "login" test inside tests/users.tarn.yaml

Append a third segment (FILE::TEST::STEP_INDEX) to drill into a single step. The selector grammar is the same one tarn-lsp's code lens emits on its tarn.runTest and tarn.runStep commands, so editor runs and terminal runs target identical slices.

Fail CI when canonical formatting drifts:

$ tarn fmt --check tests/
# exits non-zero if any file would be reformatted

tarn fmt --check shares tarn::format::format_document with the LSP's textDocument/formatting handler, so a file that passes --check in CI is byte-identical to what "Format Document" produces in any LSP client.

Each example is a complete .tarn.yaml file you can copy and run.

Basic health check

name: Health check
description: Simple smoke test
env:
  base_url: "http://localhost:3000"
steps:
  - name: GET /health
    request:
      method: GET
      url: "{{ env.base_url }}/health"
    assert:
      status: 200
      body:
        "$.status": "ok"

Login and authenticated request

name: Auth flow
tests:
  - name: Login and access
    steps:
      - name: POST /login
        request:
          method: POST
          url: "{{ env.base_url }}/login"
          body:
            username: "admin"
            password: "secret"
        capture:
          token: "$.token"
        assert:
          status: 200

      - name: GET /me
        request:
          method: GET
          url: "{{ env.base_url }}/me"
          auth:
            bearer: "{{ capture.token }}"
        assert:
          status: 200
          body:
            "$.username": "admin"

Full CRUD

name: User CRUD
setup:
  - name: Cleanup
    request:
      method: DELETE
      url: "{{ env.base_url }}/users/test-user"
    assert:
      status: { in: [200, 204, 404] }

tests:
  - name: User lifecycle
    steps:
      - name: Create user
        request:
          method: POST
          url: "{{ env.base_url }}/users"
          body:
            name: "{{ $name }}"
            email: "{{ $email }}"
        capture:
          user_id: "$.id"
        assert:
          status: 201
          body:
            "$.id": { type: string, not_empty: true }

      - name: Get user
        request:
          method: GET
          url: "{{ env.base_url }}/users/{{ capture.user_id }}"
        assert:
          status: 200

      - name: Update user
        request:
          method: PATCH
          url: "{{ env.base_url }}/users/{{ capture.user_id }}"
          body:
            name: "Updated Name"
        assert:
          status: 200
          body:
            "$.name": "Updated Name"

teardown:
  - name: Delete user
    request:
      method: DELETE
      url: "{{ env.base_url }}/users/{{ capture.user_id }}"
    assert:
      status: { in: [200, 204] }

Using faker data

name: Faker demo
env:
  base_url: "{{ env.BASE_URL }}"
steps:
  - name: Create user with faker
    request:
      method: POST
      url: "{{ env.base_url }}/users"
      body:
        name: "{{ $name }}"
        email: "{{ $email }}"
        username: "{{ $username }}"
        phone: "{{ $phone }}"
        role: "{{ $choice(admin, user, viewer) }}"
        tags: "{{ $words(3) }}"
        active: "{{ $bool }}"
    assert:
      status: 201