Files
zero/internal/oauth/refresh_singleflight_test.go
KRATOS 9e800b4ffc fix five concurrency/leak bugs (swarm, specialist, oauth, mcp, cron) (#265)
Surfaced by a full-tree audit; each ships a regression test where the
behavior is deterministically testable.

- swarm: deliver a handoff's mailbox note BEFORE registering the new task,
  so a mailbox failure can't leave a phantom pending task in the coordinator.
- specialist: put a foreground sub-agent child in its own process group and
  group-kill on cancel/timeout (+ WaitDelay backstop), so a build/server it
  forked dies with it instead of orphaning.
- oauth: serialize on-demand token refresh per key (keyed mutex) and re-load
  inside the lock, so parallel callers don't each spend the single-use
  refresh token (all but one would otherwise fail at the token endpoint).
- mcp: treat a "source already gone" rename during legacy-token migration as
  success, so a concurrent migrator doesn't wrongly drop an OAuth MCP server.
- cron: claim a due job atomically (advance NextRunAt under the per-job lock)
  before running it, so two schedulers can't double-fire the same job.
2026-06-20 10:39:55 +05:30

48 lines
1.3 KiB
Go

package oauth
import (
"context"
"sync"
"testing"
"time"
)
func TestRefreshSingleflightsConcurrentCallers(t *testing.T) {
// The fake token endpoint counts refreshes; with a single-use refresh token,
// only ONE concurrent refresh must happen — the rest reuse the rotated token (M7).
fp := newFakeProvider(t, `{"access_token":"fresh-at","expires_in":3600}`)
env := map[string]string{
"ZERO_OAUTH_DEMO_CLIENT_ID": "client",
"ZERO_OAUTH_DEMO_TOKEN_URL": fp.server.URL + "/token",
}
m := managerFor(t, env, nil)
if err := m.store.Save(ProviderKey("demo"), Token{AccessToken: "stale", RefreshToken: "rt", ExpiresAt: time.Now().Add(-time.Hour)}); err != nil {
t.Fatalf("seed: %v", err)
}
const N = 10
var wg sync.WaitGroup
got := make([]string, N)
errs := make([]error, N)
for i := 0; i < N; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
got[i], errs[i] = m.GetFresh(context.Background(), ProviderKey("demo"))
}(i)
}
wg.Wait()
for i := range got {
if errs[i] != nil {
t.Fatalf("GetFresh[%d]: %v", i, errs[i])
}
if got[i] != "fresh-at" {
t.Errorf("GetFresh[%d] = %q, want fresh-at", i, got[i])
}
}
if hits := fp.tokenHits.Load(); hits != 1 {
t.Fatalf("expected exactly 1 refresh across %d concurrent callers, got %d", N, hits)
}
}