test(realtime): record and replay websocket traffic in redis vcr cassettes - #32390
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR adds a WebSocket-level VCR layer (
Confidence Score: 5/5Safe to merge; changes are entirely within the test tree with no production code touched. All changes live in tests/; no litellm source files are modified. The implementation is well-structured, end-to-end proof runs are provided in the PR description (cold and warm runs with frame-exact counts and a DNS-block network guard), and 17 unit tests cover the harness with fakeredis. The two minor observations do not affect current test correctness. No files require special attention.
|
| Filename | Overview |
|---|---|
| tests/_ws_vcr.py | New WebSocket VCR harness: record/replay sessions via Redis with causal gating, secret scrubbing, and save-on-pass semantics. Well-structured; no blocking issues found. |
| tests/llm_translation/realtime/conftest.py | New autouse fixture that patches websockets.connect per-test, loads/saves cassettes from Redis, and re-raises replay errors on pass. Correctly inherits pytest_runtest_makereport from the parent conftest. |
| tests/llm_translation/test_ws_vcr.py | 17 unit tests covering cassette round-trip, causal gate, volatile-tolerant matching, secret scrubbing, TTL-on-save, no-save-on-fail, and replay timeout. All use fakeredis with no live network calls. |
| tests/llm_translation/conftest.py | Adds test_ws_vcr.py to the VCR auto-marker skip list so the unit tests don't run under a live cassette context; otherwise unchanged. |
Reviews (4): Last reviewed commit: "fix(realtime): warn instead of silently ..." | Re-trigger Greptile
…is client cannot be built
|
the asyncio concern is moot since pyproject sets |
fixing this -> the reward is epsilon (symmetric normalization already neutralizes over-matching) while any tightening carries real replay-flake risk, e.g., all-letter ids
rewriting proven concurrency plumbing for an untriggerable case invites real regressions |
…ettes (BerriAI#32390) * test(realtime): record and replay websocket traffic in redis vcr cassettes * style(realtime): ruff-format ws-vcr harness * fix(realtime): warn instead of silently disabling ws-vcr when the redis client cannot be built
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
The
realtime_translation_testingCI job always hit the live provider realtime APIs because vcrpy only intercepts HTTP; every realtime test printed[VCR NOOP] played=0 entries=0. This adds a WebSocket layer to the existing Redis VCR so realtime sessions record once and then replay deterministically for 24h with zero provider traffic, and re-record live after the TTL lapses to catch provider driftBoth proof runs below were captured at commit
6a78485b41on branchlitellm_ws_vcr_realtime_cassettes, against the live OpenAI realtime API (real spend, no mocks), with a throwaway single-purpose redis forCASSETTE_REDIS_URL. xAI realtime tests skip because noXAI_API_KEYwas setCold run (empty cassette store, records live)
Warm run (same cassettes, replays), executed with a hard DNS block on
api.openai.com/api.x.aito prove zero provider traffic while the API key stays set so no test skipsEvery recorded frame count is reproduced exactly on replay (42, 1, 1, 1, 1, 39, 41), the run is faster, and the network guard confirms the provider hosts were never resolved. The residual wall time is dominated by fixed
asyncio.sleepcalls inside the existing test bodies, not provider round-tripsCassette keys and TTLs after the warm replay, showing the distinct
wscassetteprefix and that TTL is set on write and deliberately not refreshed on read, so each cassette lapses ~24h after its last recording and the next run past that point re-records liveThe guardrail
test_text_message_blocked_by_guardrail_no_ai_responseis a known live flake; it passed on this cold run and its cassette was saved, so the warm run replays it deterministically. When a test fails live it saves nothing (the save-on-pass gate), which keeps any prior cassette intact and re-records next time; that path plus the replay timeout, contract-drift, and scrub-on-save behavior are covered bytests/llm_translation/test_ws_vcr.pyThe harness's own 17 unit tests, run against fakeredis with no cassette context
Type
✅ Test
Changes
tests/_ws_vcr.pyintercepts thewebsockets.connectboundary, which both realtime paths funnel through (the guardrail tests callwebsockets.connectdirectly for the backend socket they hand toRealTimeStreaming, and the SDK path connects internally insideOpenAIRealtime.async_realtime), so nolitellm/source change is needed. A per-test autouse fixture intests/llm_translation/realtime/conftest.pypatcheswebsockets.connectfor the duration of each testOn a cache miss the wrapper passes through to the real server and logs every frame in order with its direction, a text/binary flag, and, for each server frame, the count of client frames seen before it. On a cache hit a fake connection serves the recorded server frames under that causal gate, releasing each only once the client has sent the recorded number of frames, and matches each client frame against the recording with volatile fields (event/item/response/session ids, timestamps) normalized away. A structurally different client frame raises contract drift with a diff, and every replay wait is bounded by a timeout so a mismatch surfaces loudly instead of hanging CI
Persistence reuses the existing cassette Redis client, 24h TTL, save-on-pass, best-effort degradation counters, and session-end banner from
tests/_vcr_redis_persister.py, under a distinctlitellm:vcr:wscassette:key keyed by the test node path. Auth material is never stored: connection headers are dropped and stored frame text is scrubbed of bearer tokens and API keys. The layer disables cleanly to live passthrough whenLITELLM_VCR_DISABLE=1orCASSETTE_REDIS_URLis unset.tests/llm_translation/test_ws_vcr.pyadds 17 unit tests covering cassette round-trip, the causal replay gate, volatile-tolerant matching that accepts id drift and rejects structural drift, secret scrubbing, TTL-on-save, no-save-on-fail, and replay timeout raising instead of hanging