Документация Скриптовые ответы

Скриптовые ответы

Большинство моков отдают статическое тело или шаблон с подстановками Faker/JsonPath. Когда
ответу нужна настоящая логика — вычисление, ветвление по запросу, состояние между вызовами
или намеренная нестабильность для проверки отказоустойчивости — используйте скриптовый
ответ
.

Скриптовый ответ — это JavaScript, который выполняется при каждом срабатывании мока. Он
получает входящий request и заполняет исходящий response. Это ещё один вариант ответа —
в одном ряду с «Тело», «Пусто» и «Файл-шаблон».

Скриптовые ответы доступны для моков HTTP, gRPC, GraphQL, SOAP, MCP, Kafka,
RabbitMQ, NATS и Socket. Для не-HTTP протоколов запрос приводится к тому же объекту request,
а сформированный response отображается обратно в ответ соответствующего
протокола (см. Другие протоколы ниже).

Создание скриптового ответа в интерфейсе

  1. Откройте Конструктор и задайте маршрут и метод как обычно.
  2. В разделе Тип тела ответа нажмите Скрипт.
  3. Напишите скрипт в редакторе. Автодополнение (Ctrl+Space или ввод request.) подсказывает
    всё, что доступно.
  4. Введите пример тела запроса в блоке Проверка и нажмите Запустить пример — увидите
    ответ, который формирует скрипт, и вывод console.log, не сохраняя мок.
  5. Сохраните мок.

Объект 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 });

Связанные разделы