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:

  • FakeTransport plays the script one Step per send: Reply { status, headers, body }, Fail(TransportErrorKind), or Stall, which never answers.
  • FakeRuntime keeps virtual time. sleep adds its duration to the clock and returns at once. timeout polls the future once: if it is ready it returns the output, and if not (a Stall) it adds the whole timeout to the clock and returns None. jitter is always 0, so every backoff is exact.
  • FakeObserver writes each Event as 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_everything checks the clock reads exactly 30 s and finishes instantly.

What the tests pin down

TestShows
answers_first_timeOne attempt, no time passes, the authorization header is marked sensitive.
honours_the_servers_delayA 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_idThree 503s: 0.5 s then 1 s of backoff, three attempts, the last request id kept.
other_4xx_fail_at_onceA 403 is not retried.
context_overflow_is_not_resentmax_tokens_exceeded comes back as ContextOverflow at once.
never_sent_is_redialled_not_retriedOne NotSent is resent with no backoff and no retry header.
repeated_never_sent_becomes_a_retryAfter two redials, a third NotSent is a retry.
unretryable_transport_failures_fail_at_onceTls, TooLarge and Config are not retried.
a_stalled_attempt_times_out_distrusts_the_connection_and_retriesA stall costs 10 s, the connection is distrusted, the retry answers.
the_budget_bounds_everythingThree stalls end in TimedOut at exactly 30 s.
transport_errors_keep_an_earlier_request_idAn id from an earlier 503 survives later transport failures.
a_wrong_answer_carries_its_request_idA 200 from another model is a Response error with its id.
a_success_reports_its_request_id_and_usageThe events of a clean answer.
a_retry_reports_its_cause_delay_and_request_idThe events of two retries, with causes and delays.
a_redial_is_reported_and_is_not_a_retryThe events of a redial.

Try it

cargo test -p jev-client

For the people who maintain it

In this folder

FileWhat
tests.rsThe fakes, run, and the fifteen tests above.
CLAUDE.mdInvariants for agents.

← Previous: Chapter 18½: inside jev-client/src · Up: jev-client/src · Next: Chapter 19: jev-worker →