feat(auth): implement auto-refresh loop for managing auth token schedule
- Introduced `authAutoRefreshLoop` to handle token refresh scheduling. - Replaced semaphore-based refresh logic in `Manager` with the new loop. - Added unit tests to verify refresh schedule logic and edge cases.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testRefreshEvaluator struct{}
|
||||
|
||||
func (testRefreshEvaluator) ShouldRefresh(time.Time, *Auth) bool { return false }
|
||||
|
||||
func setRefreshLeadFactory(t *testing.T, provider string, factory func() *time.Duration) {
|
||||
t.Helper()
|
||||
key := strings.ToLower(strings.TrimSpace(provider))
|
||||
refreshLeadMu.Lock()
|
||||
prev, hadPrev := refreshLeadFactories[key]
|
||||
if factory == nil {
|
||||
delete(refreshLeadFactories, key)
|
||||
} else {
|
||||
refreshLeadFactories[key] = factory
|
||||
}
|
||||
refreshLeadMu.Unlock()
|
||||
t.Cleanup(func() {
|
||||
refreshLeadMu.Lock()
|
||||
if hadPrev {
|
||||
refreshLeadFactories[key] = prev
|
||||
} else {
|
||||
delete(refreshLeadFactories, key)
|
||||
}
|
||||
refreshLeadMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
func TestNextRefreshCheckAt_DisabledUnschedule(t *testing.T) {
|
||||
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
|
||||
auth := &Auth{ID: "a1", Provider: "test", Disabled: true}
|
||||
if _, ok := nextRefreshCheckAt(now, auth, 15*time.Minute); ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRefreshCheckAt_APIKeyUnschedule(t *testing.T) {
|
||||
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
|
||||
auth := &Auth{ID: "a1", Provider: "test", Attributes: map[string]string{"api_key": "k"}}
|
||||
if _, ok := nextRefreshCheckAt(now, auth, 15*time.Minute); ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRefreshCheckAt_NextRefreshAfterGate(t *testing.T) {
|
||||
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
|
||||
nextAfter := now.Add(30 * time.Minute)
|
||||
auth := &Auth{
|
||||
ID: "a1",
|
||||
Provider: "test",
|
||||
NextRefreshAfter: nextAfter,
|
||||
Metadata: map[string]any{"email": "x@example.com"},
|
||||
}
|
||||
got, ok := nextRefreshCheckAt(now, auth, 15*time.Minute)
|
||||
if !ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = false, want true")
|
||||
}
|
||||
if !got.Equal(nextAfter) {
|
||||
t.Fatalf("nextRefreshCheckAt() = %s, want %s", got, nextAfter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRefreshCheckAt_PreferredInterval_PicksEarliestCandidate(t *testing.T) {
|
||||
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
|
||||
expiry := now.Add(20 * time.Minute)
|
||||
auth := &Auth{
|
||||
ID: "a1",
|
||||
Provider: "test",
|
||||
LastRefreshedAt: now,
|
||||
Metadata: map[string]any{
|
||||
"email": "x@example.com",
|
||||
"expires_at": expiry.Format(time.RFC3339),
|
||||
"refresh_interval_seconds": 900, // 15m
|
||||
},
|
||||
}
|
||||
got, ok := nextRefreshCheckAt(now, auth, 15*time.Minute)
|
||||
if !ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = false, want true")
|
||||
}
|
||||
want := expiry.Add(-15 * time.Minute)
|
||||
if !got.Equal(want) {
|
||||
t.Fatalf("nextRefreshCheckAt() = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRefreshCheckAt_ProviderLead_Expiry(t *testing.T) {
|
||||
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
|
||||
expiry := now.Add(time.Hour)
|
||||
lead := 10 * time.Minute
|
||||
setRefreshLeadFactory(t, "provider-lead-expiry", func() *time.Duration {
|
||||
d := lead
|
||||
return &d
|
||||
})
|
||||
|
||||
auth := &Auth{
|
||||
ID: "a1",
|
||||
Provider: "provider-lead-expiry",
|
||||
Metadata: map[string]any{
|
||||
"email": "x@example.com",
|
||||
"expires_at": expiry.Format(time.RFC3339),
|
||||
},
|
||||
}
|
||||
|
||||
got, ok := nextRefreshCheckAt(now, auth, 15*time.Minute)
|
||||
if !ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = false, want true")
|
||||
}
|
||||
want := expiry.Add(-lead)
|
||||
if !got.Equal(want) {
|
||||
t.Fatalf("nextRefreshCheckAt() = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRefreshCheckAt_RefreshEvaluatorFallback(t *testing.T) {
|
||||
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
|
||||
interval := 15 * time.Minute
|
||||
auth := &Auth{
|
||||
ID: "a1",
|
||||
Provider: "test",
|
||||
Metadata: map[string]any{"email": "x@example.com"},
|
||||
Runtime: testRefreshEvaluator{},
|
||||
}
|
||||
got, ok := nextRefreshCheckAt(now, auth, interval)
|
||||
if !ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = false, want true")
|
||||
}
|
||||
want := now.Add(interval)
|
||||
if !got.Equal(want) {
|
||||
t.Fatalf("nextRefreshCheckAt() = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user