1# Chapter 18¾: jev-client/src/client — a stage play for one client 2 3This folder holds one file, [tests.rs](tests.rs): the tests for 4`Client::ask`. Each test writes a script of what the "server" will do, hands 5it to a fake transport, and runs the real retry loop against it on a clock 6that only moves when told to. 7 8## The fake world 9 10Three fakes share one `Fake` struct, which keeps the script, a log of 11events, every request sent, a count of distrusted connections, and the 12clock: 13 14- **`FakeTransport`** plays the script one `Step` per send: `Reply { status, 15 headers, body }`, `Fail(TransportErrorKind)`, or `Stall`, which never 16 answers. 17- **`FakeRuntime`** keeps virtual time. `sleep` adds its duration to the 18 clock and returns at once. `timeout` polls the future once: if it is ready 19 it returns the output, and if not (a `Stall`) it adds the whole timeout to 20 the clock and returns `None`. `jitter` is always 0, so every backoff is 21 exact. 22- **`FakeObserver`** writes each `Event` as a line of text, so a test can 23 compare the whole story at once. 24 25`run(script)` builds a client over the three, asks one `Noul`, and polls the 26future exactly once. With fakes that never leave it waiting, one poll runs 27the whole ask, retries and sleeps included. If it does not finish, the test 28fails with "fakes never leave the client pending". 29 30> **Aside.** This is why the client takes its clock as a port. A real 31> 30-second budget test would take 30 seconds; here 32> `the_budget_bounds_everything` checks the clock reads exactly 30 s and 33> finishes instantly. 34 35## What the tests pin down 36 37| Test | Shows | 38| --- | --- | 39| `answers_first_time` | One attempt, no time passes, the authorization header is marked sensitive. | 40| `honours_the_servers_delay` | A `429` with `retry-after-ms: 300` waits exactly 300 ms, and the retry carries `x-typesafe-retry-count: 1`. | 41| `backs_off_then_gives_up_with_the_last_request_id` | Three `503`s: 0.5 s then 1 s of backoff, three attempts, the last request id kept. | 42| `other_4xx_fail_at_once` | A `403` is not retried. | 43| `context_overflow_is_not_resent` | `max_tokens_exceeded` comes back as `ContextOverflow` at once. | 44| `never_sent_is_redialled_not_retried` | One `NotSent` is resent with no backoff and no retry header. | 45| `repeated_never_sent_becomes_a_retry` | After two redials, a third `NotSent` is a retry. | 46| `unretryable_transport_failures_fail_at_once` | `Tls`, `TooLarge` and `Config` are not retried. | 47| `a_stalled_attempt_times_out_distrusts_the_connection_and_retries` | A stall costs 10 s, the connection is distrusted, the retry answers. | 48| `the_budget_bounds_everything` | Three stalls end in `TimedOut` at exactly 30 s. | 49| `transport_errors_keep_an_earlier_request_id` | An id from an earlier `503` survives later transport failures. | 50| `a_wrong_answer_carries_its_request_id` | A 200 from another model is a `Response` error with its id. | 51| `a_success_reports_its_request_id_and_usage` | The events of a clean answer. | 52| `a_retry_reports_its_cause_delay_and_request_id` | The events of two retries, with causes and delays. | 53| `a_redial_is_reported_and_is_not_a_retry` | The events of a redial. | 54 55## Try it 56 57``` 58cargo test -p jev-client 59``` 60 61## For the people who maintain it 62 63### In this folder 64 65| File | What | 66| --- | --- | 67| [tests.rs](tests.rs) | The fakes, `run`, and the fifteen tests above. | 68| [CLAUDE.md](CLAUDE.md) | Invariants for agents. | 69 70← Previous: [Chapter 18½: inside jev-client/src](../) · Up: [jev-client/src](../) · Next: [Chapter 19: jev-worker](../../../jev-worker/) →