forked from
evan.jarrett.net/at-container-registry
A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
1package auth
2
3import (
4 "testing"
5 "time"
6)
7
8func TestGetServiceToken_NotCached(t *testing.T) {
9 // Clear cache first
10 globalServiceTokensMu.Lock()
11 globalServiceTokens = make(map[string]*serviceTokenEntry)
12 globalServiceTokensMu.Unlock()
13
14 did := "did:plc:test123"
15 holdDID := "did:web:hold.example.com"
16
17 token, expiresAt := GetServiceToken(did, holdDID)
18 if token != "" {
19 t.Errorf("Expected empty token for uncached entry, got %q", token)
20 }
21 if !expiresAt.IsZero() {
22 t.Error("Expected zero time for uncached entry")
23 }
24}
25
26func TestSetServiceToken_ManualExpiry(t *testing.T) {
27 // Clear cache first
28 globalServiceTokensMu.Lock()
29 globalServiceTokens = make(map[string]*serviceTokenEntry)
30 globalServiceTokensMu.Unlock()
31
32 did := "did:plc:test123"
33 holdDID := "did:web:hold.example.com"
34 token := "invalid_jwt_token" // Will fall back to 50s default
35
36 // This should succeed with default 50s TTL since JWT parsing will fail
37 err := SetServiceToken(did, holdDID, token)
38 if err != nil {
39 t.Fatalf("SetServiceToken() error = %v", err)
40 }
41
42 // Verify token was cached
43 cachedToken, expiresAt := GetServiceToken(did, holdDID)
44 if cachedToken != token {
45 t.Errorf("Expected token %q, got %q", token, cachedToken)
46 }
47 if expiresAt.IsZero() {
48 t.Error("Expected non-zero expiry time")
49 }
50
51 // Expiry should be approximately 50s from now (with 10s margin subtracted in some cases)
52 expectedExpiry := time.Now().Add(50 * time.Second)
53 diff := expiresAt.Sub(expectedExpiry)
54 if diff < -5*time.Second || diff > 5*time.Second {
55 t.Errorf("Expiry time off by %v (expected ~50s from now)", diff)
56 }
57}
58
59func TestGetServiceToken_Expired(t *testing.T) {
60 // Manually insert an expired token
61 did := "did:plc:test123"
62 holdDID := "did:web:hold.example.com"
63 cacheKey := did + ":" + holdDID
64
65 globalServiceTokensMu.Lock()
66 globalServiceTokens[cacheKey] = &serviceTokenEntry{
67 token: "expired_token",
68 expiresAt: time.Now().Add(-1 * time.Hour), // 1 hour ago
69 }
70 globalServiceTokensMu.Unlock()
71
72 // Try to get - should return empty since expired
73 token, expiresAt := GetServiceToken(did, holdDID)
74 if token != "" {
75 t.Errorf("Expected empty token for expired entry, got %q", token)
76 }
77 if !expiresAt.IsZero() {
78 t.Error("Expected zero time for expired entry")
79 }
80
81 // Verify token was removed from cache
82 globalServiceTokensMu.RLock()
83 _, exists := globalServiceTokens[cacheKey]
84 globalServiceTokensMu.RUnlock()
85
86 if exists {
87 t.Error("Expected expired token to be removed from cache")
88 }
89}
90
91func TestInvalidateServiceToken(t *testing.T) {
92 // Set a token
93 did := "did:plc:test123"
94 holdDID := "did:web:hold.example.com"
95 token := "test_token"
96
97 err := SetServiceToken(did, holdDID, token)
98 if err != nil {
99 t.Fatalf("SetServiceToken() error = %v", err)
100 }
101
102 // Verify it's cached
103 cachedToken, _ := GetServiceToken(did, holdDID)
104 if cachedToken != token {
105 t.Fatal("Token should be cached")
106 }
107
108 // Invalidate
109 InvalidateServiceToken(did, holdDID)
110
111 // Verify it's gone
112 cachedToken, _ = GetServiceToken(did, holdDID)
113 if cachedToken != "" {
114 t.Error("Expected token to be invalidated")
115 }
116}
117
118func TestCleanExpiredTokens(t *testing.T) {
119 // Clear cache first
120 globalServiceTokensMu.Lock()
121 globalServiceTokens = make(map[string]*serviceTokenEntry)
122 globalServiceTokensMu.Unlock()
123
124 // Add expired and valid tokens
125 globalServiceTokensMu.Lock()
126 globalServiceTokens["expired:hold1"] = &serviceTokenEntry{
127 token: "expired1",
128 expiresAt: time.Now().Add(-1 * time.Hour),
129 }
130 globalServiceTokens["valid:hold2"] = &serviceTokenEntry{
131 token: "valid1",
132 expiresAt: time.Now().Add(1 * time.Hour),
133 }
134 globalServiceTokensMu.Unlock()
135
136 // Clean expired
137 CleanExpiredTokens()
138
139 // Verify only valid token remains
140 globalServiceTokensMu.RLock()
141 _, expiredExists := globalServiceTokens["expired:hold1"]
142 _, validExists := globalServiceTokens["valid:hold2"]
143 globalServiceTokensMu.RUnlock()
144
145 if expiredExists {
146 t.Error("Expected expired token to be removed")
147 }
148 if !validExists {
149 t.Error("Expected valid token to remain")
150 }
151}
152
153func TestGetCacheStats(t *testing.T) {
154 // Clear cache first
155 globalServiceTokensMu.Lock()
156 globalServiceTokens = make(map[string]*serviceTokenEntry)
157 globalServiceTokensMu.Unlock()
158
159 // Add some tokens
160 globalServiceTokensMu.Lock()
161 globalServiceTokens["did1:hold1"] = &serviceTokenEntry{
162 token: "token1",
163 expiresAt: time.Now().Add(1 * time.Hour),
164 }
165 globalServiceTokens["did2:hold2"] = &serviceTokenEntry{
166 token: "token2",
167 expiresAt: time.Now().Add(1 * time.Hour),
168 }
169 globalServiceTokensMu.Unlock()
170
171 stats := GetCacheStats()
172 if stats == nil {
173 t.Fatal("Expected non-nil stats")
174 }
175
176 // GetCacheStats returns map[string]any with "total_entries" key
177 totalEntries, ok := stats["total_entries"].(int)
178 if !ok {
179 t.Fatalf("Expected total_entries in stats map, got: %v", stats)
180 }
181
182 if totalEntries != 2 {
183 t.Errorf("Expected 2 entries, got %d", totalEntries)
184 }
185
186 // Also check valid_tokens
187 validTokens, ok := stats["valid_tokens"].(int)
188 if !ok {
189 t.Fatal("Expected valid_tokens in stats map")
190 }
191
192 if validTokens != 2 {
193 t.Errorf("Expected 2 valid tokens, got %d", validTokens)
194 }
195}