mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-29 20:27:16 +00:00
235 lines
6.0 KiB
Go
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)
|
|
}
|
|
}
|