Files
at-container-registry/pkg/auth/token/handler_concurrency_test.go
2026-05-16 12:18:06 -05:00

64 lines
2.2 KiB
Go

package token
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
// TestHandler_GateAndFetchRunConcurrently asserts that the authorizer and
// service-auth fetcher run as parallel goroutines rather than sequentially.
//
// Both stubs share a cross-wired barrier: each closes its own `started`
// channel and then waits on the other's. If the handler runs them
// sequentially, the first stub blocks forever waiting for the second — its
// barrier-timeout fires and the request fails with a 403. If they run in
// parallel, both goroutines reach the barrier and release each other.
//
// This is deterministic — no wall-clock budget — so it works regardless of
// runner speed or the race detector's overhead.
func TestHandler_GateAndFetchRunConcurrently(t *testing.T) {
keyPath := getSharedTestKey(t)
issuer, err := NewIssuer(keyPath, "atcr.io", "registry", 5*time.Minute)
if err != nil {
t.Fatalf("NewIssuer() error = %v", err)
}
deviceStore, database := setupTestDeviceStore(t)
deviceSecret := createTestDevice(t, deviceStore, database, "did:plc:alice123", "alice.bsky.social")
gateStarted := make(chan struct{})
fetchStarted := make(chan struct{})
const barrierTimeout = 2 * time.Second
handler := NewHandler(issuer, deviceStore)
handler.SetAuthorizer(&stubAuthorizer{
started: gateStarted,
partnerStarted: fetchStarted,
barrierTimeout: barrierTimeout,
})
handler.SetServiceAuthFetcher(&stubServiceAuthFetcher{
expiresAt: time.Now().Add(4 * time.Minute),
started: fetchStarted,
partnerStarted: gateStarted,
barrierTimeout: barrierTimeout,
})
req := httptest.NewRequest(http.MethodGet, "/auth/token?service=registry&scope=repository:alice.bsky.social/myapp:pull,push", nil)
req.SetBasicAuth("alice", deviceSecret)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
// A barrier-timeout from sequential execution surfaces as a 403 with
// the "partner goroutine did not start" message in the body.
if strings.Contains(w.Body.String(), "sequential execution") {
t.Fatalf("gate and fetch ran sequentially: %s", w.Body.String())
}
t.Fatalf("expected 200, got %d. Body: %s", w.Code, w.Body.String())
}
}