Files
alkhttp/tasks/client/review-001-client-config-and-cert-coverage.md
T
glm-5.3-flash edbda6605b refactor(client): owned RetryConfig + TLS/mTLS test coverage (HY-06, COV-02)
- HttpClientConfig.retry_policy: ExponentialBackoff (semver anchor to a
  reqwest-retry concrete type) replaced by retry: RetryConfig — an
  owned struct of plain scalars (max_retries, initial_backoff,
  max_retry_interval, defaults matching the previous backoff exactly);
  the ExponentialBackoff policy is built internally by the middleware
  stack; no reqwest_retry type is public anymore
- ClientCertConfig fields documented (none had docs)
- new tests/client_tls.rs: per-test rcgen private PKI + tokio-rustls
  HTTPS server; drives the real SharedHttpClient through
  HttpClientConfig file paths — CA-bundle success path, private-roots
  rejection (source-chain assertion: invalid peer certificate),
  mTLS end-to-end with client identity, mTLS rejection without
  identity, and reload-to-CA-bundle interplay
- dev-deps: rcgen 0.14, tokio-rustls 0.26, rustls 0.23 (aws_lc_rs),
  rustls-pki-types 1, uuid

Verified: cargo test (288 + 5 TLS), --all-features (359 + suites),
--no-default-features (288; pre-existing warnings only), clippy
--all-targets -D warnings (default + all-features), fmt --check,
cargo doc --no-deps.

Tasks: review-001-client-config-and-cert-coverage
2026-08-30 07:24:34 +00:00

115 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: review-001-client-config-and-cert-coverage
name: Client config API cleanup (HY-06) + mTLS/CA-bundle test coverage (COV-02)
status: completed
depends_on: []
scope: narrow
risk: low
impact: component
level: implementation
tags: [client, review-001, follow-up, coverage]
---
## Description
Two deferred client-host items, grouped (same file, `src/client/http_client.rs`):
- **HY-06**: `HttpClientConfig.retry_policy` exposes
`reqwest_retry::ExponentialBackoff` in the public API (`:116`) — a
semver anchor to an upstream concrete type and an awkward construction
surface. The post-remediation config (RetryGate, TotalRetryBudget,
attempt counts, backoff bounds, budget deadline) is exactly why this
should now be an owned config struct (e.g. `max_retries`,
`max_retry_interval`, `initial_backoff`, `max_total_retry_duration`)
from which the middleware stack builds the internal backoff. Breaks
the public type — fine pre-publish; do it once, now, while the
surface is small.
- **COV-02 residue**: no test constructs a client with a working CA
bundle or client cert — only the missing-file error paths are tested
(`new_with_missing_ca_bundle_errors`). Post-remediation the
redirect/retry gates hang off this client, so the TLS-config paths
should be exercised: a test server with a self-signed cert + client
presenting its own cert, happy path through the full middleware stack.
## Acceptance Criteria
- [x] `HttpClientConfig` no longer exposes `ExponentialBackoff`; owned field set covers what the remediation made configurable; module docs updated
- [x] Existing config-construction call sites migrated (adapters' test fixtures included)
- [x] TLS test: client built with a CA bundle connects to a private-roots server; client-cert path exercised end-to-end (COV-02's uncovered build paths)
- [x] Feature matrix green (default, `--all-features`, `--no-default-features`)
- [x] `cargo test` and `cargo clippy --all-targets -- -D warnings` pass
## References
- docs/reviews/001-initial-implementation-review.md (Part H, HY-06; Part I, COV-02)
- tasks/client/review-001-client-timeout-retry.md (the retry policy work this builds on)
## Notes
**HY-06 shape chosen**: `HttpClientConfig.retry_policy:
ExponentialBackoff``HttpClientConfig.retry: RetryConfig`, an owned
struct of plain scalars `{ max_retries: u32, initial_backoff: Duration,
max_retry_interval: Duration }` (debug-clone, `Default` =
3 retries / 100 ms / 2 s — the previous `ExponentialBackoff` default
exactly). The jitter (`Bounded`) and exponential base (`2`) stay
internal policy constants, not config — they were never surfaced
before either. `RetryGateMiddleware::new` builds the internal
`ExponentialBackoff` from the `RetryConfig` at client-construction
time; `max_total_retry_duration` remains its own `HttpClientConfig`
field (unchanged; it was already a plain scalar). `reqwest-retry`
stays a private implementation detail of the middleware stack — no
`reqwest_retry` type appears in the public API.
Call-site audit: `retry_policy` had **zero** external construction
sites — every `HttpClientConfig` consumer (from_jsonschema,
from_openapi, openapi_spec, forward.rs, full_surface test) uses
`HttpClientConfig::default()`; only `http_client.rs`'s own tests
constructed the field. Those were migrated to `RetryConfig { .. }`
literals, and the default-config assertions now pin `max_retries`,
`initial_backoff`, and `max_retry_interval` on the owned struct.
`ClientCertConfig` doc comments added (it had none — caught by the
`missing_docs` gate re-measure this task unblocks).
**COV-02 shape chosen**: new integration test file
`tests/client_tls.rs` (kept out of the lib's test module — it needs
`tokio-rustls`, `rustls`, `rcgen` as dev-deps only, so the base crate
and its feature matrix stay lean). Per test it mints a throwaway
private PKI with rcgen (CA + server leaf SAN `127.0.0.1`/`localhost` +
client leaf with ClientAuth EKU), serves HTTPS/1.1 via tokio-rustls
(`WebPkiClientVerifier` when mTLS is required), and drives the **real
`SharedHttpClient`** through `HttpClientConfig` file paths — same
encode path as production (PEM read → `add_root_certificate` /
`Identity::from_pem` via `concat_pem`). Five tests:
- CA-bundle client ↔ private-roots server: 200 + body intact + exactly
1 handshake (success path through the full middleware stack);
- no-CA-bundle client rejected by private-roots server — asserts the
TLS verification failure surfaces in the error **source chain**
(`invalid peer certificate: UnknownIssuer`; the retry middleware
wraps it, so the chain is walked, not the top-level Display);
- mTLS: client presents identity → handshake completes end-to-end;
- mTLS server rejects a client with no identity (TLS-level rejection);
- `reload()` to a CA-bundle-backed client makes a previously
unreachable server trusted (hot-reload × TLS interplay).
The negative-path assertions mirror the retry-stack reality: the first
attempt fails at the TLS layer and 3 retries surface a
`Middleware(...)` wrapper — the source chain carries the answer.
## Summary
- `src/client/http_client.rs`: `RetryConfig` (new public struct)
replaces the `ExponentialBackoff` exposure; retry backoff built
internally; `ClientCertConfig` fields documented; tests migrated
(minimal_config, reload-swap, default assertions).
- `tests/client_tls.rs` (new): private-PKI test server + 5 TLS tests
(2 success-path TLS builds, 1 reload×TLS, 2 negative
verification/mTLS rejections with chain-aware assertions).
- `Cargo.toml`: dev-deps `rcgen 0.14`, `tokio-rustls 0.26`,
`rustls 0.23` (no default features; aws_lc_rs + std + tls12),
`rustls-pki-types 1`, `uuid` (v4 already a main dep).
- Verified: `cargo test` (288 + 5 TLS), `cargo test --all-features`
(359 + all suites), `cargo test --no-default-features` (288; same 4
pre-existing lib warnings as the base commit, nothing new),
`cargo clippy --all-targets -- -D warnings` (default +
all-features), `cargo fmt --check`, `cargo doc --no-deps` clean.