-
Notifications
You must be signed in to change notification settings - Fork 8
Часто встречающиеся use cases
Имеется два публичных http апи:
апи "управления" Typed Schema Swagger UI (tinkoff.ru) - через него создаются моки, (см. пример[1] пункт [1.1] )
апи "заглушек" , которое начинается на http://example/api/mockingbird/exec/ и настраивается в самих
моках. (см. пример [1] пункт [1.2] )
Обычно на проекте при внедрении мокингберд добавляется тестовый контур в котором некоторые(или все) запросы бекенда к сторонним системам ходят в мокингберд, а не к сторонним системам
чтобы создать заглушку выполните запрос (при копировании не забудьте удалить комментарии)
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"
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 отличие от любой другой заглушки.
Таблица правил Резолвинга
Как только мокингберд получает запрос он начинает искать подходящий сценарий/заглушку:
изначально берется весь возможный набор и сокращается по определенным условиям.
- Фильтруем по источнику - будем выполнять сценарий или заглушку (задается тем через какой метод мы создавали /createStub | /createScenario)
0.1 Фильтруем по URL / очереди(топику) (задается в "source" для сценария и "path" + "query" для заглушки)
-
Фильтруем по хедерам (только заглушки)
-
Фильтруем по телу запроса (если задано)
-
Фильтруем по State (задается в "state")
Для успешного выполнения сценария/ заглушки "в конце должен остаться только один". Т.о. если после выполнения всех фильтраций есть несколько подходящих сценариев/заглушек - не сработает ни одна
обратите внимание на таблицу резолвинга. Если существуют две одинаковые(по хедеру и боди) заглушки одна из которых требует состояние а другая нет - выбор будем произведен по стейту (если не найдем стейт - сработает та что без него, если найдем только один - сработает та что с ним, иначе будет ошибка)
Далее в примерах "заглушек" рассмотрим популярные ситуации для их поиска
На этом этапе выполнится инструкция описанная в заглушке:
(если указан seed) Сгенерируется информация описанная в seed
(если указан persist) Создастся или обновится state
(если настроен как proxy) Отправится запрос в другой сервис, будет получен и прокинут к клиенту ответ
отправится ответ описанный в самой заглушке
выполнится callback
persistent заглушки - вечные, не зависят от количества вызовов и не удаляются автоматически никогда (пожалуйста удаляйте самостоятельно ненужные persistent заглушки и сценарии)
ephemeral - не зависят от количества вызовов, живут сутки (удаляются в полночь по времени сервера)
countdown - "одноразовые" по умолчанию, количество срабатываний задается параметром "times": 1 , живут сутки (удаляются в полночь по времени сервера)
"mode" : "xpath", "body" : {}
"mode" : "jlens", "body" : {},
более подробно юзкейс: Мы можем сделать низкоприоритетный (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"
}
"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"
}
}
"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>"
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>
MyBeautifulResponse
Используем специальную нотацию
её же можно использовать и в правилах.
Большая часть функционала и жизненного цикла Сценария совпадает с аналогичным у Заглушки.
Есть отличия:
"Вызов" сценария происходит не по 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"
}
}
}
Обратите внимание: "отправку" можно описать в самой заглушке а не в 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"
}
}
рассмотрим работу с состояниями отдельно.
Основных применений три:
для передачи данных между заглушками
для задания порядка выполнения заглушек/сценариев
для сохранения информации которую нельзя вернуть напрямую из заглушки/сценария
Если коротко то состояние является аналогом "глобальной переменной" существующей внутри Мокингберда
Для полного понимания состояний нужно учитывать несколько их свойств:
Состояния - существуют независимо от сценариев и заглушек
Состояния создаются/изменяются только с помощью заглушки/сценария
Состояние создается/изменяются в момент выполнения заглушки/сценария
Состояния удаляются автоматически спустя 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"
}
}
Как это сработает?
Первой вызовется заглушка без 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"
}
}
{
"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"
}
}
Посмотреть логи (рекомендуется отфильтровать по используемому в заглушке url | или по имени).
Проверить наличие не удаленных/ не сработавших заглушек на том-же апи где наблюдается проблема
Проверить наличие не удаленных стейтов