Миграция с WireMock на Mockarty
Если вы уже используете WireMock (@wiremock/wiremock, standalone JAR
или Docker-образ), ваши заглушки продолжат работать без изменений после
переезда на Mockarty. Ниже — пошаговый путь миграции.
Что меняется
- URL сервера — направьте существующих клиентов WireMock (admin API,
/__admin/*) на хост Mockarty вместо хоста WireMock. Тестируемая
система обращается к заглушкам по базовому адресу пространства имён,
http://<host>:5770/stubs/<namespace>— укажите его там, где был базовый
адрес WireMock. Журнал запросов показывает пути относительно этого
адреса, как WireMock, поэтомуverify(getRequestedFor(urlEqualTo("/orders")))
продолжает работать. - Аутентификация — добавьте 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— жизненный цикл инстанса управляется
оператором, а не клиентами.
Пошаговая миграция
-
Поднимите Mockarty и создайте/выберите namespace для миграции
стабов. -
Сгенерируйте API-токен в Mockarty (Settings → API Tokens) с
привязкой к нужному namespace. -
Включите совместимость с WireMock для namespace.
-
Перенаправьте клиентов: смените URL WireMock на admin-URL
Mockarty и добавьте заголовок API-токена. -
Массовый импорт существующих стабов:
curl -X POST "$MOCKARTY_URL/__admin/mappings/import" \ -H "X-API-Key: $TOKEN" \ -H "Content-Type: application/json" \ --data-binary "@wiremock-stubs.json" -
Проверьте ответ на наличие записей
gaps— это единственные
места, которые могут потребовать корректировки исходного JSON. -
Просмотрите импортированные стабы в UI Mockarty — они появляются
в списке моков namespace с префиксом IDwm_и редактируются так же,
как нативные моки.
Проверка миграции
Запустите те же тесты, основанные на WireMock, против URL Mockarty. Они
должны проходить без дополнительных изменений — каждый документированный
matcher, сценарии, фолты, body files и шаблонизация ответа учитываются.
Полученные при импорте gaps — это и есть контракт того, что может
вести себя иначе.