Скриптовые ответы
Большинство моков отдают статическое тело или шаблон с подстановками Faker/JsonPath. Когда
ответу нужна настоящая логика — вычисление, ветвление по запросу, состояние между вызовами
или намеренная нестабильность для проверки отказоустойчивости — используйте скриптовый
ответ.
Скриптовый ответ — это JavaScript, который выполняется при каждом срабатывании мока. Он
получает входящий request и заполняет исходящий response. Это ещё один вариант ответа —
в одном ряду с «Тело», «Пусто» и «Файл-шаблон».
Скриптовые ответы доступны для моков HTTP, gRPC, GraphQL, SOAP, MCP, Kafka,
RabbitMQ, NATS и Socket. Для не-HTTP протоколов запрос приводится к тому же объектуrequest,
а сформированныйresponseотображается обратно в ответ соответствующего
протокола (см. Другие протоколы ниже).
Создание скриптового ответа в интерфейсе
- Откройте Конструктор и задайте маршрут и метод как обычно.
- В разделе Тип тела ответа нажмите Скрипт.
- Напишите скрипт в редакторе. Автодополнение (Ctrl+Space или ввод
request.) подсказывает
всё, что доступно. - Введите пример тела запроса в блоке Проверка и нажмите Запустить пример — увидите
ответ, который формирует скрипт, и выводconsole.log, не сохраняя мок. - Сохраните мок.
Объект request (вход)
Только для чтения. Входящий запрос, разложенный на составляющие:
| Поле | Описание |
|---|---|
request.method |
HTTP-метод, например "POST" |
request.path |
Путь запроса, на который сматчился мок, например "/api/orders/42" |
request.route |
Маршрут мока (шаблон), например "/api/orders/:id" |
request.url |
Полный URL |
request.host |
Хост запроса |
request.remoteAddr |
Адрес клиента |
request.query |
Query-параметры (первое значение), например request.query.page |
request.queryAll |
Query-параметры массивами (все значения), например request.queryAll.tag |
request.queryString |
Сырая query-строка, например "page=2&tag=a" |
request.params |
Path-параметры из шаблона маршрута, например request.params.id для /api/orders/:id |
request.headers |
Заголовки (первое значение, ключи в нижнем регистре), например request.headers["content-type"] |
request.headersAll |
Заголовки массивами (все значения), ключи в нижнем регистре |
request.header(name) |
Поиск заголовка без учёта регистра |
request.cookies |
Cookie по имени |
request.body |
Сырое тело запроса (строка) |
request.json() |
Разбирает тело как JSON и возвращает объект |
request.protocol |
Протокол, на который отвечает мок, напр. "http", "grpc", "kafka" |
request.attrs |
Протокол-специфичные поля для не-HTTP моков (напр. service/method gRPC, топик Kafka, имя MCP-тула). Общие поля выше всегда побеждают при совпадении ключа. |
Скрипту передаются все поля входящего запроса — ничего не теряется.
Объект response (выход)
Изменяемый. Его итоговое состояние становится ответом, который отдаёт Mockarty:
| Поле / метод | Описание |
|---|---|
response.status(201) |
Код состояния HTTP (по умолчанию 200). Также работает response.status = 201. |
response.json(obj) |
Тело в JSON и Content-Type: application/json |
response.text(s) |
Текстовое тело |
response.body = "..." |
Прямая установка тела |
response.setHeader(name, value) |
Добавить заголовок ответа (псевдоним: response.header(name, value)) |
response.delay(200) |
Задержать ответ на N миллисекунд. Также работает response.delay = 200. |
Методы-формы можно объединять в цепочку: response.status(201).setHeader("X-Run", "1").json({ ok: true }).
Можно также вернуть значение через return как сокращение: если response.body не задан,
возвращённый объект станет JSON-телом.
Значения формы засеивают ответ. Код статуса и заголовки ответа, заданные в форме мока,
предзагружаются как стартовый response скрипта — поэтому скрипт, не трогающий статус,
всё равно вернёт статус из формы, а заголовки формы уже на месте. Всё, что задаёт скрипт,
побеждает: response.status(503) переопределит статус формы 201. Это же используется в
предпросмотре Запустить пример, поэтому сухой прогон показывает ровно то, что вернул бы
живой мок.
Объект mock (метаданные)
Read-only информация о моке, который сейчас отвечает:
| Поле | Описание |
|---|---|
mock.id |
Идентификатор мока |
mock.namespace |
Рабочее пространство мока |
mock.route |
Маршрут мока, например /api/orders/:id |
mock.protocol |
http, grpc, graphql, soap, mcp, kafka, rabbitmq, nats или socket |
mock.chainId |
Цепочка, частью которой является мок (пусто, если нет) — используйте как ключ для store.chain, чтобы получить чистый скоуп на каждый поток |
// Пометить ответ источником.
response.setHeader("X-Mock-Id", mock.id);
response.json({ servedBy: mock.id, chain: mock.chainId });
Сторы и окружение
Скрипты читают и пишут те же Mock-, Chain- и Global-сторы, что и остальной Mockarty, поэтому
скрипт может хранить состояние между запросами:
// store.mock — буфер для одного этого запроса
// store.chain — общий для моков одной цепочки (поток заказа)
// store.global — общий для всего рабочего пространства
const visits = (store.global.get("visits") || 0) + 1;
store.global.set("visits", visits);
response.json({ visits: visits });
В каждой области есть get(key), set(key, value), has(key), delete(key) и keys() —
можно и читать, и писать. Записи Chain и Global сохраняются через общий кеш, поэтому значение,
записанное одним моком, видно следующему моку в цепочке (и на других нодах). Mock-область —
буфер текущего запроса.
env.get("KEY") читает значения окружения рабочего пространства.
Секреты доступны только для чтения — скрипт может прочитать секрет, чтобы использовать его
(например, подставить API-ключ в исходящий вызов), но не может записать:
const apiKey = store.secrets.get("vault", "api_key");
if (store.secrets.has("vault", "api_key")) { /* ... */ }
Секреты скоупятся рабочим пространством мока и маскируются в предпросмотре «прогнать
пример» (вы видите плейсхолдер, а не реальное значение). store.secrets обслуживается
основным узлом Mockarty; мок, выгруженный в отдельный сгенерированный сервер, работает без
хранилища секретов — читайте нужный секрет в заголовок или тело на основном узле, не
полагаясь на него ниже по потоку.
Помощники mk
| Помощник | Пример |
|---|---|
mk.faker.* |
Сфокусированный набор генераторов: name, firstName, lastName, email, username, phone, uuid, word, sentence, paragraph, url, ipv4, ipv6, date, timestamp, boolean, macAddress, currency, creditCard, domainName. (Более широкий каталог $.fake.* из справочника Faker — для шаблонных ответов, не для скриптов.) |
mk.uuid() |
Случайный UUID |
mk.randomInt(min, max) |
Случайное целое |
mk.crypto.* |
mk.crypto.sha256(s), mk.crypto.hmac("sha256", key, msg) |
mk.base64.* |
mk.base64.encode(s), mk.base64.decode(s) |
mk.jsonpath(obj, path) |
Извлечь значение по JsonPath |
mk.http.send(opts) |
Исходящий HTTP-запрос — по умолчанию выключен (см. ниже) |
Примеры
Вычисление по запросу:
const data = request.json();
response.json({
orderId: request.params.id,
total: data.amount * 1.1,
currency: "USD"
});
Ветвление по заголовку:
if (request.header("authorization")) {
response.json({ user: "alice@example.com" });
} else {
response.status = 401;
response.json({ error: "unauthorized" });
}
Счётчик с состоянием (chain-стор):
const n = (store.chain.get("hits") || 0) + 1;
store.chain.set("hits", n);
response.json({ count: n });
Токсичность: сбои и задержки
Скрипты — самый простой способ заставить мок вести себя как ненадёжный внешний сервис, что
полезно для проверки отказоустойчивости и хаос-тестирования:
// Падать на 10% запросов, остальным добавлять 200 мс задержки
if (Math.random() < 0.1) {
response.status = 503;
response.json({ error: "service unavailable" });
return;
}
response.delay = 200;
response.json({ ok: true });
Исходящие вызовы
По умолчанию скрипт не может обращаться в сеть — mk.http.send выключен. Включайте
Разрешить исходящие вызовы у скрипта (или scriptAllowNet в API) только когда ответу
действительно нужно сходить во внешнюю систему. Цели исходящих запросов проверяются, чтобы
не допустить обращений к приватным, loopback- и cloud-metadata-адресам.
// требует включённого «Разрешить исходящие вызовы»
const upstream = mk.http.send({ method: "GET", url: env.get("UPSTREAM_URL") });
response.status = upstream.status;
response.body = upstream.body;
Для более тяжёлых побочных эффектов (публикация в очередь, уведомление другого сервиса после
ответа) используйте Вебхуки и обратные вызовы — они выполняются
асинхронно после отправки ответа.
Ограничения
Скрипты выполняются с жёстким бюджетом времени (по умолчанию 50 мс; настраивается per-mock до
1 секунды). Скрипт, превысивший бюджет, останавливается, и мок возвращает 500. Используйте
бюджет для быстрой логики; тяжёлую работу выносите в callbacks.
Размер исходника скрипта ограничен 256 КБ. Если скрипт установит HTTP-статус вне допустимого
диапазона 100–999, мок вернёт 200.
Валидация и ошибки
Редактор проверяет скрипт по мере набора: зелёный бейдж Корректно означает, что он
компилируется, а красный показывает первую синтаксическую ошибку с номером строки. Мок со
скриптом, который не компилируется, отклоняется при сохранении — с сообщением и указанием
строки, так что битый скрипт никогда не уедет молча.
Во время выполнения, если скрипт бросает исключение (или тело — невалидный JSON, прочитанный
через request.json()), мок отвечает понятной JSON-ошибкой вместо пустого 500:
{ "error": "mock script error", "detail": "TypeError: ... at line 3" }
request.json() возвращает null, когда у запроса нет тела, поэтому привычная защита
const body = request.json() || {} работает без особого случая.
Создание через API
Скриптовый ответ — это поле script у ответа:
curl -X POST "http://localhost:5770/api/v1/mocks?namespace=sandbox" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "calc",
"http": { "route": "/api/calc/:op", "httpMethod": "POST" },
"response": {
"script": {
"code": "const d=request.json(); response.json({op:request.params.op, out: request.params.op===\"double\"? d.v*2 : d.v/2});",
"allowNet": false
}
}
}'
Проверить скрипт на примере запроса без сохранения:
curl -X POST "http://localhost:5770/api/v1/mocks/script/preview?namespace=sandbox" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "script": "response.json({ hi: request.json().name });", "request": { "body": "{\"name\":\"world\"}" } }'
Другие протоколы
Скриптовые ответы работают и для моков gRPC, GraphQL, SOAP, MCP, Kafka, RabbitMQ,
NATS и Socket.
Объекты request и response адаптируются под протокол — поля протокола доступны
прямо на request (а полный набор — на request.attrs):
| Протокол | На request |
Формируем ответ |
|---|---|---|
| gRPC | request.service, request.method, request.message (и request.json()) |
response.json(obj) — сообщение-ответ; response.status = gRPC-код |
| GraphQL | request.operation, request.field, request.variables, request.queryText |
response.json({ data: {...} }) |
| SOAP | request.action, request.method, request.service, request.bodyData |
response.text("<...>") — XML-тело |
| MCP | request.tool, request.arguments (и request.json()) |
response.json({ content: [{ type: "text", text: "..." }] }) |
| Kafka | request.topic, request.value (и request.json()) |
response.json(obj) — payload ответа |
| RabbitMQ | request.queue, request.value (и request.json()) |
response.json(obj) |
| NATS | request.subject, request.reply, request.queueGroup, request.message (и request.json()) |
response.json(obj) — payload ответа |
| Socket | request.serverName, request.event, request.message (и request.json()) |
response.json(obj) |
Заголовки доступны во всех протоколах. request.headers и request.header(name)
работают так же, как для HTTP:
- gRPC — метаданные вызова доступны как заголовки, поэтому
request.header("authorization")
читает запись метаданных (сырая карта также вrequest.metadata).request.route—
каноническийservice/method. - GraphQL, SOAP, MCP — приходят по HTTP, поэтому заголовки клиента доступны через
request.header(name)/request.headers, аrequest.path/request.routeсодержат
сматченный путь эндпоинта. - Kafka, RabbitMQ, NATS — заголовки сообщения доступны через
request.headers.
Сторы, env, секреты, mk.* и бюджет работают так же, как для HTTP.
// Скрипт gRPC-мока
const u = request.message; // входящее сообщение
response.json({ id: u.id, name: "User " + u.id });