From bae5aa6537dd7312b64a18f0e1d76a1bb41be8d0 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Fri, 2 Oct 2026 14:33:05 +0200 Subject: [PATCH] #66 Add a WireMock container and demo processes to mock the APIs called by the processes Co-Authored-By: Claude Opus 5.5 (1M context) --- .claude/CLAUDE.md | 2 +- .docker/compose.yaml | 12 +++ .docker/wiremock/__files/books.json | 17 +++++ .../wiremock/mappings/authors-authorized.json | 25 +++++++ .../mappings/authors-unauthorized.json | 17 +++++ .../mappings/books-create-invalid.json | 17 +++++ .docker/wiremock/mappings/books-create.json | 24 ++++++ .docker/wiremock/mappings/books-get.json | 18 +++++ .docker/wiremock/mappings/books-list.json | 14 ++++ .../wiremock/mappings/books-not-found.json | 17 +++++ .../wiremock/mappings/books-server-error.json | 17 +++++ .../mappings/fault-connection-reset.json | 10 +++ .env | 2 + .../process/demo.wiremock.authentication.yaml | 40 ++++++++++ .../process/demo.wiremock.error_codes.yaml | 42 +++++++++++ .../packages/process/demo.wiremock.fault.yaml | 19 +++++ .../process/demo.wiremock.get_json.yaml | 30 ++++++++ .../demo.wiremock.path_templating.yaml | 34 +++++++++ .../process/demo.wiremock.post_json.yaml | 42 +++++++++++ .../demo.wiremock.verify_requests.yaml | 74 +++++++++++++++++++ config/services.yaml | 18 +++++ docs/index.md | 4 + docs/wiremock.md | 52 +++++++++++++ 23 files changed, 546 insertions(+), 1 deletion(-) create mode 100644 .docker/wiremock/__files/books.json create mode 100644 .docker/wiremock/mappings/authors-authorized.json create mode 100644 .docker/wiremock/mappings/authors-unauthorized.json create mode 100644 .docker/wiremock/mappings/books-create-invalid.json create mode 100644 .docker/wiremock/mappings/books-create.json create mode 100644 .docker/wiremock/mappings/books-get.json create mode 100644 .docker/wiremock/mappings/books-list.json create mode 100644 .docker/wiremock/mappings/books-not-found.json create mode 100644 .docker/wiremock/mappings/books-server-error.json create mode 100644 .docker/wiremock/mappings/fault-connection-reset.json create mode 100644 config/packages/process/demo.wiremock.authentication.yaml create mode 100644 config/packages/process/demo.wiremock.error_codes.yaml create mode 100644 config/packages/process/demo.wiremock.fault.yaml create mode 100644 config/packages/process/demo.wiremock.get_json.yaml create mode 100644 config/packages/process/demo.wiremock.path_templating.yaml create mode 100644 config/packages/process/demo.wiremock.post_json.yaml create mode 100644 config/packages/process/demo.wiremock.verify_requests.yaml create mode 100644 docs/wiremock.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 5c75de5..d5a5010 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -56,7 +56,7 @@ Each config's `help:` line shows a concrete example. `-c key:"'value'"` injects **Bridge bundles** each add a small set of tasks (`src/Task/`) plus a client/adapter concept wired in `config/services.yaml`: - `doctrine-process-bundle` — DB & entity tasks: `Database/DatabaseReaderTask`, `DatabaseUpdaterTask`, and `EntityManager/*` (`DoctrineReaderTask`, `DoctrineWriterTask`, `DoctrineBatchWriterTask`, cleaner/detacher/refresher/remover/clearer). -- `rest-process-bundle` — `Task/RequestTask` driven by a tagged `cleverage.rest.client` (see `apicarto_ign` client in `services.yaml`). +- `rest-process-bundle` — `Task/RequestTask` driven by a tagged `cleverage.rest.client` (see `apicarto_ign` client in `services.yaml`). To mock an API, prefer the `wiremock` service and its `wiremock` / `wiremock_admin` clients (stubs in `.docker/wiremock/`, see `docs/wiremock.md` and the `demo.wiremock.*` processes). - `soap-process-bundle` — `Task/RequestTask` driven by a tagged `cleverage.soap.client` (see `oorsprong_countryinfo` client). - `flysystem-process-bundle` — `FileFetchTask`, `ListContentTask`, `RemoveFileTask` over Flysystem storages (config in `config/packages/flysystem.yaml`); the demo also uses SFTP. - `archive-process-bundle` — `ZipTask` / `UnzipTask`. diff --git a/.docker/compose.yaml b/.docker/compose.yaml index ebe45be..b48962b 100644 --- a/.docker/compose.yaml +++ b/.docker/compose.yaml @@ -110,5 +110,17 @@ services: SFTP_MOCK_ROOT: / SFTP_MOCK_IDLE: "4" + # WireMock (https://wiremock.org/): mocks the HTTP APIs called by the demo.wiremock.* processes. + # Stubs are loaded at startup from ./wiremock/mappings (bodies in ./wiremock/__files), response + # templating is enabled for every stub. Admin UI / API: http://localhost:${WIREMOCK_PORT:-8089}/__admin + wiremock: + image: wiremock/wiremock:3.13.2 + tty: true + volumes: + - ./wiremock:/home/wiremock + command: [ "--global-response-templating", "--verbose" ] + ports: + - "${WIREMOCK_PORT:-8089}:8080" + volumes: process_bundle_demo_data: diff --git a/.docker/wiremock/__files/books.json b/.docker/wiremock/__files/books.json new file mode 100644 index 0000000..08a6ac8 --- /dev/null +++ b/.docker/wiremock/__files/books.json @@ -0,0 +1,17 @@ +[ + { + "id": 1, + "title": "It", + "author": "Stephen King" + }, + { + "id": 2, + "title": "Salem", + "author": "Stephen King" + }, + { + "id": 3, + "title": "Fahrenheit 451", + "author": "Ray Bradbury" + } +] diff --git a/.docker/wiremock/mappings/authors-authorized.json b/.docker/wiremock/mappings/authors-authorized.json new file mode 100644 index 0000000..002cf63 --- /dev/null +++ b/.docker/wiremock/mappings/authors-authorized.json @@ -0,0 +1,25 @@ +{ + "name": "Header matching: the authors are only returned with the expected bearer token", + "priority": 1, + "request": { + "method": "GET", + "urlPath": "/api/authors", + "headers": { + "Authorization": { + "equalTo": "Bearer demo-token" + } + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": [ + { + "id": 1, + "name": "Stephen King" + } + ] + } +} diff --git a/.docker/wiremock/mappings/authors-unauthorized.json b/.docker/wiremock/mappings/authors-unauthorized.json new file mode 100644 index 0000000..cdfba08 --- /dev/null +++ b/.docker/wiremock/mappings/authors-unauthorized.json @@ -0,0 +1,17 @@ +{ + "name": "Header matching fallback: any other request on the authors is unauthorized", + "priority": 10, + "request": { + "method": "GET", + "urlPath": "/api/authors" + }, + "response": { + "status": 401, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "error": "Unauthorized" + } + } +} diff --git a/.docker/wiremock/mappings/books-create-invalid.json b/.docker/wiremock/mappings/books-create-invalid.json new file mode 100644 index 0000000..df3789a --- /dev/null +++ b/.docker/wiremock/mappings/books-create-invalid.json @@ -0,0 +1,17 @@ +{ + "name": "POST without title: validation error (lower priority than the matching stub)", + "priority": 10, + "request": { + "method": "POST", + "urlPath": "/api/books" + }, + "response": { + "status": 422, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "error": "The title is required" + } + } +} diff --git a/.docker/wiremock/mappings/books-create.json b/.docker/wiremock/mappings/books-create.json new file mode 100644 index 0000000..48d25f5 --- /dev/null +++ b/.docker/wiremock/mappings/books-create.json @@ -0,0 +1,24 @@ +{ + "name": "POST with a JSON body: matching on the body, the created book echoes the sent title", + "request": { + "method": "POST", + "urlPath": "/api/books", + "headers": { + "Content-Type": { + "contains": "application/json" + } + }, + "bodyPatterns": [ + { + "matchesJsonPath": "$.title" + } + ] + }, + "response": { + "status": 201, + "headers": { + "Content-Type": "application/json" + }, + "body": "{\"id\": {{randomInt lower=100 upper=999}}, \"title\": \"{{jsonPath request.body '$.title'}}\"}" + } +} diff --git a/.docker/wiremock/mappings/books-get.json b/.docker/wiremock/mappings/books-get.json new file mode 100644 index 0000000..0dd7186 --- /dev/null +++ b/.docker/wiremock/mappings/books-get.json @@ -0,0 +1,18 @@ +{ + "name": "Path matching and response templating: a book built from the requested id", + "request": { + "method": "GET", + "urlPathPattern": "/api/books/[0-9]+" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "id": "{{request.pathSegments.[2]}}", + "title": "Book {{request.pathSegments.[2]}}", + "requestedAt": "{{now}}" + } + } +} diff --git a/.docker/wiremock/mappings/books-list.json b/.docker/wiremock/mappings/books-list.json new file mode 100644 index 0000000..ca256e5 --- /dev/null +++ b/.docker/wiremock/mappings/books-list.json @@ -0,0 +1,14 @@ +{ + "name": "Static JSON response: the list of books, read from a body file", + "request": { + "method": "GET", + "urlPath": "/api/books" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFileName": "books.json" + } +} diff --git a/.docker/wiremock/mappings/books-not-found.json b/.docker/wiremock/mappings/books-not-found.json new file mode 100644 index 0000000..f24a055 --- /dev/null +++ b/.docker/wiremock/mappings/books-not-found.json @@ -0,0 +1,17 @@ +{ + "name": "Error status code: book 404 does not exist (higher priority than the generic book stub)", + "priority": 1, + "request": { + "method": "GET", + "urlPath": "/api/books/404" + }, + "response": { + "status": 404, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "error": "Book not found" + } + } +} diff --git a/.docker/wiremock/mappings/books-server-error.json b/.docker/wiremock/mappings/books-server-error.json new file mode 100644 index 0000000..550acac --- /dev/null +++ b/.docker/wiremock/mappings/books-server-error.json @@ -0,0 +1,17 @@ +{ + "name": "Error status code: book 500 triggers a server error", + "priority": 1, + "request": { + "method": "GET", + "urlPath": "/api/books/500" + }, + "response": { + "status": 500, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "error": "Internal error" + } + } +} diff --git a/.docker/wiremock/mappings/fault-connection-reset.json b/.docker/wiremock/mappings/fault-connection-reset.json new file mode 100644 index 0000000..4f568c4 --- /dev/null +++ b/.docker/wiremock/mappings/fault-connection-reset.json @@ -0,0 +1,10 @@ +{ + "name": "Fault injection: the connection is reset, the client gets a transport error", + "request": { + "method": "GET", + "urlPath": "/api/unstable" + }, + "response": { + "fault": "CONNECTION_RESET_BY_PEER" + } +} diff --git a/.env b/.env index d80163d..84a94ae 100644 --- a/.env +++ b/.env @@ -47,6 +47,8 @@ SFTP_SHORT_HOST=sftp-short # Mock SFTP server that injects a stray packet after idle (see .docker/sftp-mock/), to # reproduce the verbatim issue #24 error. Serves /data as its root (SFTP root is empty). SFTP_MOCK_HOST=sftp-mock +# WireMock service (see .docker/wiremock/): mocked HTTP APIs of the demo.wiremock.* processes. +WIREMOCK_URL=http://wiremock:8080 ###< docker/atmoz-sftp ### ###> symfony/routing ### diff --git a/config/packages/process/demo.wiremock.authentication.yaml b/config/packages/process/demo.wiremock.authentication.yaml new file mode 100644 index 0000000..c188fe5 --- /dev/null +++ b/config/packages/process/demo.wiremock.authentication.yaml @@ -0,0 +1,40 @@ +clever_age_process: + configurations: + demo.wiremock.authentication: + description: > + WireMock: header matching (authors-authorized.json, authors-unauthorized.json): the authors are returned + with the expected bearer token, a request without token is unauthorized (401, error outputs) + help: > + Ex: bin/console cleverage:process:execute demo.wiremock.authentication + tasks: + requests: + service: '@CleverAge\ProcessBundle\Task\ConstantIterableOutputTask' + options: + output: + - headers: { } + - headers: { Authorization: 'Bearer demo-token' } + outputs: [ request ] + + request: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + error_strategy: skip + options: + client: wiremock + url: '/authors' + method: 'GET' + outputs: [ decode ] + error_outputs: [ error ] + + decode: + service: '@CleverAge\ProcessBundle\Task\TransformerTask' + options: + transformers: + json_decode: ~ + outputs: [ debug ] + + debug: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' + + error: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' + description: Receives the response body of the requests in error (error_outputs) diff --git a/config/packages/process/demo.wiremock.error_codes.yaml b/config/packages/process/demo.wiremock.error_codes.yaml new file mode 100644 index 0000000..66f616a --- /dev/null +++ b/config/packages/process/demo.wiremock.error_codes.yaml @@ -0,0 +1,42 @@ +clever_age_process: + configurations: + demo.wiremock.error_codes: + description: > + WireMock: error status codes (books-not-found.json, books-server-error.json): with error_strategy skip, + the books 404 (404) and 500 (500) are sent to the error outputs, the other ones are output + help: > + Ex: bin/console cleverage:process:execute demo.wiremock.error_codes + tasks: + ids: + service: '@CleverAge\ProcessBundle\Task\ConstantIterableOutputTask' + options: + output: + - url_parameters: { id: '1' } + - url_parameters: { id: '404' } + - url_parameters: { id: '500' } + - url_parameters: { id: '2' } + outputs: [ request ] + + request: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + error_strategy: skip + options: + client: wiremock + url: '/books/{id}' + method: 'GET' + outputs: [ decode ] + error_outputs: [ error ] + + decode: + service: '@CleverAge\ProcessBundle\Task\TransformerTask' + options: + transformers: + json_decode: ~ + outputs: [ debug ] + + debug: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' + + error: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' + description: Receives the response body of the requests in error (error_outputs) diff --git a/config/packages/process/demo.wiremock.fault.yaml b/config/packages/process/demo.wiremock.fault.yaml new file mode 100644 index 0000000..f353b33 --- /dev/null +++ b/config/packages/process/demo.wiremock.fault.yaml @@ -0,0 +1,19 @@ +clever_age_process: + configurations: + demo.wiremock.fault: + description: > + WireMock: fault injection (fault-connection-reset.json): the connection is reset by the mocked server, + the request fails with a transport error and the process fails + help: > + Ex: bin/console cleverage:process:execute demo.wiremock.fault -v + tasks: + request: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + options: + client: wiremock + url: '/unstable' + method: 'GET' + outputs: [ debug ] + + debug: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' diff --git a/config/packages/process/demo.wiremock.get_json.yaml b/config/packages/process/demo.wiremock.get_json.yaml new file mode 100644 index 0000000..1d62087 --- /dev/null +++ b/config/packages/process/demo.wiremock.get_json.yaml @@ -0,0 +1,30 @@ +clever_age_process: + configurations: + demo.wiremock.get_json: + description: > + WireMock: GET a static JSON response, read from a body file (.docker/wiremock/mappings/books-list.json, + __files/books.json), decode it and iterate over the books + help: > + Ex: bin/console cleverage:process:execute demo.wiremock.get_json + tasks: + request: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + options: + client: wiremock + url: '/books' + method: 'GET' + outputs: [ decode ] + + decode: + service: '@CleverAge\ProcessBundle\Task\TransformerTask' + options: + transformers: + json_decode: ~ + outputs: [ iterate ] + + iterate: + service: '@CleverAge\ProcessBundle\Task\InputIteratorTask' + outputs: [ debug ] + + debug: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' diff --git a/config/packages/process/demo.wiremock.path_templating.yaml b/config/packages/process/demo.wiremock.path_templating.yaml new file mode 100644 index 0000000..9f1da0b --- /dev/null +++ b/config/packages/process/demo.wiremock.path_templating.yaml @@ -0,0 +1,34 @@ +clever_age_process: + configurations: + demo.wiremock.path_templating: + description: > + WireMock: request matching on a path pattern and response templating (books-get.json): the mocked book + is built from the requested id, given by the input + help: > + Ex: bin/console cleverage:process:execute demo.wiremock.path_templating + tasks: + ids: + service: '@CleverAge\ProcessBundle\Task\ConstantIterableOutputTask' + options: + output: + - url_parameters: { id: '1' } + - url_parameters: { id: '42' } + outputs: [ request ] + + request: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + options: + client: wiremock + url: '/books/{id}' + method: 'GET' + outputs: [ decode ] + + decode: + service: '@CleverAge\ProcessBundle\Task\TransformerTask' + options: + transformers: + json_decode: ~ + outputs: [ debug ] + + debug: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' diff --git a/config/packages/process/demo.wiremock.post_json.yaml b/config/packages/process/demo.wiremock.post_json.yaml new file mode 100644 index 0000000..a60bb3a --- /dev/null +++ b/config/packages/process/demo.wiremock.post_json.yaml @@ -0,0 +1,42 @@ +clever_age_process: + configurations: + demo.wiremock.post_json: + description: > + WireMock: POST a JSON body (books-create.json, books-create-invalid.json): a book with a title is + created (201, the response echoes the title), a book without title is rejected (422) and its response + body is sent to the error outputs + help: > + Ex: bin/console cleverage:process:execute demo.wiremock.post_json + tasks: + books: + service: '@CleverAge\ProcessBundle\Task\ConstantIterableOutputTask' + options: + output: + - data: { title: 'Carrie' } + - data: { author: 'Unknown' } + outputs: [ request ] + + request: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + error_strategy: skip + options: + client: wiremock + url: '/books' + method: 'POST' + valid_response_code: [ 201 ] + outputs: [ decode ] + error_outputs: [ error ] + + decode: + service: '@CleverAge\ProcessBundle\Task\TransformerTask' + options: + transformers: + json_decode: ~ + outputs: [ debug ] + + debug: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' + + error: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' + description: Receives the response body of the requests in error (error_outputs) diff --git a/config/packages/process/demo.wiremock.verify_requests.yaml b/config/packages/process/demo.wiremock.verify_requests.yaml new file mode 100644 index 0000000..bc541bd --- /dev/null +++ b/config/packages/process/demo.wiremock.verify_requests.yaml @@ -0,0 +1,74 @@ +clever_age_process: + configurations: + demo.wiremock.verify_requests: + description: > + WireMock: verify the requests received by the mocked API through the WireMock admin API: reset the + request journal, call the API twice, then count the received GET requests on /api/books* + help: > + Ex: bin/console cleverage:process:execute demo.wiremock.verify_requests + tasks: + reset: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + description: Reset the request journal (DELETE /__admin/requests) + options: + client: wiremock_admin + url: '/requests' + method: 'DELETE' + outputs: [ no_input_1 ] + + no_input_1: + service: '@CleverAge\ProcessBundle\Task\ConstantOutputTask' + description: The next request does not take the previous response as input + options: + output: { } + outputs: [ books ] + + books: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + options: + client: wiremock + url: '/books' + method: 'GET' + outputs: [ no_input_2 ] + + no_input_2: + service: '@CleverAge\ProcessBundle\Task\ConstantOutputTask' + options: + output: { } + outputs: [ book ] + + book: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + options: + client: wiremock + url: '/books/1' + method: 'GET' + outputs: [ no_input_3 ] + + no_input_3: + service: '@CleverAge\ProcessBundle\Task\ConstantOutputTask' + options: + output: { } + outputs: [ count ] + + count: + service: '@CleverAge\RestProcessBundle\Task\RequestTask' + description: Count the matching requests (POST /__admin/requests/count), outputs {"count":2} + options: + client: wiremock_admin + url: '/requests/count' + method: 'POST' + data: + method: 'GET' + urlPathPattern: '/api/books.*' + outputs: [ decode ] + + decode: + service: '@CleverAge\ProcessBundle\Task\TransformerTask' + options: + transformers: + json_decode: ~ + outputs: [ debug ] + + debug: + service: '@CleverAge\ProcessBundle\Task\Debug\DebugTask' diff --git a/config/services.yaml b/config/services.yaml index c74728e..8b1d585 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -81,6 +81,24 @@ services: tags: - { name: cleverage.rest.client } + # REST clients on the WireMock service (.docker/wiremock/), used by the demo.wiremock.* processes: + # the mocked "library" API, and the WireMock admin API (to reset / verify the received requests). + app.cleverage_rest_process.client.wiremock: + class: CleverAge\RestProcessBundle\Client\Client + bind: + $code: 'wiremock' + $uri: '%env(string:WIREMOCK_URL)%/api' + tags: + - { name: cleverage.rest.client } + + app.cleverage_rest_process.client.wiremock_admin: + class: CleverAge\RestProcessBundle\Client\Client + bind: + $code: 'wiremock_admin' + $uri: '%env(string:WIREMOCK_URL)%/__admin' + tags: + - { name: cleverage.rest.client } + # For cleverage/cache-process-bundle app.cleverage_cache_process.adapter.memory: class: App\Adapter\MemoryAdapter diff --git a/docs/index.md b/docs/index.md index 9084491..b47580f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,5 +6,9 @@ make start Then access to UI via http://process-bundle-demo.localhost/process (username: admin@clever-age.com / password: admin@clever-age.com) +## Mocking APIs + +See [Mocking APIs with WireMock](wiremock.md): the `wiremock` service and the `demo.wiremock.*` processes. + ## Reference diff --git a/docs/wiremock.md b/docs/wiremock.md new file mode 100644 index 0000000..e349bd6 --- /dev/null +++ b/docs/wiremock.md @@ -0,0 +1,52 @@ +## Mocking APIs with WireMock + +The `wiremock` service of the Docker stack (`.docker/compose.yaml`) runs [WireMock](https://wiremock.org/), which mocks +the HTTP APIs called by the processes: no network access is needed, the responses are known in advance, and the error +cases (status codes, transport errors...) can be reproduced at will. + +### Setup + +- **Stubs**: one JSON file per stub in `.docker/wiremock/mappings/` (request to match, response to return), response + bodies in `.docker/wiremock/__files/`. They are loaded when the container starts: run + `docker compose -f .docker/compose.yaml restart wiremock` after a change (or `POST /__admin/mappings/reset`). +- **Response templating** is enabled for every stub (`--global-response-templating`): a response can use the request + (`{{request.pathSegments.[2]}}`, `{{jsonPath request.body '$.title'}}`...), see + [response templating](https://wiremock.org/docs/response-templating/). +- **REST clients** (`config/services.yaml`): `wiremock` on the mocked API (`%env(WIREMOCK_URL)%/api`), and + `wiremock_admin` on the [admin API](https://wiremock.org/docs/standalone/admin-api-reference/) + (`%env(WIREMOCK_URL)%/__admin`). `WIREMOCK_URL` is defined in `.env` (`http://wiremock:8080`). +- **Admin UI / API** from the host: http://localhost:8089/__admin (the host port can be changed with `WIREMOCK_PORT` + in `.docker/.env`), e.g. `curl http://localhost:8089/__admin/requests` lists the received requests, + `__admin/requests/unmatched` the ones that matched no stub (useful to write a new stub). + +### Use cases + +| Process | Stub(s) (`.docker/wiremock/mappings/`) | Shows | +|---------------------------------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------| +| `demo.wiremock.get_json` | `books-list.json` | Static JSON response read from a body file (`bodyFileName`), decoded and iterated | +| `demo.wiremock.path_templating` | `books-get.json` | Matching on a path pattern (`urlPathPattern`), response built from the requested id | +| `demo.wiremock.post_json` | `books-create.json`, `books-create-invalid.json` | Matching on the JSON body (`matchesJsonPath`), stub priorities, `422` sent to `error_outputs` | +| `demo.wiremock.error_codes` | `books-not-found.json`, `books-server-error.json` | `404` / `500` responses with `error_strategy: skip` and `error_outputs` | +| `demo.wiremock.authentication` | `authors-authorized.json`, `authors-unauthorized.json` | Matching on a header (bearer token), with a lower priority fallback stub (`401`) | +| `demo.wiremock.fault` | `fault-connection-reset.json` | Fault injection (`CONNECTION_RESET_BY_PEER`): transport error, the process fails | +| `demo.wiremock.verify_requests` | `books-list.json`, `books-get.json` | Admin API: reset the request journal, call the API, count the received requests | + +Run them with `bin/console cleverage:process:execute ` (`make bash` first). + +### Adding a stub + +1. Call the API once and look at the unmatched request: `curl http://localhost:8089/__admin/requests/unmatched`. +2. Add a mapping file, e.g. `.docker/wiremock/mappings/books-search.json`: + +```json +{ + "request": { "method": "GET", "urlPath": "/api/books", "queryParameters": { "q": { "matches": ".+" } } }, + "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": [] } +} +``` + +3. Restart the container, or create the stub at runtime: `curl -X POST http://localhost:8089/__admin/mappings -d @books-search.json` + (runtime stubs are lost when the container restarts). + +When several stubs match a request, the one with the lowest `priority` wins (`5` by default): a generic stub with a +high priority value can serve as a fallback (see `authors-unauthorized.json`).