Repository navigation
test(scheduledauth): make rebuild-rate-limit tests deterministic via injectable clock - #1399
Conversation
…pointer assertions TestVerifyRetry_RebuildRateLimited and TestVerifyRetry_RotationRecoversAfterInterval intermittently failed in CI because they asserted exact JWKS-fetch counts (hits==3 and hits==4). Those counts depend on goroutine scheduling inside go-oidc v3's keysFromRemote: it calls inflight.done() before re-acquiring its mutex to update r.cachedKeys and clear r.inflight. A Verify call that arrives in that narrow window joins the still-live (already-done) inflight without issuing an HTTP GET, shifting all subsequent fetch ordinals and causing the exact-count assertions to flake. Fix: - TestVerifyRetry_RebuildRateLimited: replace hits==3 with a v.verifier pointer comparison under verMu. After the first bad token the pointer must change (rebuild fired); after the second bad token within minRebuildInterval the pointer must be identical (rate limiter suppressed the rebuild). This assertion is 100% deterministic regardless of how many HTTP GETs go-oidc actually issued. - TestVerifyRetry_RotationRecoversAfterInterval: replace the n>=4 fetch-count gate (which assumed exactly 3 prior fetches) with an atomic.Bool phase flag set between the two Validate calls. Every fetch during the second call now reliably receives keyB, whether go-oidc's own kid-miss refresh or the validator's rebuild retry fires first. Assert the behavioral outcome (second Validate returns nil) rather than the internal recovery path. The production clock injection (v.now func() time.Time) introduced by PR #1386 was already correct. No changes to validator.go. Verified: go test -race -count=20 and -run TestVerifyRetry -count=50 both exit 0.
|
@coderabbitai review |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 9 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
✅ Action performedReview finished.
|
|
Merged to main (CLEAN + all CI green). Removes the intermittent TestVerifyRetry Unit Tests flake queue-wide: root cause was a go-oidc goroutine race in the test's fetch-count assertion (production rebuild-rate-limit clock was already injectable/correct). Replaced fetch-count asserts with a verifier-pointer comparison + atomic phase flag; verified -race -count=20 and -count=50. |
Root cause
TestVerifyRetry_RebuildRateLimitedandTestVerifyRetry_RotationRecoversAfterIntervalasserted exact JWKS-fetch counts (hits==3andhits==4). Those counts depend on goroutine scheduling inside go-oidc v3, not the validator itself.go-oidc v3's
keysFromRemotestarts a goroutine that callsinflight.done()(closing the channel, signalling callers) before it re-acquires its mutex to writer.cachedKeysand clearr.inflight = nil. If the nextVerifycall arrives in that narrow window it joins the still-live (already-done) inflight without issuing an HTTP GET. This shifts all subsequent fetch ordinals and makes anyhits == Nassertion non-deterministic (typically 2 instead of 3, or 3 instead of 4), causing a rare but real CI failure.The production clock injection (
v.now func() time.Time, set totime.Nowin the constructor and overridden in tests) introduced by PR #1386 was already correct. No changes tovalidator.go.Fix
TestVerifyRetry_RebuildRateLimited: replacehits==3with av.verifierpointer comparison read underv.verMu.RLock():lastRebuildwas zero so rate-limit permitted it).t0suppressed the rebuild).This is 100% deterministic; it directly asserts what the rate-limiter controls.
TestVerifyRetry_RotationRecoversAfterInterval: replace then>=4fetch-count gate (which assumed exactly 3 prior HTTP GETs) with anatomic.Boolphase flag set between the twoValidatecalls. Every fetch during the second call now reliably receives keyB, regardless of whether go-oidc's own kid-miss refresh or the validator's rebuild retry fires first. The test asserts the behavioral outcome (secondValidatereturnsnil) rather than the internal recovery path, which is the correct invariant.DoS semantics unchanged
minRebuildInterval.v.now) still drives both checks deterministically in tests and in production usestime.Now.Verification