Vibe-guided bskyoauth and custom repo example code in Golang 馃 probably not safe to use in prod
1package bskyoauth
2
3import (
4 "crypto/ecdsa"
5 "crypto/elliptic"
6 "crypto/rand"
7 "sync"
8 "testing"
9 "time"
10)
11
12// TestGenerateSessionID verifies that session IDs are generated correctly.
13func TestGenerateSessionID(t *testing.T) {
14 id := GenerateSessionID()
15
16 if len(id) != 32 {
17 t.Errorf("Expected session ID length of 32, got %d", len(id))
18 }
19
20 // Session ID should only contain base64 URL-safe characters
21 for _, c := range id {
22 if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_') {
23 t.Errorf("Session ID contains invalid character: %c", c)
24 }
25 }
26}
27
28// TestGenerateSessionIDUniqueness verifies that generated session IDs are unique.
29func TestGenerateSessionIDUniqueness(t *testing.T) {
30 ids := make(map[string]bool)
31 iterations := 1000
32
33 for i := 0; i < iterations; i++ {
34 id := GenerateSessionID()
35 if ids[id] {
36 t.Errorf("Generated duplicate session ID: %s", id)
37 }
38 ids[id] = true
39 }
40
41 if len(ids) != iterations {
42 t.Errorf("Expected %d unique IDs, got %d", iterations, len(ids))
43 }
44}
45
46// TestNewMemorySessionStore verifies proper initialization of MemorySessionStore.
47func TestNewMemorySessionStore(t *testing.T) {
48 store := NewMemorySessionStore()
49
50 if store == nil {
51 t.Fatal("NewMemorySessionStore returned nil")
52 }
53
54 // Test that we can use the store
55 _, err := store.Get("nonexistent")
56 if err != ErrSessionNotFound {
57 t.Error("Expected ErrSessionNotFound for nonexistent session")
58 }
59}
60
61// TestMemorySessionStoreSetAndGet verifies basic Set and Get operations.
62func TestMemorySessionStoreSetAndGet(t *testing.T) {
63 store := NewMemorySessionStore()
64 sessionID := "test-session-123"
65
66 // Create a test session
67 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
68 session := &Session{
69 DID: "did:plc:test123",
70 AccessToken: "test-access-token",
71 RefreshToken: "test-refresh-token",
72 DPoPKey: key,
73 PDS: "https://test.pds.com",
74 DPoPNonce: "test-nonce",
75 }
76
77 // Set the session
78 err := store.Set(sessionID, session)
79 if err != nil {
80 t.Fatalf("Set failed: %v", err)
81 }
82
83 // Get the session
84 retrieved, err := store.Get(sessionID)
85 if err != nil {
86 t.Fatalf("Get failed: %v", err)
87 }
88
89 if retrieved == nil {
90 t.Fatal("Retrieved session is nil")
91 }
92
93 // Verify all fields
94 if retrieved.DID != session.DID {
95 t.Errorf("DID mismatch: expected %s, got %s", session.DID, retrieved.DID)
96 }
97
98 if retrieved.AccessToken != session.AccessToken {
99 t.Errorf("AccessToken mismatch: expected %s, got %s", session.AccessToken, retrieved.AccessToken)
100 }
101
102 if retrieved.RefreshToken != session.RefreshToken {
103 t.Errorf("RefreshToken mismatch: expected %s, got %s", session.RefreshToken, retrieved.RefreshToken)
104 }
105
106 if retrieved.PDS != session.PDS {
107 t.Errorf("PDS mismatch: expected %s, got %s", session.PDS, retrieved.PDS)
108 }
109
110 if retrieved.DPoPNonce != session.DPoPNonce {
111 t.Errorf("DPoPNonce mismatch: expected %s, got %s", session.DPoPNonce, retrieved.DPoPNonce)
112 }
113
114 if retrieved.DPoPKey != session.DPoPKey {
115 t.Error("DPoPKey mismatch")
116 }
117}
118
119// TestMemorySessionStoreGetNotFound verifies error handling for non-existent sessions.
120func TestMemorySessionStoreGetNotFound(t *testing.T) {
121 store := NewMemorySessionStore()
122
123 _, err := store.Get("non-existent-session")
124 if err != ErrSessionNotFound {
125 t.Errorf("Expected ErrSessionNotFound, got %v", err)
126 }
127}
128
129// TestMemorySessionStoreDelete verifies session deletion.
130func TestMemorySessionStoreDelete(t *testing.T) {
131 store := NewMemorySessionStore()
132 sessionID := "test-session-delete"
133
134 // Create and set a session
135 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
136 session := &Session{
137 DID: "did:plc:test123",
138 AccessToken: "test-token",
139 DPoPKey: key,
140 }
141
142 err := store.Set(sessionID, session)
143 if err != nil {
144 t.Fatalf("Set failed: %v", err)
145 }
146
147 // Verify it exists
148 _, err = store.Get(sessionID)
149 if err != nil {
150 t.Fatalf("Get failed before delete: %v", err)
151 }
152
153 // Delete the session
154 err = store.Delete(sessionID)
155 if err != nil {
156 t.Fatalf("Delete failed: %v", err)
157 }
158
159 // Verify it no longer exists
160 _, err = store.Get(sessionID)
161 if err != ErrSessionNotFound {
162 t.Errorf("Expected ErrSessionNotFound after delete, got %v", err)
163 }
164}
165
166// TestMemorySessionStoreDeleteNonExistent verifies deleting non-existent session doesn't error.
167func TestMemorySessionStoreDeleteNonExistent(t *testing.T) {
168 store := NewMemorySessionStore()
169
170 // Delete should not return an error even if session doesn't exist
171 err := store.Delete("non-existent-session")
172 if err != nil {
173 t.Errorf("Delete of non-existent session returned error: %v", err)
174 }
175}
176
177// TestMemorySessionStoreUpdate verifies that sessions can be updated.
178func TestMemorySessionStoreUpdate(t *testing.T) {
179 store := NewMemorySessionStore()
180 sessionID := "test-session-update"
181
182 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
183
184 // Create initial session
185 session1 := &Session{
186 DID: "did:plc:test123",
187 AccessToken: "token-v1",
188 DPoPKey: key,
189 DPoPNonce: "nonce-v1",
190 }
191
192 err := store.Set(sessionID, session1)
193 if err != nil {
194 t.Fatalf("Initial Set failed: %v", err)
195 }
196
197 // Update with new session data
198 session2 := &Session{
199 DID: "did:plc:test123",
200 AccessToken: "token-v2",
201 DPoPKey: key,
202 DPoPNonce: "nonce-v2",
203 }
204
205 err = store.Set(sessionID, session2)
206 if err != nil {
207 t.Fatalf("Update Set failed: %v", err)
208 }
209
210 // Verify updated values
211 retrieved, err := store.Get(sessionID)
212 if err != nil {
213 t.Fatalf("Get after update failed: %v", err)
214 }
215
216 if retrieved.AccessToken != "token-v2" {
217 t.Errorf("Expected updated AccessToken 'token-v2', got %s", retrieved.AccessToken)
218 }
219
220 if retrieved.DPoPNonce != "nonce-v2" {
221 t.Errorf("Expected updated DPoPNonce 'nonce-v2', got %s", retrieved.DPoPNonce)
222 }
223}
224
225// TestMemorySessionStoreMultipleSessions verifies storing multiple sessions.
226func TestMemorySessionStoreMultipleSessions(t *testing.T) {
227 store := NewMemorySessionStore()
228
229 // Create and store multiple sessions
230 sessionCount := 10
231 sessionIDs := make([]string, sessionCount)
232 for i := 0; i < sessionCount; i++ {
233 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
234 sessionID := GenerateSessionID()
235 sessionIDs[i] = sessionID
236 session := &Session{
237 DID: "did:plc:test" + string(rune(i)),
238 AccessToken: "token-" + string(rune(i)),
239 DPoPKey: key,
240 }
241
242 err := store.Set(sessionID, session)
243 if err != nil {
244 t.Fatalf("Set failed for session %d: %v", i, err)
245 }
246 }
247
248 // Verify all sessions can be retrieved
249 for i, sid := range sessionIDs {
250 _, err := store.Get(sid)
251 if err != nil {
252 t.Errorf("Failed to get session %d: %v", i, err)
253 }
254 }
255}
256
257// TestMemorySessionStoreConcurrentAccess verifies thread-safe concurrent operations.
258func TestMemorySessionStoreConcurrentAccess(t *testing.T) {
259 store := NewMemorySessionStore()
260 goroutines := 50
261 operationsPerGoroutine := 20
262
263 var wg sync.WaitGroup
264 wg.Add(goroutines)
265
266 // Launch multiple goroutines performing concurrent operations
267 for i := 0; i < goroutines; i++ {
268 go func(id int) {
269 defer wg.Done()
270
271 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
272
273 for j := 0; j < operationsPerGoroutine; j++ {
274 sessionID := GenerateSessionID()
275
276 // Set session
277 session := &Session{
278 DID: "did:plc:concurrent" + string(rune(id)) + string(rune(j)),
279 AccessToken: "token",
280 DPoPKey: key,
281 }
282 store.Set(sessionID, session)
283
284 // Get session
285 store.Get(sessionID)
286
287 // Update session
288 session.DPoPNonce = "updated-nonce"
289 store.Set(sessionID, session)
290
291 // Delete session
292 store.Delete(sessionID)
293 }
294 }(i)
295 }
296
297 wg.Wait()
298
299 // Test passes if no race conditions occur
300 t.Log("Concurrent access test completed successfully")
301}
302
303// TestMemorySessionStoreConcurrentReadWrite verifies concurrent reads and writes.
304func TestMemorySessionStoreConcurrentReadWrite(t *testing.T) {
305 store := NewMemorySessionStore()
306 sessionID := "shared-session"
307
308 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
309 session := &Session{
310 DID: "did:plc:shared",
311 AccessToken: "token",
312 DPoPKey: key,
313 DPoPNonce: "nonce-0",
314 }
315
316 // Set initial session
317 store.Set(sessionID, session)
318
319 var wg sync.WaitGroup
320 readers := 20
321 writers := 10
322
323 // Launch readers
324 wg.Add(readers)
325 for i := 0; i < readers; i++ {
326 go func() {
327 defer wg.Done()
328 for j := 0; j < 50; j++ {
329 store.Get(sessionID)
330 }
331 }()
332 }
333
334 // Launch writers
335 wg.Add(writers)
336 for i := 0; i < writers; i++ {
337 go func(id int) {
338 defer wg.Done()
339 for j := 0; j < 50; j++ {
340 updatedSession := &Session{
341 DID: "did:plc:shared",
342 AccessToken: "token",
343 DPoPKey: key,
344 DPoPNonce: "nonce-" + string(rune(id)) + string(rune(j)),
345 }
346 store.Set(sessionID, updatedSession)
347 }
348 }(i)
349 }
350
351 wg.Wait()
352
353 // Verify session still exists and is valid
354 retrieved, err := store.Get(sessionID)
355 if err != nil {
356 t.Fatalf("Session not found after concurrent access: %v", err)
357 }
358
359 if retrieved.DID != "did:plc:shared" {
360 t.Errorf("Session corrupted after concurrent access")
361 }
362
363 t.Log("Concurrent read/write test completed successfully")
364}
365
366// TestSessionStoreInterface verifies that MemorySessionStore implements SessionStore.
367func TestSessionStoreInterface(t *testing.T) {
368 var _ SessionStore = (*MemorySessionStore)(nil)
369 t.Log("MemorySessionStore correctly implements SessionStore interface")
370}
371
372// TestSessionFieldPersistence verifies all Session fields are preserved.
373func TestSessionFieldPersistence(t *testing.T) {
374 store := NewMemorySessionStore()
375 sessionID := "test-all-fields"
376
377 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
378
379 // Create session with all fields populated
380 session := &Session{
381 DID: "did:plc:abc123xyz",
382 AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
383 RefreshToken: "refresh_token_value_here",
384 DPoPKey: key,
385 PDS: "https://pds.example.com",
386 DPoPNonce: "server_provided_nonce_123",
387 }
388
389 // Store the session
390 err := store.Set(sessionID, session)
391 if err != nil {
392 t.Fatalf("Failed to store session: %v", err)
393 }
394
395 // Retrieve and verify all fields
396 retrieved, err := store.Get(sessionID)
397 if err != nil {
398 t.Fatalf("Failed to retrieve session: %v", err)
399 }
400
401 if retrieved.DID != session.DID {
402 t.Errorf("DID not preserved: expected %s, got %s", session.DID, retrieved.DID)
403 }
404
405 if retrieved.AccessToken != session.AccessToken {
406 t.Errorf("AccessToken not preserved")
407 }
408
409 if retrieved.RefreshToken != session.RefreshToken {
410 t.Errorf("RefreshToken not preserved")
411 }
412
413 if retrieved.PDS != session.PDS {
414 t.Errorf("PDS not preserved: expected %s, got %s", session.PDS, retrieved.PDS)
415 }
416
417 if retrieved.DPoPNonce != session.DPoPNonce {
418 t.Errorf("DPoPNonce not preserved: expected %s, got %s", session.DPoPNonce, retrieved.DPoPNonce)
419 }
420
421 if retrieved.DPoPKey == nil {
422 t.Error("DPoPKey is nil after retrieval")
423 }
424
425 // Verify the DPoP key is the same instance
426 if retrieved.DPoPKey != session.DPoPKey {
427 t.Error("DPoPKey pointer not preserved")
428 }
429}
430
431// TestMemorySessionStoreStressTest performs stress testing with many operations.
432func TestMemorySessionStoreStressTest(t *testing.T) {
433 if testing.Short() {
434 t.Skip("Skipping stress test in short mode")
435 }
436
437 store := NewMemorySessionStore()
438 operations := 10000
439
440 for i := 0; i < operations; i++ {
441 sessionID := GenerateSessionID()
442 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
443
444 session := &Session{
445 DID: "did:plc:stress" + string(rune(i)),
446 AccessToken: "token-" + string(rune(i)),
447 DPoPKey: key,
448 }
449
450 // Set
451 store.Set(sessionID, session)
452
453 // Get
454 retrieved, err := store.Get(sessionID)
455 if err != nil {
456 t.Fatalf("Get failed at iteration %d: %v", i, err)
457 }
458
459 // Verify
460 if retrieved.DID != session.DID {
461 t.Fatalf("Data corruption at iteration %d", i)
462 }
463
464 // Delete every 10th session to keep memory reasonable
465 if i%10 == 0 {
466 store.Delete(sessionID)
467 }
468 }
469
470 t.Logf("Stress test completed: %d operations", operations)
471}
472
473// TestMemorySessionStoreExpiration verifies sessions expire after TTL.
474func TestMemorySessionStoreExpiration(t *testing.T) {
475 // Create store with 200ms TTL
476 store := NewMemorySessionStoreWithTTL(200*time.Millisecond, 1*time.Minute)
477 defer store.Stop()
478
479 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
480 sessionID := "test-expiration"
481 session := &Session{
482 DID: "did:plc:expire-test",
483 AccessToken: "token",
484 DPoPKey: key,
485 }
486
487 // Store session
488 err := store.Set(sessionID, session)
489 if err != nil {
490 t.Fatalf("Set failed: %v", err)
491 }
492
493 // Should be retrievable immediately
494 retrieved, err := store.Get(sessionID)
495 if err != nil {
496 t.Fatalf("Get before expiration failed: %v", err)
497 }
498 if retrieved.DID != session.DID {
499 t.Error("Retrieved session doesn't match")
500 }
501
502 // Wait for expiration
503 time.Sleep(250 * time.Millisecond)
504
505 // Should now return ErrSessionNotFound
506 _, err = store.Get(sessionID)
507 if err != ErrSessionNotFound {
508 t.Errorf("Expected ErrSessionNotFound after expiration, got %v", err)
509 }
510}
511
512// TestMemorySessionStoreCleanup verifies automatic cleanup of expired sessions.
513func TestMemorySessionStoreCleanup(t *testing.T) {
514 // Create store with 100ms TTL and 50ms cleanup interval
515 store := NewMemorySessionStoreWithTTL(100*time.Millisecond, 50*time.Millisecond)
516 defer store.Stop()
517
518 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
519
520 // Store 5 sessions and keep their IDs
521 sessionIDs := make([]string, 5)
522 for i := 0; i < 5; i++ {
523 sessionID := GenerateSessionID()
524 sessionIDs[i] = sessionID
525 session := &Session{
526 DID: "did:plc:cleanup" + string(rune(i)),
527 AccessToken: "token",
528 DPoPKey: key,
529 }
530 store.Set(sessionID, session)
531 }
532
533 // Verify all sessions initially exist
534 for _, sid := range sessionIDs {
535 _, err := store.Get(sid)
536 if err != nil {
537 t.Errorf("Session should exist initially: %v", err)
538 }
539 }
540
541 // Wait for expiration and cleanup (100ms + 50ms + buffer)
542 time.Sleep(200 * time.Millisecond)
543
544 // Verify sessions were cleaned up by trying to retrieve them
545 for _, sid := range sessionIDs {
546 _, err := store.Get(sid)
547 if err != ErrSessionNotFound {
548 t.Error("Session should have been cleaned up")
549 }
550 }
551}
552
553// TestMemorySessionStoreStop verifies Stop() terminates cleanup goroutine.
554func TestMemorySessionStoreStop(t *testing.T) {
555 store := NewMemorySessionStoreWithTTL(1*time.Hour, 100*time.Millisecond)
556
557 // Stop the store
558 store.Stop()
559
560 // Calling Stop again should be safe (shouldn't panic)
561 store.Stop()
562
563 // Store should still function after Stop
564 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
565 session := &Session{
566 DID: "did:plc:test-after-stop",
567 AccessToken: "token",
568 DPoPKey: key,
569 }
570 err := store.Set("test-id", session)
571 if err != nil {
572 t.Errorf("Set should work after Stop: %v", err)
573 }
574}
575
576// TestMemorySessionStoreCustomTTL verifies custom TTL is respected.
577func TestMemorySessionStoreCustomTTL(t *testing.T) {
578 customTTL := 500 * time.Millisecond
579 store := NewMemorySessionStoreWithTTL(customTTL, 1*time.Minute)
580 defer store.Stop()
581
582 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
583 sessionID := "test-custom-ttl"
584 session := &Session{
585 DID: "did:plc:custom-ttl",
586 AccessToken: "token",
587 DPoPKey: key,
588 }
589
590 store.Set(sessionID, session)
591
592 // Should be valid before TTL
593 time.Sleep(300 * time.Millisecond)
594 _, err := store.Get(sessionID)
595 if err != nil {
596 t.Errorf("Session should still be valid at 300ms: %v", err)
597 }
598
599 // Should be expired after TTL
600 time.Sleep(300 * time.Millisecond) // Total: 600ms > 500ms TTL
601 _, err = store.Get(sessionID)
602 if err != ErrSessionNotFound {
603 t.Errorf("Expected ErrSessionNotFound after TTL, got %v", err)
604 }
605}
606
607// TestMemorySessionStoreDefaultTTL verifies default store creation works.
608func TestMemorySessionStoreDefaultTTL(t *testing.T) {
609 store := NewMemorySessionStore()
610 defer store.Stop()
611
612 // Test that default store works correctly
613 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
614 sessionID := "test-default"
615 session := &Session{
616 DID: "did:plc:default-test",
617 AccessToken: "token",
618 DPoPKey: key,
619 }
620
621 err := store.Set(sessionID, session)
622 if err != nil {
623 t.Fatalf("Set failed: %v", err)
624 }
625
626 _, err = store.Get(sessionID)
627 if err != nil {
628 t.Errorf("Get failed: %v", err)
629 }
630}
631
632// TestMemorySessionStoreConcurrentExpiration verifies thread-safe cleanup.
633func TestMemorySessionStoreConcurrentExpiration(t *testing.T) {
634 store := NewMemorySessionStoreWithTTL(100*time.Millisecond, 50*time.Millisecond)
635 defer store.Stop()
636
637 var wg sync.WaitGroup
638 goroutines := 10
639
640 // Concurrent writes
641 wg.Add(goroutines)
642 for i := 0; i < goroutines; i++ {
643 go func(id int) {
644 defer wg.Done()
645 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
646 for j := 0; j < 10; j++ {
647 sessionID := GenerateSessionID()
648 session := &Session{
649 DID: "did:plc:concurrent" + string(rune(id)) + string(rune(j)),
650 AccessToken: "token",
651 DPoPKey: key,
652 }
653 store.Set(sessionID, session)
654 time.Sleep(5 * time.Millisecond)
655 }
656 }(i)
657 }
658
659 wg.Wait()
660
661 // Wait for cleanup to process
662 time.Sleep(200 * time.Millisecond)
663
664 t.Log("Concurrent expiration test completed successfully")
665}
666
667// TestMemorySessionStoreExpirationOnGet verifies expired sessions return error on Get.
668func TestMemorySessionStoreExpirationOnGet(t *testing.T) {
669 store := NewMemorySessionStoreWithTTL(100*time.Millisecond, 10*time.Minute)
670 defer store.Stop()
671
672 key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
673 sessionID := "test-get-expiration"
674 session := &Session{
675 DID: "did:plc:get-expire",
676 AccessToken: "token",
677 DPoPKey: key,
678 }
679
680 store.Set(sessionID, session)
681
682 // Wait for expiration (but before cleanup runs)
683 time.Sleep(150 * time.Millisecond)
684
685 // Get should detect expiration even if cleanup hasn't run yet
686 _, err := store.Get(sessionID)
687 if err != ErrSessionNotFound {
688 t.Errorf("Expected ErrSessionNotFound on Get of expired session, got %v", err)
689 }
690}