Перейти к содержимому

Вызовы запросов и проверки

api/users/create.outry
CreateUser: POST /users {
body { name: "Viktor" }
expect {
status == 201
body.name == "Viktor"
body.id matches /^u_\d+$/
headers.content-type.contains("json")
duration < 500
body matches { id: string, name: string, tags?: [string] }
}
}

По проверке на строку; каждая должна быть истинной. Выполняются все проверки, даже после упавшей, и у упавшей видно, почему:

✗ api/users/create.outry CreateUser POST http://localhost:8080/users 201 Created 9ms 211B
✓ status == 201
✗ body.name == "Viktor" — body.name is "viktor"
✗ body matches { id: string, name: string, tags?: [string] } — body.id: expected string, got 42

Сравнение не приводит типы ("1" != 1), отсутствующее поле — это null, а не ошибка, а matches принимает регулярное выражение, форму прямо в файле, именованную форму или файл JSON Schema. Все операторы и методы — в разделе Выражения.

Запрос — это функция: вызовите его по имени, результат — его ответ.

api/auth/login.outry
Login: POST /auth/login {
params {
email: "dev@example.com"
password
}
body { email, password }
expect { status == 200 }
}
api/users/me.outry
Me: GET /me {
headers { Authorization: "Bearer ${Login().body.token}" }
expect { status == 200 }
}
Окно терминала
outry run api/users/me.outry # сам сначала залогинится
✓ api/users/me.outry Me GET http://localhost:8080/me 200 OK 8ms 96B
↳ Login 200 31ms
✓ status == 200
  • Не нужно следить за порядком. Me работает сам по себе — он вызывает Login, когда нужен токен.

  • Один раз за прогон. Десять запросов с Login() логинятся один раз, остальные берут тот же ответ (↳ Login cached). fresh Login() всегда отправляет заново. cache: 30m у Login хранит его ответ и между запусками.

  • Аргументы заполняют params и параметры пути {…} вызываемого: GetUser(id: 7), Login(email: "admin@example.com").

  • Ошибки поднимаются наверх. Если у Login упал expect, падает и Me — с цепочкой:

    ✗ api/users/me.outry Me
    Me → Login: status == 200 — status is 401
  • Cookies из Set-Cookie хранятся весь прогон, как в браузере.

Имена запросов уникальны в проекте; если Create есть в двух папках, вызывайте users.Create(). outry check сообщает о неизвестных именах, неверных аргументах и циклах вызовов.

api/flows/checkout.outry
// Checkout
flow Checkout {
user = CreateUser(name: "Bob")
order = CreateOrder(customer: user.body.id)
Pay(order: order.body.id)
expect { GetOrder(id: order.body.id).body.status == "paid" }
}

Шаги выполняются сверху вниз, сценарий останавливается на первом упавшем. В приложении сценарий показывает trace всех вызванных запросов. Запуск по имени: outry run Checkout.

save order_id = body.id

Сохранённое значение — переменная для следующих запросов прогона и запоминается между запусками, для каждого проекта и окружения: после одного outry run CreateOrder запрос GetOrder и сам по себе знает order_id. Значения хранятся вне репозитория, см. Сохранённые значения. Для токенов и подобного лучше вызовы: они не зависят от того, что запускалось раньше.

outry run завершается с кодом:

  • 0 — все запросы отправлены, все проверки и сохранения прошли;
  • 1 — хотя бы один запрос упал (проверка, сохранение, сетевая ошибка, нет переменной);
  • 2 — Outry не смог запуститься: неверные аргументы, неизвестное окружение, сломанный env.toml.

С --fail-fast прогон останавливается на первом упавшем запросе.