Vibe-guided bskyoauth and custom repo example code in Golang 馃 probably not safe to use in prod
at main 18 kB view raw
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}