test(remote): cover single-flight follower models and edges (S13 Step 8, part 2/2)
Split into 2 stacked MRs to keep each part within a reviewable size (~600 LoC ideal; both parts land just under the 1000 hard cap): each part targets the previous one (part 1 targets main), and the stack is reviewed and merged bottom-up.
Part 2 of 2 of S13 virtual and remote foundation — Step 8: single-flight coalescing.
🔬 What this part delivers
No production changes — this part completes the single-flight test suite over part 1's implementation (the remaining 20 test functions of internal/remote/singleflight_test.go): the committed-read follower model, leader-failure propagation to both follower models, shared non-stream outcomes, timeout/cancellation disambiguation, the per-repo fill-cap semantics, per-repository scoping, flight lifecycle, and constructor validation.
📋 Spec coverage
| Spec item (S13 "Single-flight on Fetch" + AC) | Tests |
|---|---|
| AC "Single-flight coalescing": N identical Fetches → one GET, one cache write, all N stream | TestSingleFlight_Fetch_CollapsesToOneUpstreamGET (part 1) |
| Distinct paths fill concurrently | TestSingleFlight_Fetch_DistinctPathsFillConcurrently (part 1) |
| Coalescing keyed per remote repository | TestSingleFlight_Fetch_DistinctRepositoriesDoNotCollapse |
| Transformed-fetch followers read the committed document | TestSingleFlight_Fetch_TransformedFollowersReadCommitted |
| Post-first-byte joiner reads the committed document | TestSingleFlight_Fetch_LateRawJoinerReadsCommitted |
| Leader failure propagates immediately — live followers | TestSingleFlight_Fetch_MidStreamFailurePropagatesToLiveFollowers, TestSingleFlight_Fetch_LeaderTransportErrorSharedImmediately, TestSingleFlight_Fetch_CommitFailurePropagatesErrCacheFill |
| Leader failure propagates immediately — committed-read followers | TestSingleFlight_Fetch_MidStreamFailureFailsCommittedReadFollower, TestSingleFlight_Fetch_AbandonedLeaderFailsFollowers |
single_flight_wait_timeout follower bound |
TestSingleFlight_Fetch_FollowerWaitTimeout, TestSingleFlight_Fetch_FollowerContextCancelIsNotTimeout |
max_concurrent_fills_per_repo cap |
TestSingleFlight_Fetch_FillCapBlocksNewLeaderUntilSlotFrees, TestSingleFlight_Fetch_FillCapWaitTimesOut, TestSingleFlight_Fetch_FillCapCancelIsNotTimeout, TestSingleFlight_Fetch_FollowersNeverConsumeFillSlots |
| Shared 304/404 outcomes, one upstream call, one freshness bump | TestSingleFlight_Fetch_SharedNonStreamOutcomes |
Committed-read failure classification (ErrCacheFill) |
TestSingleFlight_Fetch_CommittedReadFailuresWrapErrCacheFill |
| Follower cancellation detaches only that follower | TestSingleFlight_Fetch_LiveFollowerCancelDetachesOnlyThatFollower |
| Flight lifecycle (fresh flight after completion; empty body) | TestSingleFlight_Fetch_CompletedFlightDoesNotAbsorbLaterFetches, TestSingleFlight_Fetch_EmptyBodyServesLiveFollowersCleanly |
| Constructor fail-loud guards | TestNewSingleFlight_PanicsOnNonPositiveKnobs |
All tests run under -race; the collapse and fan-out tests are deterministic (the join point is observable at Fetch return, plus the SetBeforeFollowerWait hook for parked followers) rather than settle-based.
📖 References
Related to #329 (closed)