# Mock Services

<!-- MDOC !-->

How the platform reaches mocked third-party APIs, on a laptop and from deployed
environments. Two mock surfaces exist and they answer different questions:
**WireMock** stands in for third-party SaaS APIs, **MiniStack** stands in for AWS.

## The two surfaces

**WireMock** (`wiremock/wiremock:3.10.0`) serves the stub corpus in
`wm_mappings/`, with response bodies in `wm_response_files/`. It covers the
third-party APIs the platform integrates with — Athenahealth, Twilio, Mailgun,
Stripe, SendGrid, HubSpot, QuickBooks, Airtable, Atomic FI, the LLM providers,
and a small set of AWS endpoints MiniStack cannot serve.

**MiniStack** serves AWS itself: `s3,lambda,sns,ssm,sqs,sts,logs,secretsmanager,cloudformation,rds,ses`
(`local-dependencies.yml`). See [Datalakes](datalakes.md) for how datalake
provisioning uses it. An SES email tool whose endpoint URL is `http://localhost:4566`
sends into MiniStack, which lists every message it received at
`http://localhost:4566/_ministack/ses/messages`.

The division is not arbitrary. No AWS emulator here serves
`pinpoint-sms-voice-v2` — LocalStack Community answered 501 (Ultimate tier
only) and MiniStack has no such service at all — so End User Messaging points
at WireMock for its send, its S3 media staging, and its STS credential probe. A
tool has one `endpoint_url`, so all three land on the same host.

## The corpus

Counted on `release/0.23.0` — 16 files, 98 stubs. The feature mix is what makes
WireMock itself load-bearing rather than incidental:

| Feature | Uses | What it does |
|---|---:|---|
| `urlPathPattern` | 80 | Regex path matching |
| `jsonBody` / `body` / `base64Body` | 85 | Inline response bodies |
| `bodyFileName` | 21 | Bodies served from `wm_response_files/` |
| `bodyPatterns` | 11 | Request-body matching |
| `queryParameters` | 9 | Query matching |
| `scenarioName` + `newScenarioState` | 7 | **Stateful scenarios — a state machine across requests** |
| `--global-response-templating` | on | Handlebars templating in responses |

The last two rows are why the corpus is served by the real WireMock image
everywhere rather than by a bespoke matcher. Reimplementing stateful scenarios
and a templating engine means re-earning fidelity, and a gap in a mock surfaces
as a test passing against a fiction rather than as a failure.

### Two gotchas that cost real time

**WireMock rejects unknown keys on a stub mapping.** A stray `_comment` field
makes it refuse to load the *whole file* and 404 everything in it, while
`json.load` and any JSON linter pass it happily. Put prose in the free-form
`metadata` object instead.

**WireMock loads mappings at boot.** After editing `wm_mappings/`, reload the
local container without restarting it:

```bash
curl -X POST http://localhost:8080/__admin/mappings/reset
```

That is the first thing to try when a stub 404s locally. It is not available on
the hosted instance — see below.

## Running locally

`make run-backing-services` starts both. WireMock listens on `:8080`, MiniStack
on `:4566`. Tool bodies reach them by setting `endpoint_url` — blank means real
AWS, any value replaces host, port and scheme.

MiniStack's S3 state does **not** survive a container restart. Recreate a bucket
after one:

```bash
AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=us-east-1 \
  aws s3 mb s3://healthcare-lake --endpoint-url http://localhost:4566
```

## The hosted mock — `wiremock.alvera.ai`

Deployed environments used to need their own always-on WireMock, which is the
wrong billing shape for a service that is idle almost all of the time. The corpus
now runs on Cloudflare Containers behind one hostname, billed only while awake.

```mermaid
flowchart TD
    C["Consumers<br/>platform dev · UAT · integration-tests · CI"]
    C -->|"https://wiremock.alvera.ai"| W

    subgraph CF["Cloudflare"]
        F["WAF custom rule<br/>/__admin → Block"]
        W["Worker + Durable Object<br/>container binding"]
        K["Container — basic<br/>wiremock/wiremock:3.10.0<br/>/mappings ◀ wm_mappings/<br/>/__files ◀ wm_response_files/"]
        F -.->|"blocks before the Worker runs"| W
        W -->|fetch| K
    end
```

**It lives in this repo**, at `cloudflare/workers/mock/`. Deploy from a normal checkout:

```bash
cd cloudflare/workers/mock && wrangler deploy
```

The single line that makes this work without a vendored copy of the corpus:

```jsonc
"image_build_context": "../../.."
```

That sets the Docker build context to the repo root, so the Dockerfile copies
`wm_mappings/` and `wm_response_files/` from where they already live. Without it
the context is `cloudflare/workers/mock/` and the corpus would have to be duplicated there —
two directories that look identical until they quietly aren't.

### There is no authentication

The hostname is public and deliberately so. The corpus is synthetic fixture data,
the mock authenticates nobody, and a gate in front of it caused more problems
than it solved:

- Nineteen of the 98 stubs match on the `Authorization` header, because they
  assert the *tool* authenticated to the API being mocked. Cloudflare Access
  consumes `Authorization` for its own credential, so gating on that header made
  those nineteen stubs stop matching.
- Any gate means a credential per consumer, which means minting, distributing,
  rotating and revoking — for a service whose contents are fixtures.

The accepted risk is bot traffic waking the container. Workers include 10 million
requests a month before any charge, so the exposure is container time, not
request volume — and it is small enough to deal with if it ever shows up.

### The admin API is blocked

WireMock's admin API has no authentication of its own: anything that can reach
`/__admin` can read the request journal, reset scenarios, or reprogram stubs.
Publicly hosted, that surfaces as CI failing for no visible reason. A WAF custom
rule on the zone refuses it:

```
(http.host eq "wiremock.alvera.ai" and http.request.uri.path contains "/__admin")  →  Block
```

Scoped by `http.host` so it cannot affect the other services on `alvera.ai`.
Cloudflare normalises the URL before evaluating rules, so `/%5f%5fadmin` and
`//__admin` are blocked too — verified, not assumed.

> #### The block is safe *because* the corpus is baked {: .warning}
>
> Stubs live in the image. Nothing registers them at runtime, so nothing needs
> the admin API in production. **If that ever changes — if an app wants to
> register stubs at runtime — this rule must be reconsidered, not worked
> around.** It would also need `max_instances: 1` and a fixed container name,
> because registrations do not survive an instance sleeping or a request landing
> on a different instance.

### Adding or changing a stub

Edit `wm_mappings/` (or `wm_response_files/`), then redeploy. There is no hot
reload on the hosted instance — the image is the state, which makes the hosted
mock immutable per deploy and reproducible for CI.

### Verifying the hosted mock matches local

The failure worth catching is the two environments quietly disagreeing. Compare
them by hashing responses rather than asserting content, so the check does not
break when someone edits a stub — only when local and edge diverge:

```bash
for base in http://localhost:8080 https://wiremock.alvera.ai; do
  curl -s -X POST -H 'content-type: application/json' -d '{"contents":[]}' \
    "$base/gemini/v1beta/models/gemini-2.0-flash:generateContent" | shasum -a 256
done
```

The gemini stubs are the sharpest test: they use a seven-state scenario, so three
identical POSTs return three *different* bodies in a fixed order. Matching hashes
across three successive calls proves both environments walked the same state
machine — something no stateless matcher could fake.

## MiniStack at the edge

Not done, and deliberately scoped apart. MiniStack is also just an image and
rides the same mechanism, but Containers **sleep by design** and MiniStack's S3
state already does not survive a restart. What is an occasional annoyance locally
becomes routine when instances sleep between runs. Anything depending on objects
persisting must move to R2, which speaks S3 natively — migration work, not a flag
flip.

## Related

- [Tools](tools.md) — tool bodies, `endpoint_url`, and REST API auth methods
- [Datalakes](datalakes.md) — how datalake provisioning uses MiniStack
