Skip to content

test: close deep-audit test-quality findings#65

Merged
lesnik512 merged 14 commits into
mainfrom
test/audit-test-quality
Jun 14, 2026
Merged

test: close deep-audit test-quality findings#65
lesnik512 merged 14 commits into
mainfrom
test/audit-test-quality

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

Closes the confirmed test-quality findings from the 2026-06-14 deep audit. Test-only — no src/ changes.

Finding Fix
M3 sync Client terminal had no exception-mapping/status-raising suite → added the sync mirrors of test_error_mapping_terminal
Nit13 httpx2.CookieConflict → TransportError mapping branch was untested → added (sync + async)
L11 no test enforced the "StatusError leaves don't override __init__" invariant → parametrized check over all 9 leaves
Nit8 ForbiddenError/ConflictError/UnprocessableEntityError never constructed → added to the per-status parametrize
Nit12 no sync mirror for status-before-decode / DecodeError-is-ClientError → added
Nit11 sync Client overload typing untested → mirrored the AsyncClient overload tests
L9 test_retry_props overclaimed "interleaving" → corrected to "sequential bounds" (concurrency is covered by test_threading_with_shared_budget)
L10 test_bulkhead_sync_props fixed time.sleep(0.005) was flaky → replaced with a threading.Barrier on actual slot acquisition
L12 TimeoutError tripping the CircuitBreaker was untested → added (sync + async, via httpx2.ReadTimeout)
Nit7 test_threading_with_shared_budget deposit count rested on a comment → pinned the injected clock so _purge provably can't fire
Nit10 the no-active-span observability test had no assertion → now asserts the log record via caplog

(Nit9 — large-attempt_index backoff test — already landed in #64.)

Test plan

  • just test — 656 passed, 100% coverage; just lint clean
  • No src/ files changed (git diff origin/main..HEAD -- src empty)
  • L10/L12/Nit7 stress-run 5× — deterministic, no flakiness

🤖 Generated with Claude Code

lesnik512 and others added 14 commits June 14, 2026 17:03
…apping

Add sync mirrors for every async case in test_error_mapping_terminal.py:
2xx pass-through, typed status subclasses, unknown 4xx/5xx fallbacks, 3xx
no-raise, timeout/connect/invalid-url/decoding-error transport mapping, and
closed-client TransportError. Uses Client(httpx2_client=httpx2.Client(...))
pattern per CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add async and sync tests asserting that httpx2.CookieConflict raised inside a
transport handler surfaces as TransportError (not NetworkError), covering the
mapping branch that was exercised only for InvalidURL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parametrize over all nine StatusError leaf subclasses and assert that none
defines __init__ in its own __dict__, enforcing the CLAUDE.md invariant that
leaf classes must not override StatusError.__init__.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ityError

Extend test_per_status_subclasses_construct parametrize from 6 to all 9 leaf
status subclasses, adding 403/ForbiddenError, 409/ConflictError, and
422/UnprocessableEntityError. Also smoke-tests __str__ on each.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s-ClientError

Add test_sync_status_error_raised_before_decoder_runs (4xx with response_model
raises StatusError, not DecodeError) and test_sync_decode_error_caught_by_client_error
(malformed body raises DecodeError which is-a ClientError) for the sync Client,
mirroring the existing async counterparts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add sync mirrors for all four AsyncClient overload tests: get/send ×
with/without response_model. Verifies at runtime that the sync Client's
overloads resolve httpx2.Response vs typed model correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… not interleaving)

The module docstring implied concurrent "retry interleaving" but every test
issues one sequential request per Hypothesis example. Rewrite the docstring
to accurately say it tests sequential retry-policy bounds and explicitly point
to test_threading_with_shared_budget.py for the concurrent case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on barrier

The old test used time.sleep(0.005) hoping all holder threads had acquired
their bulkhead slots before issuing the over-limit requests. On slow CI a
holder might not have acquired yet, causing a spurious pass of the extra
request and a false test outcome.

Replace the sleep with threading.Barrier(max_concurrent + 1, timeout=5.0).
_BarrierHandler.wait() is called from inside each holder's handler — meaning
the slot is already held — and the main thread joins the same barrier. The
main thread only proceeds past the barrier once every holder has acquired
its slot, making the synchronization fully deterministic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…async)

The circuit breaker catches both NetworkError and TimeoutError as counted
failures, but tests only covered NetworkError (via ConnectError). Add
test_timeout_error_counts_as_failure to both test_circuit_breaker.py (async)
and test_circuit_breaker_sync.py (sync): a handler raising httpx2.ReadTimeout
(maps to httpware TimeoutError) with failure_threshold=2 causes two such
requests to open the circuit, and the third raises CircuitOpenError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…forced

The deposit-count assertion relied on a comment saying the 60s TTL means no
_purge fires during the sub-second run. RetryBudget accepts an injectable
_now clock, so inject _fixed_clock (always returns 0.0). All deposit
timestamps are 0.0, the purge cutoff is 0.0 - 60.0 = -60.0, and the strict
"< cutoff" predicate is never true — making the no-purge guarantee a
provable invariant rather than a time-dependent assumption.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test previously passed even if _emit_event became a no-op because its
only assertion was "no exception raised." Strengthen it with caplog: capture
at WARNING level on the test logger and assert that exactly one record fires
with the correct level, message, and event attribute. This confirms the
log-only fallback path actually emitted (not silently swallowed) when OTel
is installed but no tracer is active.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ort shadow

httpware.TimeoutError deliberately shadows the Python builtin (noqa A001 at
the definition site). Tests importing it need noqa A004 at the import line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An auto-fix left uncommitted by the test-quality sweep; CI lint-ci (no
autofix) caught the committed typing.Callable import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit 5260f63 into main Jun 14, 2026
5 checks passed
@lesnik512 lesnik512 deleted the test/audit-test-quality branch June 14, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant