1# Chapter 21½: inside jev-mock/src — the set, the script and the recorder
2
3Three files: the server itself, and the two ways to give it someone else's
4lines.
5
6## The files, in reading order
7
81. **[lib.rs](lib.rs)**: the server.
9   - `Recorded` is one request as the server saw it: HTTP version, method,
10     path, headers, body.
11   - `Reply` is what to answer: `Reply::json(status, body)`, then
12     `.header(name, value)`, `.after(delay)` (stall first; a client that
13     gives up resets the stream, which `abandoned()` counts) and
14     `.after_requests(n)` (stall until n requests have arrived, so a test
15     can count what a client sends before its first answer without racing
16     it). `raw_body` sends exact bytes instead of the JSON.
17   - `MockJev::start(respond)` serves on `127.0.0.1` with a certificate for
18     that address; `start_for(names, respond)` adds host names;
19     `start_limited(max_streams, respond)` advertises
20     `SETTINGS_MAX_CONCURRENT_STREAMS`. Each start makes a fresh CA with
21     `rcgen`, writes it to a temp directory (`ca_file()`), and accepts only
22     ALPN `h2`, as the real endpoint does.
23   - Reading the result: `endpoint()`, `endpoint_via(host)`, `requests()`,
24     `connections()`, `accepts()`, `peak_in_flight()`, `abandoned()`,
25     `pings()`.
26   - Making trouble: `goaway()` sends GOAWAY on every open connection;
27     `ignore_pings_on_next(n)` makes the next n connections swallow their
28     PING acknowledgements.
29   - `Frames`, at the bottom, sits between TLS and hyper and reads the
30     plaintext HTTP/2 stream frame by frame. hyper answers PINGs itself, so
31     this is the only place the mock can count them or withhold the replies.
322. **[replay.rs](replay.rs)**: `Fixture` and `Replay`. A fixture is JSON,
33   `{"model", "exchanges": [{"request", "responses": [{"status", "headers":
34   [[name, value]], "body"}]}]}`, with `request` and `body` as the bytes on
35   the wire, as text. `Fixture::parse`, `load` and `to_json` read and write
36   it. `Replay::new(&fixture).responder()` serves each request's responses
37   in order; `misses()` lists what it lacked, and `assert_complete()` panics
38   naming them, as the last handle's `Drop` does unless the test is already
39   panicking.
403. **[forward.rs](forward.rs)**: `Forward::new(upstream, api_key, model)`
41   and its `responder()`, which sends each request upstream with
42   `block_in_place` (so the mock must run on a multi-threaded tokio
43   runtime), one fresh connection per request. Upstream it passes only
44   `content-type` and `x-typesafe-retry-count` and sets its own
45   `authorization`; from the answer it keeps only `content-type`,
46   `x-typesafe-request-id`, `retry-after` and `retry-after-ms`. A retried
47   request records its answers in order. A failure to reach the endpoint is
48   answered with a 400 (`forward_failed`) and recorded as nothing.
49   `exchange(body)` sends a request built elsewhere; `fixture()` returns
50   everything recorded so far.
51
52## Try it
53
54```
55cargo test -p jev-http --test mock
56```
57
58jev-mock has no tests of its own; its users' tests exercise it.
59
60## For the people who maintain it
61
62### In this folder
63
64| File | What |
65| --- | --- |
66| [lib.rs](lib.rs) | `MockJev`, `Reply`, `Recorded`, the frame watcher. |
67| [replay.rs](replay.rs) | `Fixture`, `Replay`, `Response`. |
68| [forward.rs](forward.rs) | `Forward`: record a fixture from a real endpoint. |
69| [CLAUDE.md](CLAUDE.md) | Invariants for agents. |
70
71← Previous: [Chapter 21: jev-mock](../) · Up: [jev-mock](../) · The end of the guide. [Back to Chapter 16: jevcrates](../../)