Chapter 18¾: jev-client/src/client — a stage play for one client
This folder holds one file, tests.rs: the tests for
Client::ask. Each test writes a script of what the "server" will do, hands
it to a fake transport, and runs the real retry loop against it on a clock
that only moves when told to.
The fake world
Three fakes share one Fake struct, which keeps the script, a log of
events, every request sent, a count of distrusted connections, and the
clock:
FakeTransportplays the script oneStepper send:Reply { status, headers, body },Fail(TransportErrorKind), orStall, which never answers.FakeRuntimekeeps virtual time.sleepadds its duration to the clock and returns at once.timeoutpolls the future once: if it is ready it returns the output, and if not (aStall) it adds the whole timeout to the clock and returnsNone.jitteris always 0, so every backoff is exact.FakeObserverwrites eachEventas a line of text, so a test can compare the whole story at once.
run(script) builds a client over the three, asks one Noul, and polls the
future exactly once. With fakes that never leave it waiting, one poll runs
the whole ask, retries and sleeps included. If it does not finish, the test
fails with "fakes never leave the client pending".
Aside. This is why the client takes its clock as a port. A real 30-second budget test would take 30 seconds; here
the_budget_bounds_everythingchecks the clock reads exactly 30 s and finishes instantly.
What the tests pin down
| Test | Shows |
|---|---|
answers_first_time | One attempt, no time passes, the authorization header is marked sensitive. |
honours_the_servers_delay | A 429 with retry-after-ms: 300 waits exactly 300 ms, and the retry carries x-typesafe-retry-count: 1. |
backs_off_then_gives_up_with_the_last_request_id | Three 503s: 0.5 s then 1 s of backoff, three attempts, the last request id kept. |
other_4xx_fail_at_once | A 403 is not retried. |
context_overflow_is_not_resent | max_tokens_exceeded comes back as ContextOverflow at once. |
never_sent_is_redialled_not_retried | One NotSent is resent with no backoff and no retry header. |
repeated_never_sent_becomes_a_retry | After two redials, a third NotSent is a retry. |
unretryable_transport_failures_fail_at_once | Tls, TooLarge and Config are not retried. |
a_stalled_attempt_times_out_distrusts_the_connection_and_retries | A stall costs 10 s, the connection is distrusted, the retry answers. |
the_budget_bounds_everything | Three stalls end in TimedOut at exactly 30 s. |
transport_errors_keep_an_earlier_request_id | An id from an earlier 503 survives later transport failures. |
a_wrong_answer_carries_its_request_id | A 200 from another model is a Response error with its id. |
a_success_reports_its_request_id_and_usage | The events of a clean answer. |
a_retry_reports_its_cause_delay_and_request_id | The events of two retries, with causes and delays. |
a_redial_is_reported_and_is_not_a_retry | The events of a redial. |
Try it
cargo test -p jev-client
For the people who maintain it
In this folder
| File | What |
|---|---|
| tests.rs | The fakes, run, and the fifteen tests above. |
| CLAUDE.md | Invariants for agents. |
← Previous: Chapter 18½: inside jev-client/src · Up: jev-client/src · Next: Chapter 19: jev-worker →