Files
at-container-registry/pkg/auth/cache_test.go
T

235 lines
6.0 KiB
Go

package auth
import (
"testing"
"time"
)
func TestGetServiceToken_NotCached(t *testing.T) {
defaultCache.Clear()
did := "did:plc:test123"
holdDID := "did:web:hold.example.com"
token, expiresAt := GetServiceToken(did, holdDID)
if token != "" {
t.Errorf("Expected empty token for uncached entry, got %q", token)
}
if !expiresAt.IsZero() {
t.Error("Expected zero time for uncached entry")
}
}
func TestSetServiceToken_ManualExpiry(t *testing.T) {
defaultCache.Clear()
did := "did:plc:test123"
holdDID := "did:web:hold.example.com"
token := "invalid_jwt_token" // Will fall back to 50s default
// This should succeed with default 50s TTL since JWT parsing will fail
err := SetServiceToken(did, holdDID, token)
if err != nil {
t.Fatalf("SetServiceToken() error = %v", err)
}
// Verify token was cached
cachedToken, expiresAt := GetServiceToken(did, holdDID)
if cachedToken != token {
t.Errorf("Expected token %q, got %q", token, cachedToken)
}
if expiresAt.IsZero() {
t.Error("Expected non-zero expiry time")
}
// Expiry should be approximately 50s from now (with 10s margin subtracted in some cases)
expectedExpiry := time.Now().Add(50 * time.Second)
diff := expiresAt.Sub(expectedExpiry)
if diff < -5*time.Second || diff > 5*time.Second {
t.Errorf("Expiry time off by %v (expected ~50s from now)", diff)
}
}
func TestGetServiceToken_Expired(t *testing.T) {
// Manually insert an expired token by reaching into the cache. Set()
// would apply the safety margin which is the opposite of what we
// want here — we want a guaranteed-stale entry.
did := "did:plc:test123"
holdDID := "did:web:hold.example.com"
cacheKey := did + ":" + holdDID
defaultCache.mu.Lock()
defaultCache.tokens[cacheKey] = &serviceTokenEntry{
token: "expired_token",
expiresAt: time.Now().Add(-1 * time.Hour),
}
defaultCache.mu.Unlock()
// Try to get - should return empty since expired
token, expiresAt := GetServiceToken(did, holdDID)
if token != "" {
t.Errorf("Expected empty token for expired entry, got %q", token)
}
if !expiresAt.IsZero() {
t.Error("Expected zero time for expired entry")
}
// Verify token was removed from cache
defaultCache.mu.RLock()
_, exists := defaultCache.tokens[cacheKey]
defaultCache.mu.RUnlock()
if exists {
t.Error("Expected expired token to be removed from cache")
}
}
func TestInvalidateServiceToken(t *testing.T) {
defaultCache.Clear()
did := "did:plc:test123"
holdDID := "did:web:hold.example.com"
token := "test_token"
err := SetServiceToken(did, holdDID, token)
if err != nil {
t.Fatalf("SetServiceToken() error = %v", err)
}
cachedToken, _ := GetServiceToken(did, holdDID)
if cachedToken != token {
t.Fatal("Token should be cached")
}
InvalidateServiceToken(did, holdDID)
cachedToken, _ = GetServiceToken(did, holdDID)
if cachedToken != "" {
t.Error("Expected token to be invalidated")
}
}
func TestCleanExpiredTokens(t *testing.T) {
defaultCache.Clear()
defaultCache.mu.Lock()
defaultCache.tokens["expired:hold1"] = &serviceTokenEntry{
token: "expired1",
expiresAt: time.Now().Add(-1 * time.Hour),
}
defaultCache.tokens["valid:hold2"] = &serviceTokenEntry{
token: "valid1",
expiresAt: time.Now().Add(1 * time.Hour),
}
defaultCache.mu.Unlock()
CleanExpiredTokens()
defaultCache.mu.RLock()
_, expiredExists := defaultCache.tokens["expired:hold1"]
_, validExists := defaultCache.tokens["valid:hold2"]
defaultCache.mu.RUnlock()
if expiredExists {
t.Error("Expected expired token to be removed")
}
if !validExists {
t.Error("Expected valid token to remain")
}
}
func TestGetCacheStats(t *testing.T) {
defaultCache.Clear()
defaultCache.mu.Lock()
defaultCache.tokens["did1:hold1"] = &serviceTokenEntry{
token: "token1",
expiresAt: time.Now().Add(1 * time.Hour),
}
defaultCache.tokens["did2:hold2"] = &serviceTokenEntry{
token: "token2",
expiresAt: time.Now().Add(1 * time.Hour),
}
defaultCache.mu.Unlock()
stats := GetCacheStats()
if stats == nil {
t.Fatal("Expected non-nil stats")
}
totalEntries, ok := stats["total_entries"].(int)
if !ok {
t.Fatalf("Expected total_entries in stats map, got: %v", stats)
}
if totalEntries != 2 {
t.Errorf("Expected 2 entries, got %d", totalEntries)
}
validTokens, ok := stats["valid_tokens"].(int)
if !ok {
t.Fatal("Expected valid_tokens in stats map")
}
if validTokens != 2 {
t.Errorf("Expected 2 valid tokens, got %d", validTokens)
}
}
func TestCache_StructAPI_IsolatedInstances(t *testing.T) {
// A freshly constructed Cache must not share state with defaultCache —
// otherwise tests that expect isolation would silently fail.
c1 := NewCache()
c2 := NewCache()
did := "did:plc:alice"
holdDID := "did:web:hold.test"
c1.mu.Lock()
c1.tokens[did+":"+holdDID] = &serviceTokenEntry{
token: "tok-c1",
expiresAt: time.Now().Add(1 * time.Hour),
}
c1.mu.Unlock()
if tok, _ := c1.Get(did, holdDID); tok != "tok-c1" {
t.Errorf("c1.Get() = %q, want tok-c1", tok)
}
if tok, _ := c2.Get(did, holdDID); tok != "" {
t.Errorf("c2.Get() = %q, want empty (instances must not share state)", tok)
}
if tok, _ := GetServiceToken(did, holdDID); tok == "tok-c1" {
t.Error("defaultCache should not see writes to c1")
}
c1.Clear()
if tok, _ := c1.Get(did, holdDID); tok != "" {
t.Errorf("c1.Get() after Clear() = %q, want empty", tok)
}
}
func TestCache_PackageFunctionsDelegateToDefault(t *testing.T) {
// The package-level wrappers must route to defaultCache.
defaultCache.Clear()
did := "did:plc:bob"
holdDID := "did:web:hold.test"
if err := SetServiceToken(did, holdDID, "wrapper-tok"); err != nil {
t.Fatalf("SetServiceToken: %v", err)
}
tok, exp := DefaultCache().Get(did, holdDID)
if tok != "wrapper-tok" {
t.Errorf("DefaultCache().Get() = %q, want wrapper-tok", tok)
}
if exp.IsZero() {
t.Error("DefaultCache().Get() expiry is zero")
}
InvalidateServiceToken(did, holdDID)
if tok, _ := DefaultCache().Get(did, holdDID); tok != "" {
t.Errorf("after InvalidateServiceToken, DefaultCache().Get() = %q, want empty", tok)
}
}