test(auth): e2e failover doctrine harness - #211
Conversation
Validates P1 failover scenarios against the real auth conductor using scripted fake upstreams. Documents current main defects and links the open PRs that address them.
warelik
left a comment
There was a problem hiding this comment.
Review: changes requested
The harness itself is solid: every referenced symbol matches current main (NewManager, SetRetryConfig, SessionAffinitySelector, SetTransientErrorCooldownSeconds, the global registry, internalconfig Gemini types), the fake satisfies ProviderExecutor, and I traced each active scenario through the conductor:
TestSingleTransient503Cooldown: 503 ->applyAuthFailureStatesetsUnavailable+NextRetryAfter = now + transientErrorCooldown(60 s, conductor_cooldown.go:2017-2020);maxWait=0short-circuitsshouldRetryAfterError, so no minute-long sleep. OK.- Empty-completion rotation (both variants):
isEmptyCompletionPayloadon the non-stream path (conductor_execution.go:462) andisTerminalEmptyon the stream bootstrap path both exist on main and rotate as scripted. OK. TestAllButOneDeadStillServesandTestAffinityStaysHealthyAfterTransientBlip: rotation and session-stickiness paths check out; the global cooldown override is restored; no interaction with the package parallel tests (they resume after sequential tests finish). OK.
But two of the three skipped doctrine tests are scripted in ways that cannot pass once their fix PRs (#195, #208) merge and the skips are removed, which undercuts the stated purpose of the suite. Details inline; both fixes are small (per-model error keying in the fake; flip one assertion).
Non-blocking nit: the affinity test restores the global with a hardcoded SetTransientErrorCooldownSeconds(0); the package convention (cooldown_backoff_test.go, connection_lifecycle_cooldown_test.go) saves and restores the previous value via t.Cleanup, which stays correct if the default ever changes.
After Plus kaitranntt#195 the conductor rotates within the same ExecuteStream call when a provider error envelope is detected in the bootstrap, so the test should assert err == nil and fallback content. Keep the cooldown assertions on the first-picked auth and sort the auth IDs so the error auth is deterministic.
Use failFirstN so the first resolved sibling model returns 429 and subsequent siblings return the configured payload, instead of setting both an error and a payload on the same auth.
Saves the previous transient-error cooldown and restores it with t.Cleanup so the test does not hard-code the legacy default.
warelik
left a comment
There was a problem hiding this comment.
Both skipped-test scripting issues are fixed:
-
TestAliasedAccountDiscoveredWhenSiblingsDienow usesexec.failFirstN = 1andexec.executePayloads[auth.ID].doctrineExecutor.Executewas reordered so the first sibling call fails with 429 and the second returns the configured payload (commit 2bd76fa). -
TestInStreamProviderErrorDuringBootstrapnow assertserr == niland reads fallback content fromstream.Chunks;idsare sorted so the error auth is the deterministic first pick (commit cee2b13).
Non-blocking nit: TestAffinityStaysHealthyAfterTransientBlip now saves the previous transient cooldown and restores it via t.Cleanup (commit 7f282be).
go test -count=1 ./... passes.
warelik
left a comment
There was a problem hiding this comment.
Review: approved
Re-review of 7f282be after the previous round. All three findings are fixed correctly:
-
In-stream bootstrap test (#195): the assertion is now consistent with in-call rotation — success + fallback content + cooled first auth + fallback tried. The new
sort.Strings(ids)determinism claim checks out:availableAuthsFromPriorityBucketssorts candidates by ID (selector.go:349-351), and a fresh RoundRobin cursor starts at index 0, soids[0]is provably the first pick. -
Aliased sibling test (#208):
failFirstN = 1+ reordered payload check in the fake means the first sibling attempt fails with a bare 429 and the second sibling returns the candidates payload. Traced throughexecuteMixedOnce: the 429 is not credential-scoped, so the models loop continues to the second sibling within the same auth — both models are recorded,models[0] != models[1]. The fake reorder (payloads now belowfirstExecuteEmpty/failFirstN) does not regress the other scenarios: no other test setsexecutePayloads, andexecuteErrsstill wins first for the 503/Retry-After scenarios. -
Affinity test: save/load/restore of
transientErrorCooldownSecondsviat.Cleanupnow matches the package convention.
The active (non-skipped) scenarios were re-traced against current main and still hold: 503 -> 60 s transient cooldown with Unavailable set; empty-completion rotation in both modes; last-live-auth serving; affinity stickiness with the 1 s cooldown override.
Summary
Adds
sdk/cliproxy/auth/e2e_failover_doctrine_test.go, a scripted local integration harness that drives the realManager/scheduler/conductorstack through the P1 failover doctrine scenarios.Scenarios covered:
Retry-After429 escalation/rotation (t.SKIP - fix(auth): floor quota cooldown at the escalating ladder #198)Skipped tests document the current
mainviolations and are linked to the open PRs that fix them, so the suite remains green while exposing the gaps.Full report:
/Users/warelik/Developer/AIRouters/reports/e2e-failover.mdTest plan
go test -count=1 ./sdk/cliproxy/authpassesgo build -o /tmp/cli-proxy-api ./cmd/serversucceeds