Заглушки внешних партнёров на тестовом стенде через WireMock или WireMock Studio: эндпоинты из кода сервиса, стабы по контракту, негативы, retry-сценарии, вебхуки, кириллица без порчи, переключение сервиса на мок с доказательством журналом и трейсом, откат. Используй всегда, когда просят «сделай мок партнёра», «создай стаб», «замокай сервис», «эмулируй недоступность партнёра», и при ошибке Request was not matched. English: WireMock, mock API, stub, service virtualization.
Installs into .claude/skills of the current project.
Are you the author of Wiremock Stubs?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/konstantinvch-wiremock-stubs)
---
name: wiremock-stubs
description: "Заглушки внешних партнёров на тестовом стенде через WireMock или WireMock Studio: эндпоинты из кода сервиса, стабы по контракту, негативы, retry-сценарии, вебхуки, кириллица без порчи, переключение сервиса на мок с доказательством журналом и трейсом, откат. Используй всегда, когда просят «сделай мок партнёра», «создай стаб», «замокай сервис», «эмулируй недоступность партнёра», и при ошибке Request was not matched. English: WireMock, mock API, stub, service virtualization."
license: MIT
compatibility: "Python 3 (скрипты только на stdlib); доступ к admin-API WireMock или WireMock Studio. Опционально: Jaeger Query API, Bitbucket Server (архив репозитория), Confluence или другая документация интеграций."
metadata:
author: KonstantinVCH
version: "1.0.0"
language: ru
---
# Mock-окружение внешнего партнёра на тестовом стенде
## Настройка под проект
Адреса, режим сервера, шаблон имён сервисов в Jaeger, соглашения команды и механика переключения
стенда собраны в [references/project-config.md](references/project-config.md). Заполни его перед
первой работой. Если у проекта есть база знаний, сначала прочитай в ней страницы о моках и о
нужной интеграции.
- **Python 3** — все четыре скрипта на stdlib, зависимостей нет.
- **WireMock** — `WIREMOCK_URL`. Поддерживаются два режима (`WIREMOCK_MODE`, по умолчанию
определяется сам):
- `studio` — WireMock Studio с реестром `GET /api/v1/mock-apis`: много mock API на одном хосте,
у каждого свой порт; мок называется именем, id или портом;
- `plain` — обычный WireMock: admin-API на `<хост>:<порт>/__admin`; мок называется портом на
хосте `WIREMOCK_URL` или `-` (сам `WIREMOCK_URL`). Команд `apis`, `create`, `delete-api` нет.
- Для шага «переключить сервис» — доступ к репозиторию конфигов деплоя и системе деплоя (для
Bamboo — скилл `bamboo-deploy`).
- **Документация интеграций** — желательна (MCP Atlassian, если подключён, иначе REST Confluence):
там лежит контракт метода партнёра (примеры тел, коды, синхронный или асинхронный обмен). Без
неё тела ответов придётся угадывать.
## Инструменты скилла
```bash
SK=<путь к каталогу этого скилла>
python "$SK/scripts/wm.py" --help # мок и стабы
python "$SK/scripts/adapter_paths.py" --help # что надо застабить
python "$SK/scripts/jaeger_urls.py" --help # куда сервис реально ходит
python "$SK/scripts/post_json.py" --help # послать запрос за внешнюю систему (входящие)
```
Детали по шагам вынесены отдельно, чтобы не занимать контекст без нужды:
| Файл | Когда читать |
|---|---|
| [references/stubs.md](references/stubs.md) | шаг 5: перечень эндпоинтов, спецификация, имена, матчинг, шаблоны, сценарии, вебхуки |
| [references/inbound.md](references/inbound.md) | шаг 1 показал входящую интеграцию: как сыграть внешнюю систему самому (колбэки, вебхуки, постбэки) |
| [references/switch.md](references/switch.md) | шаг 6: два маршрута переключения, OAuth-провайдер, возврат на реальный адрес |
| [references/traps.md](references/traps.md) | при странном поведении и перед первым самостоятельным прогоном; там же статус проверок |
| [references/project-config.md](references/project-config.md) | настройка под проект |
## Правила, которые дороже всего стоят при нарушении
- **Мок часто общий на все тестовые стенды.** Правка одного стаба меняет поведение у всех, кто
тестирует эту интеграцию прямо сейчас. Поэтому перед правкой существующего — снимок и
предупреждение команде.
- **Чужой стаб не чинить.** Разбирая мок, легко найти дефект в стабе, который делал не ты:
опечатку в вебхуке, слишком широкий матчер, устаревший ответ. Это находка, а не задача.
Правильно — доложить владельцу или в задачу; неправильно — «заодно поправить». Стаб может быть
сломан намеренно под чей-то текущий прогон, а «исправление» уронит его молча и в другом месте.
Трогай своими руками только то, что относится к твоей задаче.
- **Снимок делай до правки, а не после.** `wm.py backup`; накатить его обратно можно только через
`wm.py restore` — `add` создаст дубли с теми же uuid.
- **Переименование или отключение стаба — фикстура.** Возвращай в том же сеансе: чужой прогон не
должен упасть из-за твоей проверки.
- **Верь только факту.** Журнал WireMock (`wasMatched`) и `http.url` client-спана в Jaeger.
«Деплой SUCCESS» и «файл с мок-адресом существует» не доказывают ничего.
- **Кириллицу в JSON шли скриптами скилла** (`wm.py` для стабов, `post_json.py` для запросов за
партнёра). Прямой `curl -d` из Git Bash или консоли Windows пишет `U+FFFD` необратимо: стаб
приходится пересоздавать, а запись сохраняется с битыми ФИО.
- **Создание и удаление mock API — с согласия пользователя.** Порты общие, у мока может быть
чужой потребитель, а удаление необратимо.
- Только тестовые стенды и предпрод: на prod моки невозможны и недопустимы.
---
# 0. Preflight
```bash
python "$SK/scripts/wm.py" apis | tail -3 # studio
python "$SK/scripts/wm.py" stubs - | head -3 # plain
```
Молчит — открой `{WIREMOCK_URL}/__admin/mappings` (или `/api/v1/mock-apis` для Studio) в браузере:
если UI живой, а API нет, остановись и сообщи.
Узнай версию сервера. Ловушки ниже проверены на **WireMock Studio, сервер 2.33.1**: фичи 3.x
(`subEvents`, лог вебхуков, новые матчеры) там недоступны, предлагать их не надо. На 3.x часть
ограничений снята — сверяйся со своей версией.
---
# 1. Какой мок вообще нужен
Мокать не то место — самая дорогая ошибка, и делается она до первой команды. Определи направление
вызова:
| Что нужно | Куда идти |
|---|---|
| Твой сервис → партнёр: отправка заказа, запрос каталога, колбэк партнёру | **этот скилл** |
| Партнёр → твой сервис: партнёр сам вызывает твой API и получает 404 | маршрутизация входящего трафика (ingress, шлюз), WireMock не участвует |
| Экран входа / OAuth2-провайдер | отдельный OAuth2-мок — [switch.md](references/switch.md#oauthoidc-провайдер) |
| Внешняя система зовёт твой сервис (вебхук, постбэк, колбэк результата) | мока быть не может — играй партнёра сам: [inbound.md](references/inbound.md) |
| «Партнёр» с состоянием и сложной логикой | отдельный сервис-эмулятор, если он есть в проекте, а не WireMock |
| WireMock внутри unit-теста сервиса | не этот скилл, это к разработчику |
**Не каждую интеграцию можно замокать — решает направление, а не «известность» партнёра.**
Провайдер, к которому сервис ходит наружу, мокается. Провайдер, который сам постит данные тебе,
мока не имеет и не может иметь: интеграция **входящая**. Пример: браузер уходит на страницу оплаты,
провайдер постит результат в `payment-service`, а в карточке метода так и написано «Вызывающий
сервис: внешняя система». Бэкенд к провайдеру за этим наружу не ходит вовсе — исходящих вызовов
самого сервиса в трейсах ноль. Подменять нечего: WireMock работает только с исходящими.
Отсюда рабочая проверка перед тем, как идти дальше. Три факта, каждый быстрый:
```bash
python "$SK/scripts/wm.py" apis --grep <партнёр> # есть ли мок вообще (studio)
python "$SK/scripts/jaeger_urls.py" <сервис> <стенд> # ходит ли сервис наружу
```
плюс карточка метода в спецификации: поле «Вызывающий сервис». Если там внешняя система —
направление входящее, и задача решается **без** мока: партнёра играешь сам, посылая тот запрос,
который прислал бы он. Это не тупик и обычно быстрее заглушки — рецепт, скрипт и пример с
негативным сценарием в [references/inbound.md](references/inbound.md).
Тот же вопрос уместен для любой интеграции: состояние иногда дешевле создать фикстурой
в БД или сообщением в очередь, чем стабом. Мок оправдан там, где сервис ходит **наружу**,
многократно, по нескольким путям, и нужны негативные ответы.
---
# 2. Найти существующий мок
В живом проекте моков обычно уже десятки — новый нужен редко. В Studio реестр отдаёт имя, id и
порт одним запросом, искать можно по любому из трёх:
```bash
python "$SK/scripts/wm.py" apis --grep payment # 18063 448ew RUNNING payment-gateway-adapter
python "$SK/scripts/wm.py" stubs 18063 # или stubs payment-gateway-adapter
```
`stubs` печатает `uuid · приоритет · метод · код · url · имя · флаги`, где флаги — `tmpl`
(включён `response-template`), `webhook`, `scenario=…`, `delay=…ms`.
Второй источник — эталон в git: мок-профиль сервиса в репозитории конфигов деплоя. Порт оттуда —
гипотеза: файл может расходиться с конфигом стенда, и именно так однажды возник дефект. Факт даёт
трейс.
---
# 3. Создать mock API, если нужного нет
🚦 Покажи пользователю, что создаётся и почему не подходит существующий, дождись «да».
```bash
python "$SK/scripts/wm.py" create "<имя-сервиса-потребителя>" # studio
```
Имя — как репозиторий сервиса-потребителя: тогда порт находится по имени сервиса без реестра. В
Studio порт назначается автоматически, шаблон `blank`, мок сразу `RUNNING` и слушает
`{WIREMOCK_URL}:<порт>` без деплоя. В обычном WireMock новый мок — отдельный процесс или контейнер,
поднимается средствами инфраструктуры.
Если в команде есть регламент — задача на добавление мока по шаблону из `project-config.md`.
---
# 4. Снимок ДО
```bash
python "$SK/scripts/wm.py" backup 18063 -o payment-18063-BEFORE.json
```
Файл в utf-8, положи к рабочим файлам задачи (и в базу знаний, если она есть). Откат потом:
```bash
python "$SK/scripts/wm.py" restore 18063 payment-18063-BEFORE.json # покажет, что снесёт
python "$SK/scripts/wm.py" restore 18063 payment-18063-BEFORE.json --yes
```
---
# 5. Собрать перечень эндпоинтов и написать стабы
**Стаб нельзя написать, не зная контракта.** Ни код, ни документация по отдельности его не дают —
нужны оба, и каждый отвечает на свои вопросы:
| Источник | Что даёт только он |
|---|---|
| **Код сервиса** (блок клиентов в `application.yml` или `.path("…")` в коде) | какие пути сервис реально зовёт, каким **методом**, с какими `timeouts` и `retry.max-attempts` |
| **Спецификация** (страница на метод) | **тела** запроса и ответа примерами, коды ответов, **синхронный или асинхронный** обмен, JSON или **XML**, аутентификация |
| **Действующий мок и его журнал** | что уже застаблено, что уже стреляло 404, принятый стиль имён |
Пропустишь код — не будешь знать, что стабить, и половина путей отдаст 404 посреди флоу.
Пропустишь спеку — стабы получатся с выдуманными телами: `{"status":"ok"}` вместо контракта, и
проверка выродится в «запрос дошёл». Асинхронный обмен без спеки вообще не увидишь: сделаешь стаб с
телом там, где партнёр отвечает колбэком.
Перечень путей — скриптом:
```bash
python "$SK/scripts/adapter_paths.py" <источник> -o paths.txt
python "$SK/scripts/wm.py" cover 18063 paths.txt # exit 2 = есть дырки
```
`<источник>` — каталог склонированного репо, `.zip` архива ветки (Bitbucket/GitHub → Download) или
slug репозитория с `--pat` (Bitbucket Server). Репо часто не склонирован, и это не повод
пропустить шаг.
`adapter_paths.py` достаёт пути **вместе с методами** (в конфиге методов нет, они в коде) и попутно
печатает `timeouts.response` и `retry.max-attempts` — оба нужны, чтобы придумать стаб с задержкой и
сценарий retry. Скрипт понимает Spring-конфиг вида `<префикс>.clients.<партнёр>.path` и литералы
`.path("/…")`; другая схема — перечень вручную в том же формате `МЕТОД /путь`.
Где искать контракт, что именно брать со страницы метода и почему пример важнее таблицы
параметров — [references/stubs.md](references/stubs.md#спецификация-метода).
Как писать сами стабы — [references/stubs.md](references/stubs.md): имена по соглашению команды
(например, `N. Бизнес-описание (вариант)`), матчинг через `urlPath` + `queryParameters`, разные
контексты шаблонов, обязательный негатив к каждому пути, сценарные стабы, вебхуки, опт-ин
заголовок вместо переименования.
Залить и проверить, что не побилось:
```bash
python "$SK/scripts/wm.py" add 18063 new-stubs.json # объект, массив или {"mappings":[…]}
python "$SK/scripts/wm.py" stubs 18063
```
Правка существующего — целиком, иначе `PUT` потеряет поля, которых нет в теле:
```bash
python "$SK/scripts/wm.py" get 18063 <uuid> > stub.json
python "$SK/scripts/wm.py" update 18063 <uuid> stub.json
```
---
# 6. Переключить сервис на мок
Файл с мок-адресом сам по себе ничего не включает: по умолчанию сервис едет на реальный dev-адрес
партнёра. Два маршрута, условия включения и случай OAuth-провайдера —
[references/switch.md](references/switch.md).
Коротко: маршрут **A** (по умолчанию) — точечно переопределить URL партнёра в конфиге стенда в
ветке задачи и выкатить обычным деплоем, браузер не нужен. Маршрут **B** — включить мок-профиль
сервиса флагом деплоя целиком; в разобранном примере для этого нужна была браузерная сессия
системы деплоя.
---
# 7. Верификация
Без неё работа не закончена: два независимых источника отвечают на два разных вопроса — «дошло ли
до мока» и «тот ли адрес применился».
```bash
python "$SK/scripts/wm.py" log 18063 -n 20 # дошло ли и в какой стаб попало
python "$SK/scripts/wm.py" log 18063 --unmatched # что не сматчилось
python "$SK/scripts/wm.py" log 18063 -n 1 --full # запись целиком: заголовки и тело ответа
python "$SK/scripts/jaeger_urls.py" <сервис> <стенд> # куда сервис реально ходит
```
| Признак в журнале | Значение |
|---|---|
| запись есть, `matched=True`, ожидаемое имя стаба | сервис дошёл до мока и попал куда надо |
| запись есть, `matched=False` | стаб не совпал: путь, метод, query или заголовок. WireMock отвечает `404 Request was not matched` с диаграммой сравнения — это не недоступность партнёра |
| записей нет вовсе | сервис **не переключён**, идёт на реальный адрес |
| `matched=True`, но в теле `?param=` вместо значения | сломанный шаблон, см. stubs.md |
`jaeger_urls.py` считает вердикт только по внешним вызовам: внутренние сервисы платформы под мок не
идут никогда и в знаменателе только мешают. Actuator у сервисов часто закрыт (`/actuator/**` → 404),
прочитать применённый конфиг через него нельзя.
Заголовки из журнала снимай целиком: фильтрация «интересных» однажды скрыла `X-Idempotency-Key`,
который отвечал на открытый вопрос партнёра про дедупликацию.
---
# 8. Откат фикстур и возврат стенда
Сразу после прогона, в том же сеансе — иначе фикстура достанется коллеге как «баг»:
```bash
python "$SK/scripts/wm.py" rename 18063 <uuid> "3. Подтверждение платежа"
python "$SK/scripts/wm.py" rm 18063 <uuid>
python "$SK/scripts/wm.py" reset-scenarios 18063
python "$SK/scripts/wm.py" stubs 18063 > AFTER.txt # сверить с BEFORE
```
Состав не сходится — `restore <снимок> --yes`. Сервис вернуть: маршрут A — передеплой с основной
ветки конфигов, маршрут B — деплой со значением мок-флага по умолчанию.
Оставить стоит стабы, описанные в задаче и полезные команде; убрать — временные переименования,
задержки и отладочные вебхуки.
---
# 9. Версионировать
- **Эталон стабов** → репозиторий, указанный в `project-config.md`, выгрузка
`wm.py get <мок> > имя.json`. В эталон пиши корректные значения, а не слепок стенда с чужими
временными отключениями.
- **Конфиг переключения** → PR в репозиторий конфигов деплоя.
- **Задача** → мок, порт, перечень стабов, какой сервис переключён и как вернуть.
---
# 10. Пример: негативный ответ платёжного шлюза
```
1. Направление: payment-gateway-adapter → шлюз. Порт из мок-профиля → 18063.
2. wm.py apis --grep payment → 18063 448ew payment-gateway-adapter
wm.py stubs 18063 → 8 стабов, 3 штатных + сценарий retry
3. wm.py backup 18063 -o payment-18063-BEFORE.json
4. adapter_paths.py <репо> -o paths.txt ; wm.py cover 18063 paths.txt → дырок нет
5. Нужен «шлюз упал на первой попытке, успех на второй»: два стаба,
scenarioName=payment-confirm-retry, priority 1, 503 → second-attempt → 200.
wm.py add 18063 retry.json ; wm.py reset-scenarios 18063
6. Прогон → wm.py log 18063 -n 5
POST /payments/confirm -> 503 matched=True stub="4. … (шлюз недоступен)"
POST /payments/confirm -> 200 matched=True stub="5. … (успех после повтора)"
jaeger_urls.py payment-gateway-adapter 3 → все внешние вызовы в мок
7. Откат: reset-scenarios, удалить временные стабы, сверить с BEFORE.
8. Экспорт эталона в репозиторий стабов, описать в задаче.
```
---
# 11. Шпаргалка
```
Studio UI {WIREMOCK_URL}/mock-apis/<id>/stubs (SPA, только глазами)
Реестр моков GET /api/v1/mock-apis (Studio: имя+id+порт)
Admin по id /api/v1/mock-apis/<id>/__admin/... (Studio: весь admin-API WireMock)
Admin по порту {WIREMOCK_URL}:<порт>/__admin/mappings (любой WireMock)
wm.py apis [--grep X] [--count] stubs <мок> get <мок> [uuid]
cover <мок> paths.txt [--prefix /p] (exit 2 = есть дырки)
backup <мок> [-o f] restore <мок> f.json --yes
add <мок> f.json update <мок> uuid f.json
rename <мок> uuid "имя" rm <мок> uuid
log <мок> [-n N] [--unmatched] [--full] reset-log <мок>
scenarios <мок> reset-scenarios <мок>
create "имя" [--force] delete-api <мок> --yes (только studio)
adapter_paths.py <каталог | .zip | slug --pat> [--client X] [--clients-key K] [--branch B] [-o f]
jaeger_urls.py <сервис> [<стенд>] [--lookback 7d] [--trace ID]
post_json.py <url> body.json [--set путь=знач] [--set-json путь=JSON] [--get] [--basic u:p] [--dry-run]
<мок> = studio: имя | id | порт; plain: порт | -
Контракт код: блок клиентов в application.yml (пути, таймауты, retry) + .post/.get в коде (методы)
спека: страница метода (тела, коды, синхронный/асинхронный обмен, XML)
Проверка wm.py log (wasMatched) + jaeger_urls.py; Matched-Stub-Id, не -Name
Смежное bamboo-deploy (релизы и деплой), opensearch-logs (логи пода),
qa-task-testing (прогон проверок по задаче)
```