Skip to content

Часто встречающиеся use cases

akabella1 edited this page Jan 30, 2023 · 2 revisions

Часто встречающиеся use-cases

С чего начать?

Имеется два публичных http апи:

апи "управления" Typed Schema Swagger UI (tinkoff.ru) - через него создаются моки, (см. пример[1] пункт [1.1] )
апи "заглушек" , которое начинается на http://example/api/mockingbird/exec/ и настраивается в самих
моках. (см. пример [1] пункт [1.2] )

Обычно на проекте при внедрении мокингберд добавляется тестовый контур в котором некоторые(или все) запросы бекенда к сторонним системам ходят в мокингберд, а не к сторонним системам

Пример создания простейшей заглушки[1]:

чтобы создать заглушку выполните запрос (при копировании не забудьте удалить комментарии)

curl --location --request POST 'http://example/api/internal/mockingbird/createHttpStub' \
---------[1.1]
--header 'Content-Type: application/json' \
--data-raw '{
"scope": "countdown",
"method": "POST",
"name": "My demo mock",
"path": "/demo/demo", -----------[1.2]
"request": {
"headers": {},
"mode": "jlens",
"body":{"id": {"==":"1234"}} ---------------[1.3]
},
"response": { ----- [1.4]
"code": 200, ---[1.5]
"mode": "json",
"body": { "ABCD": "${req}"}, ----------[1.6]
"headers": { "Content-Type": "application/json"},
"delay": "0 second"
}
}'
мы делаем заглушку для http POST метода "method": "POST", [1.1]
расположенную на относительном URL "path":"/demo/demo" [1.2]
Эта заглушка сработает только если в JSON "mode": "jlens" теле запроса будет ключ id = "1234" "body":{"id": {"==":"
1234"}} [1.3]
При срабатывании заглушка вернет ответ [1.4] со статус кодом 200 "code": 200 [1.5]
В теле ответа будет ключ ABCD со значением = телу запроса "${req}" [1.6]

Чтобы получить ответ от заглушки выполните

curl --location --request POST 'http://example/api/mockingbird/exec/demo/demo' \
--header 'Content-Type: application/json' \
--data-raw '{
"a":"b", "id":"1234"
}'

обратите внимание, мы отправляем POST запрос на http://example/api/mockingbird/exec/demo/demo содержащий JSON в котором есть ключ "id" : "1234"

Общая информация

Сущности используемые в mockingbird

Stub - Заглушка
(под заглушками мы имеет в виду мокированые http-эндпойнты)
Scenario - Сценарий
(под "сценариями" имеем в виду мокированые реакции на сообщения в какой либо очереди)
State - состояние
("глобальная" переменная

Общие настройки

Актуальное на момент написания документации расположение Mockingbird http://example/api/internal/mockingbird

Соответственно:

Запросы на управление (к самому сервису-мокинг-берду) отправляем на http://example/api/internal
/mockingbird/[METHOD_NAME]
Запросы к заглушкам отправляем на http://example/api/mockingbird/exec/[путь переданный в "path"
заглушки]
Мы создали заглушку указав
"scope": "countdown",
"times": 1, "method": "POST",
"path": "/demo_documentation",
Для получения ответа от этой заглушки нам нужно обратиться к
Request method: POST
Request URI: http://example/api/mockingbird/exec/demo_documentation
Запросы к сценариям отправляются через отдельно конфигурируемые очереди/топики. В данной документации будут
представлены примеры на основе существующий очередей/топиков проекта Автокредиты

Жизненные циклы

Создание

происходит сразу после отправки запроса на создание в мокингберд, выполняется синхронно.

сразу после создания заглушка готова к срабатыванию

При создании заглушка проверяется на уникальность. Уникальной заглушка будет считаться только если во всем наборе полей для поиска есть не менее чем 1 отличие от любой другой заглушки.

Поиск подходящей заглушки/сценария

Таблица правил Резолвинга

Как только мокингберд получает запрос он начинает искать подходящий сценарий/заглушку:

изначально берется весь возможный набор и сокращается по определенным условиям.

  1. Фильтруем по источнику - будем выполнять сценарий или заглушку (задается тем через какой метод мы создавали /createStub | /createScenario)

0.1 Фильтруем по URL / очереди(топику) (задается в "source" для сценария и "path" + "query" для заглушки)

  1. Фильтруем по хедерам (только заглушки)

  2. Фильтруем по телу запроса (если задано)

  3. Фильтруем по State (задается в "state")

Для успешного выполнения сценария/ заглушки "в конце должен остаться только один". Т.о. если после выполнения всех фильтраций есть несколько подходящих сценариев/заглушек - не сработает ни одна

обратите внимание на таблицу резолвинга. Если существуют две одинаковые(по хедеру и боди) заглушки одна из которых требует состояние а другая нет - выбор будем произведен по стейту (если не найдем стейт - сработает та что без него, если найдем только один - сработает та что с ним, иначе будет ошибка)

Далее в примерах "заглушек" рассмотрим популярные ситуации для их поиска

Выполнение

На этом этапе выполнится инструкция описанная в заглушке:

(если указан seed) Сгенерируется информация описанная в seed
(если указан persist) Создастся или обновится state
(если настроен как proxy) Отправится запрос в другой сервис, будет получен и прокинут к клиенту ответ
отправится ответ описанный в самой заглушке
выполнится callback

Удаление

persistent заглушки - вечные, не зависят от количества вызовов и не удаляются автоматически никогда (пожалуйста удаляйте самостоятельно ненужные persistent заглушки и сценарии)

ephemeral - не зависят от количества вызовов, живут сутки (удаляются в полночь по времени сервера)

countdown - "одноразовые" по умолчанию, количество срабатываний задается параметром "times": 1 , живут сутки (удаляются в полночь по времени сервера)

HTTP (rest/soap)

Нужен мок который сработает без проверки тела

"mode" : "xpath", "body" : {}
"mode" : "jlens", "body" : {},

Мне нужно переключиться на внешнюю интеграцию но нет возможности сконфигурировать бэк(нужно

часто переключаться, а на бэке это делать дого) - "PROXY"

более подробно юзкейс: Мы можем сделать низкоприоритетный (ephemeral|persistent) прокси-мок который будет работать только если нет более приоритетного (countdown) мока. таким образом у нас всегда будет ответ на нужном эндпойнте и для его подмены не придется перенастраивать бэк, остаточно будет добавить мок.

{
"scope" : "countdown",
"times" : 1,
"method" : "POST",
"path" : "/demo/demo",
"request" : {
"body" : {},
"mode" : "jlens",
"headers" : {}
},
"response" : {
"uri" : "http://example/api/1.0",
"delay" : "0 seconds",
"mode" : "proxy"
},
"name" : "PROXY DEMO"
}

"У меня SOAP, только один эндпойнт, как сделать несколько заглушек?"

{
"scope": "countdown",
"method": "POST",
"name": "",
"path": "/example/some_system",
"request": {
"headers": {
"SOAPAction": "\"document/http://siebel.com/EXAMPLE:CalledAction\"" ---- , SOAPAction
},
"mode": "xpath",
"body": {
"/Envelope/Body/Path/Id": { -----
"==": "1234"
}
}
},
"response": {
"code": 200,
"mode": "xml",
"body": "<SOAP-ENV:Envelope>\r\n <SOAP-ENV:Header>\r\n </SOAP-ENV:Header>\r\n <SOAP-ENV:Body>\r\n
</SOAP-ENV:Body>\r\n</SOAP-ENV:Envelope>",
"headers": {
"Content-Type": "application/xml"
},
"delay": "0 second"
}
}

"Есть параметры в query, мне их нужно дальше использовать/срабатывать только на определенные"

{
"scope": "countdown",
"method": "GET",
"name": "",
"path": "/example/kek",
"request": {
"headers": {},
"mode": "no_body",
"query": {
"data": {
"==": 1234 ----- GET URL /example/kek?data=
}
}
},
"response": {
"code": 200,
"mode": "json",
"body":{"query":"${query}"}, ------ {"query": {"data": 1234}}
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}

"Нужно чтобы заглушка срабатывала только на запрос с определенным телом"

{
"scope": "countdown",
"method": "POST",
"name": "",
"path": "/example/sssss",
"request": {
"headers": {},
"mode": "xpath",
"body": {
"/Envelope/Body/Path/Id": { ----- XML json
"==": "1234"
}
}
},
"response": {
"code": 200,
"mode": "xml",
"body": "<SOAP-ENV:Envelope>\r\n <SOAP-ENV:Header>\r\n </SOAP-ENV:Header>\r\n <SOAP-ENV:Body>\r\n
</SOAP-ENV:Body>\r\n</SOAP-ENV:Envelope>",
"headers": {
"Content-Type": "application/xml"
},
"delay": "0 second"
}
}

"Найти заглушку по наличию или отсутствию поля"

// exist
"/Envelope/Body/ID": {"exists": true}
"" , ID
"<tns:Envelope><tns:Body><ID/></tns:Body></tns:Envelope>"
"<tns:Envelope><tns:Body><ID></ID></tns:Body></tns:Envelope>"
"<tns:Envelope><tns:Body><ID> </ID></tns:Body></tns:Envelope>"
DEMO-STUB
Request URI: http://example/api/internal/mockingbird/createHttpStub
Body:
{
"scope": "countdown",
"times": 1,
"method": "POST",
"name": "demo_documentation",
"path": "/demo_documentation",
"request": {
"headers": {
},
"mode": "xpath",
"body": {
"/Envelope/Body/ID": {
"exists": true
}
}
},
"response": {
"code": 200,
"mode": "raw",
"body": "MyBeautifulResponse",
"headers": {
"Content-Type": "application/xml"
},
"delay": "0 second"
}
}
DEMO-REQUEST
Request method: POST
Request URI: http://example/api/mockingbird/exec/demo_documentation
Body:
<tns:Envelope><tns:Body><ID></ID></tns:Body></tns:Envelope>
HTTP/1.1 200 OK
MyBeautifulResponse

"В запросе прилетает число а нужно вернуть строку"(или наоборот)

Используем специальную нотацию

её же можно использовать и в правилах.

$:{..} -
$~{..} -

"Шины"(rabbit|kafka)

Большая часть функционала и жизненного цикла Сценария совпадает с аналогичным у Заглушки.

Есть отличия:

"Вызов" сценария происходит не по url а по факту попадания в прослушиваемую им очередь/топик события/сообщения

"мне нужен сценарий перекладывающий из одной очереди в другую"

{
"scope": "countdown",
"name": "example",
"source": "in_queue",
"input": {
"payload": { ------------ body http
"/Body/Path/Id": {
"==": "1cd627d6-f0a3-4868-986f-2ddf9b6408ab"
}
},
"mode": "xpath"
},
"destination": "out_queue",
"output": {
"payload": "<ns6:Event> ++++++++ </ns6:Event>", ---- / (xml, json)
"delay": "0s",
"mode": "xml"
}
}
}

"Сценарий должен вызвать http метод а потом что-то отправить в очередь"

Обратите внимание: "отправку" можно описать в самой заглушке а не в callback только в том случае, если информации для
её отправки достаточно из входящего запроса(или state) т.к. она отправится одновременно с выполнением сценария(до
коллбэков)
В нашем примере в нам нужно вернуть ответ уже после завершения запроса на "url": "http://еxample/api/internal/еxample1",
{
"scope": "countdown",
"name": "еxample",
"source": "еxample",
"input": {
"payload": {
"/BODY/EXAMPLE/ID": {
"==": "1cd627d6-f0a3-4868-986f-2ddf9b6408ab"
}
},
"mode": "xpath"
},
"callback": {
"request": {
"url": "http://еxample/api/internal/еxample1",
"method": "POST",
"headers": {
"Authorization": "f05ab43f-0e3e-4a7c-9243-764c12280b0b",
"Content-Type": "application/xml"
},
"body": " xml",
"mode": "xml"
},
"callback": {
"destination": "еxample",
"output": {
"payload": "<ns6:Event > ++++++++ </ns6:Event>",
"delay": "0s",
"mode": "xml"
},
"type": "message"
},
"type": "http"
}
}

"состояния/state"

рассмотрим работу с состояниями отдельно.

Основных применений три:

для передачи данных между заглушками
для задания порядка выполнения заглушек/сценариев
для сохранения информации которую нельзя вернуть напрямую из заглушки/сценария

Если коротко то состояние является аналогом "глобальной переменной" существующей внутри Мокингберда

Для полного понимания состояний нужно учитывать несколько их свойств:

Состояния - существуют независимо от сценариев и заглушек
Состояния создаются/изменяются только с помощью заглушки/сценария
Состояние создается/изменяются в момент выполнения заглушки/сценария
Состояния удаляются автоматически спустя 1-2 недели

Для работы с состоянием у заглушки/сценария используются 2 ключа:

state - инструкция поиска состояния. Условие строгое, если состояние не было найдено или найдено несколько - заглушка /сценарий не будут выполнены.

persist - зависит от наличия в заглушке/сценарии ключа state: При наличии ключа state - обновит/дополнит содержимое состояния, При отсутствии ключа state заглушка создаст новый state сохранив в него данные описанные в persist.

Получение данных из state выполняется через ${state.Х} где Х - ключ значение которого можно подставить

При наличии инструкций persist и state внутри заглушки будет использоваться значение полученное из state, persist запишется в конце выполнения заглушки

Коллизии при поиске: еще раз отметим что инструкция state является инструкцией выбора заглушки , т.е. при нахождении 2 и более подходящих стейтов заглушка не выполнится. Таким образом настоятельно рекомендуется использовать в заглушках при записи и поиске уникальные данные/идентификаторы, при невозможности использования уникальных данных - стараться подбирать достаточно редкие для возникновения комбинации, подробнее в примерах

"бэкенд что-то вызывает и я хочу узнать что он передает в вызове/в шину/в очередь"

самый простой путь - записать весь вызов в state и найти этот стейт по части данных

обратите внимание что запись в стейт зависит от типа запроса

в шинной заглушке вместо req следует использоватьmessage

в случае XML нужно использовать XPATH (как в шинной так и в http заглушке)

http://еxample/api/internal/mockingbird/createHttpStub
{
"scope": "countdown",
"method": "POST",
"name": " 200",
"path": "/somedemoendpoint",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"persist": {
"valueForSeach":" state",
--JSON "body": "${req}"
--XML "lastName": "${/Envelope/Body/score/application/borrower/lastName}"
},
"response": {
"code": 200,
"mode": "json",
"body": {},
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}
http://еxample/api/internal/mockingbird/fetchStates
{
"query": {"valueForSeach":{"==":" state"}}
}

"бэкенд вызывает последовательно два метода. в ответе второго нужно вернуть часть запроса к первому"

http://еxample/api/internal/mockingbird/createHttpStub
{
"scope": "countdown",
"method": "POST",
"name": " 200",
"path": "/somedemoendpoint",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"persist": {
"valueForSeach":" state", ------ , 2 , , ,
"body": "${req}"
},
"response": {
"code": 200,
"mode": "json",
"body": {},
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}
{
"scope": "countdown",
"method": "POST",
"name": " ",
"path": "/somedemoendpoint2",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"state": {
"valueForSeach":{"==":" state"} ----- - "==" ( )
},
"response": {
"code": 200,
"mode": "json",
"body": {"body":"${state.body}"},
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}

"бэкенд последовательно дергает один и тот-же метод, но мне нужна заглушка срабатывающая по разному

в 1, 2 .. n раз( или разные заглушки для каждого из вызовов)"

Как это сработает?
Первой вызовется заглушка без State, т.к. для двух других стейта не найдется
При своем выполнении она создаст стейт и запишет в него 2 значения "valueForSeach" и "step" = 1, после выполнения её
счетчик уменьшится (1 0) и она перестанет существовать. Остаются заглушки 2 и 3
При следующем вызове мы дойдем до проверки стейта(т.к. нет заглушек без стейтов а прочие условия одинаковы) Для
второй есть стейт со "step" = 1 , для 3й такого стейта нет. Заглушка 2 не создаст новый стейт, но обновит существующий
(найденный) записав "step" = 2. далее будет выполняться заглушка 3 столько раз, сколько сможет
[http://еxample/api/internal/mockingbird/createHttpStub](http://еxample/api/internal/mockingbird/createHttpStub)
{
"scope": "countdown",
"method": "POST",
"name": " 200",
"path": "/somedemoendpoint",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"persist": {
"valueForSeach":" state", ------ (. ) , ,
"step": "1"
},
"response": {
"code": 200,
"mode": "json",
"body": {},
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}
{

"scope": "countdown",
"method": "POST",
"name": " ",
"path": "/somedemoendpoint",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"state": {
"valueForSeach":{"==":" state"}, "step": {"==":"1"}
},
"persist": {
"step": "2"
},
"response": {
"code": 200,
"mode": "json",
"body": {"body":"${state.step}"}, ----- 1 !!!
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}
{

"scope": "countdown",
"method": "POST",
"name": " ",
"path": "/somedemoendpoint",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"state": {
"valueForSeach":{"==":" state"},
"step": {"==":"2"}
},
"response": {
"code": 200,
"mode": "json",
"body": {"body":"${state.step}"}, ----- 2
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}

Альтернативный вариант:

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

стейт для заглушки 2 моет быть создан не заглушкой 1 а какой-то другой, главное чтобы он оказался уникальным.

http://еxample/api/internal/mockingbird/createHttpStub
{
"scope": "countdown", ---------- ephemeral/persistent
"method": "POST",
"name": " ",
"path": "/somedemoendpoint",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"persist": {
"valueForSeach":" state , ", ------ (. ) , ,
},
"response": {
"code": 200,
"mode": "json",
"body": {...},
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}
{
"scope": "ephemeral", ---------- persistent
"method": "POST",
"name": " ",
"path": "/somedemoendpoint",
"request": {
"headers": {},
"mode": "jlens",
"body":{}
},
"state": {
"valueForSeach":{"==":" state"}
},
"persist": {
"step": "2"
},
"response": {
"code": 200,
"mode": "json",
"body": {...},
"headers": {
"Content-Type": "application/json"
},
"delay": "0 second"
}
}
1.
2.
3.

есть только исходящая очередь и в неё нужно что-то отправить

{
"path": "/еxample/kafka_writer",
"name": "name",
"labels": [],
"method": "POST",
"scope": "persistent",
"request": {
"headers": {},
"query": {},
"body": {},
"mode": "jlens"},
"response": {
"code": 200,
"headers": {
"Content-Type": "application/xml"
},
"body": "",
"mode": "json"
},
"callback": {
"destination": "еxample",
"output": {
"payload": "${req}", //
"delay": "0s",
"mode": "json"
},
"type": "message"
}
}

Решение проблем (Troubleshoting)

"Вчера работало а сегодня нет"

Посмотреть логи (рекомендуется отфильтровать по используемому в заглушке url | или по имени).
Проверить наличие не удаленных/ не сработавших заглушек на том-же апи где наблюдается проблема
Проверить наличие не удаленных стейтов
Clone this wiki locally