Guides
Running the examples
Start with the checkout, POSIX shell, and build in Getting started. Run commands from the repository root. Where a plan needs the fixture server, keep it in Terminal 1 in the foreground and run vero from Terminal 2. Stop it with Ctrl-C before switching modes: only one fixture can bind port 18080 at a time. The fixture is a disposable demonstration API, not a prerequisite for testing your own service.
| Plan | Extra requirement | Command in Terminal 2 |
|---|---|---|
rate-limit.yaml |
Plain fixture | bin/vero run examples/rate-limit.yaml |
orders.yaml |
Plain fixture; demonstration API key | ORDERS_API_KEY=example-key-1234 bin/vero run examples/orders.yaml |
wait-for-api.yaml |
Plain fixture | bin/vero run examples/wait-for-api.yaml |
graphql.yaml |
Plain fixture | bin/vero run examples/graphql.yaml |
ws.yaml |
Plain fixture | bin/vero run examples/ws.yaml |
exec.yaml |
POSIX shell for the checked-in migration script; no fixture | bin/vero run examples/exec.yaml |
fixture.yaml |
Python 3 for the checked-in migration script; no fixture | bin/vero run examples/fixture.yaml |
scripts.yaml |
Plain fixture; Node with global fetch, Python 3 on PATH |
bin/vero run examples/scripts.yaml |
sqlite.yaml |
Fixture started with SQLite persistence | bin/vero run examples/sqlite.yaml |
postgres.yaml |
Dedicated Postgres test database and two role DSNs | bin/vero run examples/postgres.yaml |
grpc.yaml |
A gRPC server for orders.proto; the fixture has none |
see below |
playwright.yaml |
Node >=20, npm, pinned Playwright/Chromium; plain fixture | Complete Playwright recipe |
For plain-fixture rows, start Terminal 1 with bin/vero-testserver, wait for it to print
vero-testserver listening on http://127.0.0.1:18080 (limit 3), then run the listed command in
Terminal 2. The demonstration API key is not a credential for your service. Every plan on this
page was run from a checkout while writing it, and the summary lines below are from those runs;
the plan each one exercises is on the example plans page with its source.
The fixture server
bin/vero-testserver is a small orders API with a token bucket per API key, written for these
plans and the repository's tests. --help lists its routes.
| Flag | Default | Meaning |
|---|---|---|
--addr |
127.0.0.1:18080 |
address to listen on |
--limit |
3 | token bucket capacity per API key; buckets never refill, only a reset does |
--sqlite file |
off | also write orders to this SQLite file (table orders), for the sqlite.yaml plan |
--postgres dsn |
off | also write orders to this Postgres database (table orders), connecting as a role that can write |
--tls |
off | serve HTTPS with a self-signed certificate generated at start, offering HTTP/2 and HTTP/1.1 |
--http1-only |
off | with --tls, offer only HTTP/1.1 |
Besides /healthz, /auth/token and the /orders routes, it has a /fixtures/ family the step
pages use to provoke things: /fixtures/status?code=N, /fixtures/slow?ms=N,
/fixtures/redirect?n=N, /fixtures/large?bytes=N, /fixtures/gzip, /fixtures/set-cookie,
/fixtures/echo, /fixtures/upload, /fixtures/graphql, /fixtures/ws, /fixtures/ready?after=N,
/fixtures/drop?after=N, /fixtures/big-id and /fixtures/duplicate-keys. A request to a
limited route past the budget is a 429 with Retry-After; POST /admin/ratelimit/reset refills
one key's bucket.
What each plan shows
rate-limit.yaml is the case vero exists for: four requests against a budget of three, the
fourth a 429, and three tasks spending one key's budget under one exclusive holds, so they never
overlap and the count holds at any --jobs. It resets the key in a lifecycle.reset step.
4 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (4 tasks, 12 requests, 0.0s)
orders.yaml logs in with an API key from a secretRef, creates an order, reads it back, and
deletes it in a finally task. The key is scrubbed from every output
(Secrets). The teardown shows in the summary:
5 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (5 tasks, 5 requests, 0.0s) teardown: 1 passed
wait-for-api.yaml polls /healthz with a waitFor step until it answers, then uses the API.
The attempt count is a line of its own (the waitFor step):
waitFor wait-for-api › step 1 passed after 1 attempts, waited 0.9ms
2 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (2 tasks, 2 requests, 0.0s)
graphql.yaml posts two queries to /fixtures/graphql, one reading the data and one expecting
an error by reading body.errors itself (the graphql step). ws.yaml opens
/fixtures/ws twice, once to echo a JSON and a text message and once to collect three pushed
messages (the ws step). Each counts its upgrade as one request:
2 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (2 tasks, 2 requests, 0.0s)
2 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (2 tasks, 2 requests, 0.1s)
exec.yaml runs examples/bin/migrate as an argv list, no shell, in its own process group,
against a SQLite URL (the exec step). It sends nothing:
1 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (1 tasks, 0 requests, 0.0s)
fixture.yaml migrates a catalog.db beside the plan with an exec step, seeds two countries
through a connection declared writes: true, and reads them back through a read-only one
(the fixture step). The write is labelled in the summary, and a second run
writes nothing because the statement says on conflict do nothing:
fixture seed-countries › step 1 passed through connection catalog-admin: wrote 2 row(s), committed
3 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (3 tasks, 0 requests, 0.1s)
fixture seed-countries › step 1 passed through connection catalog-admin: wrote 0 row(s), committed
3 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (3 tasks, 0 requests, 0.0s)
scripts.yaml runs a Node script and a Python script through the script step, each logging in and creating an order, and an http step checks the Node order through the result the script returned (Script steps). The runtimes found at load are printed first:
runtime node: /run/current-system/sw/bin/node v26.10.0
runtime python: /run/current-system/sw/bin/python3 Python 3.13.15
3 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (3 tasks, 1 requests, 0.4s)
The request count is 1 because the scripts' own requests are made by the scripts; vero counts only what it sent.
sqlite.yaml and postgres.yaml create an order over HTTP and check the stored row with a
read-only sql step (the sql step). Both passed here, SQLite against the fixture's
file and Postgres against the dedicated test database described below:
3 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (3 tasks, 2 requests, 0.0s)
grpc.yaml makes a unary call and reads a server stream, described by examples/grpc/orders.proto
(the grpc step). No shipped fixture serves gRPC, so this page did not run it; the
repository's test suite runs it against a server it starts in-process, and
the ws and grpc steps against real servers records runs against grpcbin.
To run it yourself, point VERO_GRPC_TARGET at a server that implements the two methods.
playwright.yaml drives Chromium through a Node script and checks the placed order over HTTP; its install steps are in the Playwright guide.
SQLite
Stop the plain fixture if running. In Terminal 1, from the checkout root:
bin/vero-testserver --sqlite examples/orders.db
In Terminal 2, run the sqlite.yaml row above. The server flag resolves examples/orders.db
from the shell's current working directory; the plan's dsn: orders.db resolves beside
examples/sqlite.yaml. Both point to the same file. The fixture creates the orders table and
writes rows; the plan reads the persisted order and checks its status, total, and tenant. No
external database server or SQLite CLI is required. Keep an existing database if it contains data
you need; use a dedicated test file for the fixture.
Postgres
Provide an existing dedicated test database, not a production schema. Provision it and its
two roles before running the example; this guide is not a database installer. The writer role
must connect, use and create in the chosen schema, create or use the six-column orders table
(id, sku, qty, status, total_cents, tenant_id), and insert and delete rows. The
fixture issues CREATE TABLE IF NOT EXISTS; an incompatible existing table is not replaced. Use
an empty test database or schema rather than overwriting an existing table. The reader role needs
CONNECT on the chosen database, USAGE on the schema, and SELECT on orders, including default
SELECT grants for new tables created by the writer. The reader should be read-only; vero also uses
read-only transactions, and vero plan warns when the reader's role could write.
deploy/local/postgres-init.yaml illustrates this writer/reader grant contract, but provisioning
the reader's chosen database and credentials remains your responsibility.
Replace host, database, user names and passwords in these illustrative templates. URI-encode reserved characters in credentials; configure TLS for your database rather than assuming an insecure connection string. In Terminal 1, with the plain fixture stopped:
export VERO_TEST_PG_FIXTURE_DSN='postgres://writer:encoded-password@db.example.test:5432/vero_test?sslmode=require'
bin/vero-testserver --postgres "$VERO_TEST_PG_FIXTURE_DSN"
In Terminal 2:
export VERO_TEST_PG_DSN='postgres://reader:encoded-password@db.example.test:5432/vero_test?sslmode=require'
bin/vero run examples/postgres.yaml
The writer fixture creates the table and persists each order; the reader plan checks the row
through SQL. make infra-up and make pg-dsn are maintainer conveniences with cluster
assumptions, not required Postgres setup for vero. No Kubernetes cluster is needed on this path.
Paths and targets
Script entry files, exec workdirs, proto files and SQLite DSNs resolve relative to the plan
directory; CLI plan paths resolve from the shell's current directory. If copying an example, keep
its sibling scripts, protos or schema files with it. Most fixture examples hardcode
http://127.0.0.1:18080: to target another fixture address, edit env.baseUrl in a copy of the
plan. Only the Playwright plan consumes VERO_BASE_URL, and only the gRPC plan consumes
VERO_GRPC_TARGET. The rate-limit plan's reset, authentication, and orders endpoints are specific
to the sample API; adapt steps and assertions to your own service's contract, not just its host.
This page is docs/content/guides/running-examples.md in the repository.