Документация Миграция с WireMock

Миграция с WireMock на Mockarty

Если вы уже используете WireMock (@wiremock/wiremock, standalone JAR
или Docker-образ), ваши заглушки продолжат работать без изменений после
переезда на Mockarty. Ниже — пошаговый путь миграции.

Что меняется

  1. URL сервера — направьте существующих клиентов WireMock (admin API,
    /__admin/*) на хост Mockarty вместо хоста WireMock. Тестируемая
    система обращается к заглушкам по базовому адресу пространства имён,
    http://<host>:5770/stubs/<namespace> — укажите его там, где был базовый
    адрес WireMock. Журнал запросов показывает пути относительно этого
    адреса, как WireMock, поэтому verify(getRequestedFor(urlEqualTo("/orders")))
    продолжает работать.
  2. Аутентификация — добавьте API-токен Mockarty (заголовок
    X-API-Key или стандартный механизм SDK), чтобы запросы доходили до
    admin-сервера.

Что остаётся прежним

  • Файлы JSON заглушек — JSON-схема WireMock 3.x принимается как есть.
  • Admin REST endpoint’ы /__admin/* — Mockarty выставляет их на тех же
    путях.
  • Семантика матчинга — поддержаны все официально документированные
    matcher’ы.

Drop-in замена в docker (--wiremock-only)

Запустите mockarty с флагом --wiremock-only — контейнер ведёт себя
как stock WireMock: /__admin/* работает без auth, заглушки отвечают в
корне адреса контейнера, а совместимость с WireMock включается на старте
для пространства имён по умолчанию (sandbox), чтобы testcontainers-клиент
получил рабочий mock-server с первого запроса.

docker run --rm -p 8080:8080 mockarty/mockarty-cli:latest --wiremock-only

В Java-тесте через testcontainers:

GenericContainer<?> mockarty = new GenericContainer<>("mockarty/mockarty-cli:latest")
    .withCommand("--wiremock-only")
    .withExposedPorts(8080)
    .waitingFor(Wait.forHttp("/__admin/health"));
mockarty.start();
String adminUrl = "http://" + mockarty.getHost() + ":" + mockarty.getMappedPort(8080) + "/__admin";

Что меняет --wiremock-only:

  • /__admin/* больше не требует Mockarty auth header (у настоящего
    WireMock тоже нет auth).
  • В пространстве имён по умолчанию (sandbox) автоматически
    активируется wiremock-compat, так что не нужно делать PATCH после
    каждого рестарта.
  • Заглушки отвечают в корне (http://<host>:8080/orders) — там же, где
    тестируемая система обращалась бы к контейнеру WireMock. /api/* и
    /__admin/* сохраняют своё назначение.
  • Все остальные admin-фичи (UI, TCM, security-сканер, A2A) продолжают
    работать — флаг дополнительный, не выключающий. Другие
    миграционные сценарии остаются доступными.

Флаг рассчитан на эфемерные CI-контейнеры / testcontainers-тесты. Для
долгоживущих multi-tenant Mockarty-инсталляций используйте
per-namespace переключатель ниже.

Включение совместимости с WireMock для конкретного namespace

Фича выключена по умолчанию и включается per-namespace, чтобы
выкатывать миграцию по командам/пайплайнам по очереди.

Включается через API для конкретного namespace:

curl -X PATCH "$MOCKARTY/api/v1/namespaces/$NS/settings/wiremock-compat" \
  -H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"wiremock_compat_enabled": true}'

GET по тому же пути читает текущее состояние. Чтобы выключить, отправьте
{"wiremock_compat_enabled": false}.

Совместимость с WireMock входит в модуль моков — это те же самые заглушки,
только через клиент WireMock. Если переключатель включён, а модуля моков в
лицензии нет, /__admin/* отвечает
402 {"error":"WireMock compatibility is not included in your license", "feature":"mock"} — одного переключателя недостаточно.

Когда выполнены оба условия, для этого namespace становятся доступными
следующие endpoint’ы:

Метод Путь Описание
GET /__admin/health Health check
GET /__admin/version Version-хендшейк
POST /__admin/reset Сброс всего: стабы, журнал и состояния сценариев
POST /__admin/settings Принимается и отвечает 200; глобальных настроек стабов у Mockarty нет, поэтому в ответе перечислены проигнорированные ключи
GET / POST / DELETE /__admin/mappings Список / создание / удаление всех стабов (DELETE удаляет и persistent)
GET / PUT / DELETE /__admin/mappings/{id} Чтение / обновление / удаление стаба
POST /__admin/mappings/import Массовый импорт (политика дублей OVERWRITE / IGNORE)
POST /__admin/mappings/reset Удалить все WireMock-стабы (persistent стабы выживают)
POST /__admin/mappings/save No-op (Mockarty persistent by default)
GET / DELETE /__admin/mappings/unmatched Стабы, которые ни разу не сматчились / удаление именно их
POST /__admin/mappings/find-by-metadata Фильтр стабов по metadata
POST /__admin/mappings/remove-by-metadata Массовое удаление по metadata
GET / DELETE /__admin/requests Журнал запросов
POST /__admin/requests/reset Очистка журнала
POST /__admin/requests/find Фильтр записей журнала: method, все четыре формы url*, матчеры headers и bodyPatterns
POST /__admin/requests/count Подсчёт записей по тем же фильтрам (за этим стоит verify(n, …))
POST /__admin/requests/remove Удаление записей по тем же фильтрам
GET / DELETE /__admin/requests/{id} Чтение / удаление одной записи журнала
GET /__admin/requests/unmatched Список незаматченных запросов
GET /__admin/requests/unmatched/near-misses Для каждого незаматченного запроса — ближайшие стабы
POST /__admin/near-misses/request Ближайшие стабы к описанному запросу
POST /__admin/near-misses/request-pattern Ближайшие к шаблону запросы из журнала
POST /__admin/requests/remove-by-metadata Удалить из журнала запросы, обслуженные стабами с этими metadata
GET /__admin/scenarios Список сценариев и текущих состояний
POST /__admin/scenarios/reset Вернуть все сценарии в Started
PUT /__admin/scenarios/{name}/state Принудительно задать состояние
POST /__admin/recordings/start Старт записи через журнал
POST /__admin/recordings/stop Стоп и преобразование записей в стабы
GET /__admin/recordings/status NeverStarted / Recording / Stopped
POST /__admin/recordings/snapshot Снимок журнала без остановки записи
ANY /__admin/recordings/proxy/* Переслать запрос на цель записи и записать обмен
GET /__admin/files Список body files
GET / PUT / DELETE /__admin/files/{name} CRUD body files

Поддерживаемые matcher’ы

Поддержаны все документированные matcher’ы WireMock:

Matcher Примечания
equalTo Регистро-зависимое; с caseInsensitive: true — регистро-независимое
equalToIgnoreCase Нативное регистро-независимое равенство
contains / doesNotContain Подстрока
matches / doesNotMatch Регулярные выражения (Java-style)
equalToJson Структурное равенство JSON; поддержаны модификаторы ignoreArrayOrder и ignoreExtraElements
matchesJsonPath JSONPath-выражение должно дать непустой результат
equalToXml Равенство дерева элементов XML (пробелы между элементами, комментарии и порядок атрибутов не важны); параметры XMLUnit — ниже
matchesXPath XPath-subset: пути, фильтры атрибутов, индексы, предикаты text()
binaryEqualTo Побайтовое равенство против base64-ожидаемого значения
before / after / equalToDateTime Ожидаемая дата — RFC 3339, "now" или "now +3 days"; фактическое значение читается как RFC 3339 / ISO 8601 или в actualFormat (шаблон Java, например dd/MM/yyyy, либо epoch / unix). Поддержаны truncateExpected / truncateActual, expectedOffset / expectedOffsetUnit (от секунд до лет) и applyTruncationLast
hasExactly Multi-value поле строго совпадает с перечисленными предикатами
includes Multi-value поле включает (как минимум) каждый из перечисленных предикатов
absent: true Поле должно отсутствовать
matchesJsonSchema Тело должно проходить валидацию по встроенной JSON Schema (по умолчанию draft 2020-12, draft-07 — через schemaVersion). Тело, которое не является JSON, просто не совпадает; несобирающаяся схема не совпадает ни с чем и сообщает об ошибке в описании
and / or / not Логические комбинаторы — все матчеры выше вкладываются в них, включая matchesJsonSchema

URL-матчинг поддерживает все пять форм WireMock: url, urlPath,
urlPattern, urlPathPattern и urlPathTemplate (плейсхолдеры RFC 6570
{var} становятся именованными path-параметрами). url — это весь URL: если
в нём есть query (/orders?status=open), запрос должен нести ровно такой
query — другое значение, лишний параметр или его отсутствие не совпадут.

Кроме URL и матчеров из таблицы, в request pattern поддержаны method,
headers, queryParameters, cookies, bodyPatterns,
basicAuthCredentials (переводится в точную проверку заголовка
Authorization: Basic …), formParameters (матчинг по телу
application/x-www-form-urlencoded), multipartPatterns (каждая часть
тела multipart/form-data проверяется по name, headers и
bodyPatterns, matchingType — ANY или ALL), pathParameters
(переменные urlPathTemplate, каждая — своим матчером) и clientIp
(адрес вызывающего так, как его видит Mockarty; за прокси —
пересланный адрес клиента).

Расстояние near-miss — от 0 (сматчился бы) до 1 (ничего общего):
считаются метод, URL (с двойным весом и тем ближе, чем больше совпало
сегментов пути) и каждое условие стаба по заголовкам, query-параметрам и
телу. Возвращается до трёх ближайших стабов, ближайший первым.

Модификаторы матчеров. Применяются все модификаторы WireMock:
caseInsensitive; параметры дат выше; matchingType (ANY / ALL) у
multipart-паттернов; schemaVersion у matchesJsonSchema (V202012 — по
умолчанию — и V201909 проверяются как draft 2020-12, V7 / V6 / V4 — как
draft-07; $schema внутри схемы главнее); xPathNamespaces у
matchesXPath; объектная форма matchesJsonPath / matchesXPath
({"expression": "$.status", "equalTo": "open"}) — выбирает значение и
проверяет его вложенным матчером; и параметры XMLUnit у equalToXml:

  • enablePlaceholders — ожидаемое значение ${xmlunit.ignore} принимает
    что угодно, ${xmlunit.isNumber} — любое число, ${xmlunit.isDateTime}
    — любую дату, ${xmlunit.matchesRegex(ORD-\d+)} — регулярное выражение;
    placeholderOpeningDelimiterRegex / placeholderClosingDelimiterRegex
    меняют разделители ${ };
  • ignoreOrderOfSameNode — соседние элементы могут идти в любом порядке;
  • exemptedComparisons — сравнения XMLUnit, которые не выполняются,
    например ["TEXT_VALUE", "SCHEMA_LOCATION"];
  • namespaceAwareness: NONE — пространства имён не сравниваются.

Префиксы пространств имён в equalToXml не важны никогда — важно только
пространство имён, которое они обозначают.

Translator возвращает массив gaps при импорте. Каждый неподдержанный
или нераспознанный ключ выше попадает туда, поэтому читайте его: каждая
запись означает, что импортированный стаб менее строгий, чем исходный.

Админ-эндпоинты, которых у нас нет

POST /__admin/shutdown (жизненный цикл инстанса принадлежит владельцу
развёртывания). GET /__admin/requests отдаёт
канонический конверт serve-event (request{url, absoluteUrl, method, …},
response{status}, wasMatched), плоские поля сохранены рядом; неопознанный
ключ матчера в фильтре журнала — ошибка 400, а не тихое игнорирование.

postServeActions с действием webhook выполняются после ответа стаба
(расширения, отличные от webhook, репортируются на импорте как gap).
Прочие кастомные post-serve расширения сохраняются только для round-trip.

Определение ответа

  • status, body, jsonBody, base64Body, headers — все поддержаны.

  • bodyFileName — файл, загруженный через PUT /__admin/files/<имя>.

  • fixedDelayMilliseconds — фиксированная задержка перед ответом.

  • delayDistribution — случайная задержка, которая выбирается для каждого
    запроса и прибавляется к фиксированной: {"type": "lognormal", "median": 90, "sigma": 0.4} (можно ограничить сверху maxValue),
    {"type": "uniform", "lower": 300, "upper": 500} или
    {"type": "fixed", "milliseconds": 200}.

  • chunkedDribbleDelay — {"numberOfChunks": 5, "totalDuration": 1000}
    отдаёт тело пятью частями в течение секунды, как медленная сеть. Первая
    часть приходит сразу.

  • proxyBaseUrl — запрос пересылается на базовый URL вместе с путём и
    query: при proxyBaseUrl: "https://api.example.com/v2" запрос
    /users/7?x=1 уходит на https://api.example.com/v2/users/7?x=1.
    proxyUrlPrefixToRemove сначала отрезает начало пути,
    additionalProxyRequestHeaders добавляет заголовки (например,
    API-ключ), removeProxyRequestHeaders убирает заголовки клиента. Цель
    должна быть доступна с сервера Mockarty; частные и loopback-адреса
    отклоняются, если оператор их не разрешил.

  • transformers: ["response-template"] — тело (в том числе файл из
    bodyFileName) и заголовки ответа — шаблоны Handlebars, которые
    рендерятся на каждый запрос:

    • данные запроса — request.method, request.url, request.path,
      request.pathSegments.[N], request.path.<имя> (переменная
      urlPathTemplate), request.host, request.port, request.scheme,
      request.baseUrl, request.clientIp, request.body,
      request.bodyAsBase64, request.headers.X (регистр не важен),
      request.query.X, request.cookies.X; .first и .[N] выбирают одно
      значение повторённого заголовка или параметра;
    • блоки — {{#each}} (с as |item index|, @index, @first,
      @last), {{#if}} / {{else}}, {{#unless}}, {{#with}};
    • хелперы — jsonPath, regexExtract (третий аргумент сохраняет группы
      в переменную), randomValue length=N type='…' (NUMERIC, ALPHABETIC,
      HEXADECIMAL, UUID), randomInt lower= upper=,
      now offset='3 days' format='yyyy-MM-dd' timezone='…', parseDate,
      date, size, base64, а также хелперы сравнения и строк (eq,
      and, or, not, upper, lower, split, join). Форматы дат —
      шаблоны Java (dd/MM/yyyy HH:mm) или epoch / unix.
    {"request": {"method": "POST", "urlPathTemplate": "/orders/{id}"},
     "response": {"status": 200, "transformers": ["response-template"],
       "jsonBody": {"id": "{{request.path.id}}",
                    "items": "{{#each (jsonPath request.body '$.items') as |i|}}{{i.sku}} {{/each}}"}}}
    
  • fault — поддержаны все четыре типа: EMPTY_RESPONSE,
    MALFORMED_RESPONSE_CHUNK, RANDOM_DATA_THEN_CLOSE,
    CONNECTION_RESET_BY_PEER.

Запись

Запустите запись с реальным сервисом в качестве цели, пропустите трафик
через прокси записи и остановите запись — каждый записанный обмен станет
стабом, который отвечает то же, что ответил реальный сервис (статус,
заголовки и тело):

curl -X POST "$MOCKARTY/__admin/recordings/start" \
  -H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"targetBaseUrl": "https://api.example.com"}'
curl "$MOCKARTY/__admin/recordings/proxy/orders" \
  -H "Authorization: Bearer $MOCKARTY_API_TOKEN"
curl -X POST "$MOCKARTY/__admin/recordings/stop" \
  -H "Authorization: Bearer $MOCKARTY_API_TOKEN"
  • Трафик записывается через /__admin/recordings/proxy/<путь>, который
    пересылает запрос на <targetBaseUrl>/<путь>.
  • repeatsAsScenarios (включено по умолчанию): запрос, записанный
    несколько раз с разными ответами, превращается в сценарий, который
    отдаёт ответы по порядку — третий вызов получает третий ответ, дальше
    остаётся последний. false оставляет только первый ответ.
  • allowNonProxied: true записывает и запросы, на которые ответили ваши
    существующие стабы (с ответом этого стаба). По умолчанию выключено.
  • captureHeaders превращает перечисленные заголовки запроса в
    матчеры; persist: true помечает записанные стабы как persistent.

Сценарии (stateful behaviour)

Сценарии WireMock полностью round-trip’ятся:

  • scenarioName, requiredScenarioState, newScenarioState на стабе
    учитываются в момент запроса.
  • GET /__admin/scenarios возвращает сценарии, обнаруженные в
    импортированных стабах.
  • POST /__admin/scenarios/reset возвращает все сценарии в Started.
  • PUT /__admin/scenarios/{name}/state принудительно задаёт состояние.

Persistent stubs

Стабы, импортированные с persistent: true, переживают
POST /__admin/mappings/reset — соответствует семантике WireMock 3.x.

Вне scope

  • transformers: ["<custom Java class>"] — только response-template.
  • POST /__admin/shutdown — жизненный цикл инстанса управляется
    оператором, а не клиентами.

Пошаговая миграция

  1. Поднимите Mockarty и создайте/выберите namespace для миграции
    стабов.

  2. Сгенерируйте API-токен в Mockarty (Settings → API Tokens) с
    привязкой к нужному namespace.

  3. Включите совместимость с WireMock для namespace.

  4. Перенаправьте клиентов: смените URL WireMock на admin-URL
    Mockarty и добавьте заголовок API-токена.

  5. Массовый импорт существующих стабов:

    curl -X POST "$MOCKARTY_URL/__admin/mappings/import" \
      -H "X-API-Key: $TOKEN" \
      -H "Content-Type: application/json" \
      --data-binary "@wiremock-stubs.json"
    
  6. Проверьте ответ на наличие записей gaps — это единственные
    места, которые могут потребовать корректировки исходного JSON.

  7. Просмотрите импортированные стабы в UI Mockarty — они появляются
    в списке моков namespace с префиксом ID wm_ и редактируются так же,
    как нативные моки.

Проверка миграции

Запустите те же тесты, основанные на WireMock, против URL Mockarty. Они
должны проходить без дополнительных изменений — каждый документированный
matcher, сценарии, фолты, body files и шаблонизация ответа учитываются.
Полученные при импорте gaps — это и есть контракт того, что может
вести себя иначе.