forked from tangled.org/core
this repo has no description
at knot-xrpc 17 kB view raw
1package secrets 2 3import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/alecthomas/assert/v2" 9 "github.com/bluesky-social/indigo/atproto/syntax" 10) 11 12func createInMemoryDB(t *testing.T) *SqliteManager { 13 t.Helper() 14 manager, err := NewSQLiteManager(":memory:") 15 if err != nil { 16 t.Fatalf("Failed to create in-memory manager: %v", err) 17 } 18 return manager 19} 20 21func createTestSecret(repo, key, value, createdBy string) UnlockedSecret { 22 return UnlockedSecret{ 23 Key: key, 24 Value: value, 25 Repo: DidSlashRepo(repo), 26 CreatedAt: time.Now(), 27 CreatedBy: syntax.DID(createdBy), 28 } 29} 30 31// ensure that interface is satisfied 32func TestManagerInterface(t *testing.T) { 33 var _ Manager = (*SqliteManager)(nil) 34} 35 36func TestNewSQLiteManager(t *testing.T) { 37 tests := []struct { 38 name string 39 dbPath string 40 opts []SqliteManagerOpt 41 expectError bool 42 expectTable string 43 }{ 44 { 45 name: "default table name", 46 dbPath: ":memory:", 47 opts: nil, 48 expectError: false, 49 expectTable: "secrets", 50 }, 51 { 52 name: "custom table name", 53 dbPath: ":memory:", 54 opts: []SqliteManagerOpt{WithTableName("custom_secrets")}, 55 expectError: false, 56 expectTable: "custom_secrets", 57 }, 58 { 59 name: "invalid database path", 60 dbPath: "/invalid/path/to/database.db", 61 opts: nil, 62 expectError: true, 63 expectTable: "", 64 }, 65 } 66 67 for _, tt := range tests { 68 t.Run(tt.name, func(t *testing.T) { 69 manager, err := NewSQLiteManager(tt.dbPath, tt.opts...) 70 if tt.expectError { 71 if err == nil { 72 t.Error("Expected error but got none") 73 } 74 return 75 } 76 77 if err != nil { 78 t.Fatalf("Unexpected error: %v", err) 79 } 80 defer manager.db.Close() 81 82 if manager.tableName != tt.expectTable { 83 t.Errorf("Expected table name %s, got %s", tt.expectTable, manager.tableName) 84 } 85 }) 86 } 87} 88 89func TestSqliteManager_AddSecret(t *testing.T) { 90 tests := []struct { 91 name string 92 secrets []UnlockedSecret 93 expectError []error 94 }{ 95 { 96 name: "add single secret", 97 secrets: []UnlockedSecret{ 98 createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), 99 }, 100 expectError: []error{nil}, 101 }, 102 { 103 name: "add multiple unique secrets", 104 secrets: []UnlockedSecret{ 105 createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), 106 createTestSecret("did:plc:foo/repo", "db_password", "password_456", "did:plc:example123"), 107 createTestSecret("other.com/repo", "api_key", "other_secret", "did:plc:other"), 108 }, 109 expectError: []error{nil, nil, nil}, 110 }, 111 { 112 name: "add duplicate secret", 113 secrets: []UnlockedSecret{ 114 createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), 115 createTestSecret("did:plc:foo/repo", "api_key", "different_value", "did:plc:example123"), 116 }, 117 expectError: []error{nil, ErrKeyAlreadyPresent}, 118 }, 119 } 120 121 for _, tt := range tests { 122 t.Run(tt.name, func(t *testing.T) { 123 manager := createInMemoryDB(t) 124 defer manager.db.Close() 125 126 for i, secret := range tt.secrets { 127 err := manager.AddSecret(context.Background(), secret) 128 if err != tt.expectError[i] { 129 t.Errorf("Secret %d: expected error %v, got %v", i, tt.expectError[i], err) 130 } 131 } 132 }) 133 } 134} 135 136func TestSqliteManager_RemoveSecret(t *testing.T) { 137 tests := []struct { 138 name string 139 setupSecrets []UnlockedSecret 140 removeSecret Secret[any] 141 expectError error 142 }{ 143 { 144 name: "remove existing secret", 145 setupSecrets: []UnlockedSecret{ 146 createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), 147 }, 148 removeSecret: Secret[any]{ 149 Key: "api_key", 150 Repo: DidSlashRepo("did:plc:foo/repo"), 151 }, 152 expectError: nil, 153 }, 154 { 155 name: "remove non-existent secret", 156 setupSecrets: []UnlockedSecret{ 157 createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), 158 }, 159 removeSecret: Secret[any]{ 160 Key: "non_existent_key", 161 Repo: DidSlashRepo("did:plc:foo/repo"), 162 }, 163 expectError: ErrKeyNotFound, 164 }, 165 { 166 name: "remove from empty database", 167 setupSecrets: []UnlockedSecret{}, 168 removeSecret: Secret[any]{ 169 Key: "any_key", 170 Repo: DidSlashRepo("did:plc:foo/repo"), 171 }, 172 expectError: ErrKeyNotFound, 173 }, 174 { 175 name: "remove secret from wrong repo", 176 setupSecrets: []UnlockedSecret{ 177 createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), 178 }, 179 removeSecret: Secret[any]{ 180 Key: "api_key", 181 Repo: DidSlashRepo("other.com/repo"), 182 }, 183 expectError: ErrKeyNotFound, 184 }, 185 } 186 187 for _, tt := range tests { 188 t.Run(tt.name, func(t *testing.T) { 189 manager := createInMemoryDB(t) 190 defer manager.db.Close() 191 192 // Setup secrets 193 for _, secret := range tt.setupSecrets { 194 if err := manager.AddSecret(context.Background(), secret); err != nil { 195 t.Fatalf("Failed to setup secret: %v", err) 196 } 197 } 198 199 // Test removal 200 err := manager.RemoveSecret(context.Background(), tt.removeSecret) 201 if err != tt.expectError { 202 t.Errorf("Expected error %v, got %v", tt.expectError, err) 203 } 204 }) 205 } 206} 207 208func TestSqliteManager_GetSecretsLocked(t *testing.T) { 209 tests := []struct { 210 name string 211 setupSecrets []UnlockedSecret 212 queryRepo DidSlashRepo 213 expectedCount int 214 expectedKeys []string 215 expectError bool 216 }{ 217 { 218 name: "get secrets for repo with multiple secrets", 219 setupSecrets: []UnlockedSecret{ 220 createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), 221 createTestSecret("did:plc:foo/repo", "key2", "value2", "did:plc:user2"), 222 createTestSecret("other.com/repo", "key3", "value3", "did:plc:user3"), 223 }, 224 queryRepo: DidSlashRepo("did:plc:foo/repo"), 225 expectedCount: 2, 226 expectedKeys: []string{"key1", "key2"}, 227 expectError: false, 228 }, 229 { 230 name: "get secrets for repo with single secret", 231 setupSecrets: []UnlockedSecret{ 232 createTestSecret("did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"), 233 createTestSecret("other.com/repo", "other_key", "other_value", "did:plc:user2"), 234 }, 235 queryRepo: DidSlashRepo("did:plc:foo/repo"), 236 expectedCount: 1, 237 expectedKeys: []string{"single_key"}, 238 expectError: false, 239 }, 240 { 241 name: "get secrets for non-existent repo", 242 setupSecrets: []UnlockedSecret{ 243 createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), 244 }, 245 queryRepo: DidSlashRepo("nonexistent.com/repo"), 246 expectedCount: 0, 247 expectedKeys: []string{}, 248 expectError: false, 249 }, 250 { 251 name: "get secrets from empty database", 252 setupSecrets: []UnlockedSecret{}, 253 queryRepo: DidSlashRepo("did:plc:foo/repo"), 254 expectedCount: 0, 255 expectedKeys: []string{}, 256 expectError: false, 257 }, 258 } 259 260 for _, tt := range tests { 261 t.Run(tt.name, func(t *testing.T) { 262 manager := createInMemoryDB(t) 263 defer manager.db.Close() 264 265 // Setup secrets 266 for _, secret := range tt.setupSecrets { 267 if err := manager.AddSecret(context.Background(), secret); err != nil { 268 t.Fatalf("Failed to setup secret: %v", err) 269 } 270 } 271 272 // Test getting locked secrets 273 lockedSecrets, err := manager.GetSecretsLocked(context.Background(), tt.queryRepo) 274 if tt.expectError && err == nil { 275 t.Error("Expected error but got none") 276 return 277 } 278 if !tt.expectError && err != nil { 279 t.Fatalf("Unexpected error: %v", err) 280 } 281 282 if len(lockedSecrets) != tt.expectedCount { 283 t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(lockedSecrets)) 284 } 285 286 // Verify keys and that values are not present (locked) 287 foundKeys := make(map[string]bool) 288 for _, ls := range lockedSecrets { 289 foundKeys[ls.Key] = true 290 if ls.Repo != tt.queryRepo { 291 t.Errorf("Expected repo %s, got %s", tt.queryRepo, ls.Repo) 292 } 293 if ls.CreatedBy == "" { 294 t.Error("Expected CreatedBy to be present") 295 } 296 if ls.CreatedAt.IsZero() { 297 t.Error("Expected CreatedAt to be set") 298 } 299 } 300 301 for _, expectedKey := range tt.expectedKeys { 302 if !foundKeys[expectedKey] { 303 t.Errorf("Expected key %s not found", expectedKey) 304 } 305 } 306 }) 307 } 308} 309 310func TestSqliteManager_GetSecretsUnlocked(t *testing.T) { 311 tests := []struct { 312 name string 313 setupSecrets []UnlockedSecret 314 queryRepo DidSlashRepo 315 expectedCount int 316 expectedSecrets map[string]string // key -> value 317 expectError bool 318 }{ 319 { 320 name: "get unlocked secrets for repo with multiple secrets", 321 setupSecrets: []UnlockedSecret{ 322 createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), 323 createTestSecret("did:plc:foo/repo", "key2", "value2", "did:plc:user2"), 324 createTestSecret("other.com/repo", "key3", "value3", "did:plc:user3"), 325 }, 326 queryRepo: DidSlashRepo("did:plc:foo/repo"), 327 expectedCount: 2, 328 expectedSecrets: map[string]string{ 329 "key1": "value1", 330 "key2": "value2", 331 }, 332 expectError: false, 333 }, 334 { 335 name: "get unlocked secrets for repo with single secret", 336 setupSecrets: []UnlockedSecret{ 337 createTestSecret("did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"), 338 createTestSecret("other.com/repo", "other_key", "other_value", "did:plc:user2"), 339 }, 340 queryRepo: DidSlashRepo("did:plc:foo/repo"), 341 expectedCount: 1, 342 expectedSecrets: map[string]string{ 343 "single_key": "single_value", 344 }, 345 expectError: false, 346 }, 347 { 348 name: "get unlocked secrets for non-existent repo", 349 setupSecrets: []UnlockedSecret{ 350 createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), 351 }, 352 queryRepo: DidSlashRepo("nonexistent.com/repo"), 353 expectedCount: 0, 354 expectedSecrets: map[string]string{}, 355 expectError: false, 356 }, 357 { 358 name: "get unlocked secrets from empty database", 359 setupSecrets: []UnlockedSecret{}, 360 queryRepo: DidSlashRepo("did:plc:foo/repo"), 361 expectedCount: 0, 362 expectedSecrets: map[string]string{}, 363 expectError: false, 364 }, 365 } 366 367 for _, tt := range tests { 368 t.Run(tt.name, func(t *testing.T) { 369 manager := createInMemoryDB(t) 370 defer manager.db.Close() 371 372 // Setup secrets 373 for _, secret := range tt.setupSecrets { 374 if err := manager.AddSecret(context.Background(), secret); err != nil { 375 t.Fatalf("Failed to setup secret: %v", err) 376 } 377 } 378 379 // Test getting unlocked secrets 380 unlockedSecrets, err := manager.GetSecretsUnlocked(context.Background(), tt.queryRepo) 381 if tt.expectError && err == nil { 382 t.Error("Expected error but got none") 383 return 384 } 385 if !tt.expectError && err != nil { 386 t.Fatalf("Unexpected error: %v", err) 387 } 388 389 if len(unlockedSecrets) != tt.expectedCount { 390 t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(unlockedSecrets)) 391 } 392 393 // Verify keys, values, and metadata 394 for _, us := range unlockedSecrets { 395 expectedValue, exists := tt.expectedSecrets[us.Key] 396 if !exists { 397 t.Errorf("Unexpected key: %s", us.Key) 398 continue 399 } 400 if us.Value != expectedValue { 401 t.Errorf("Expected value %s for key %s, got %s", expectedValue, us.Key, us.Value) 402 } 403 if us.Repo != tt.queryRepo { 404 t.Errorf("Expected repo %s, got %s", tt.queryRepo, us.Repo) 405 } 406 if us.CreatedBy == "" { 407 t.Error("Expected CreatedBy to be present") 408 } 409 if us.CreatedAt.IsZero() { 410 t.Error("Expected CreatedAt to be set") 411 } 412 } 413 }) 414 } 415} 416 417// Test that demonstrates interface usage with table-driven tests 418func TestManagerInterface_Usage(t *testing.T) { 419 tests := []struct { 420 name string 421 operations []func(Manager) error 422 expectError bool 423 }{ 424 { 425 name: "successful workflow", 426 operations: []func(Manager) error{ 427 func(m Manager) error { 428 secret := createTestSecret("interface.test/repo", "test_key", "test_value", "did:plc:user") 429 return m.AddSecret(context.Background(), secret) 430 }, 431 func(m Manager) error { 432 _, err := m.GetSecretsLocked(context.Background(), DidSlashRepo("interface.test/repo")) 433 return err 434 }, 435 func(m Manager) error { 436 _, err := m.GetSecretsUnlocked(context.Background(), DidSlashRepo("interface.test/repo")) 437 return err 438 }, 439 func(m Manager) error { 440 secret := Secret[any]{ 441 Key: "test_key", 442 Repo: DidSlashRepo("interface.test/repo"), 443 } 444 return m.RemoveSecret(context.Background(), secret) 445 }, 446 }, 447 expectError: false, 448 }, 449 { 450 name: "error on duplicate key", 451 operations: []func(Manager) error{ 452 func(m Manager) error { 453 secret := createTestSecret("interface.test/repo", "dup_key", "value1", "did:plc:user") 454 return m.AddSecret(context.Background(), secret) 455 }, 456 func(m Manager) error { 457 secret := createTestSecret("interface.test/repo", "dup_key", "value2", "did:plc:user") 458 return m.AddSecret(context.Background(), secret) // Should return ErrKeyAlreadyPresent 459 }, 460 }, 461 expectError: true, 462 }, 463 } 464 465 for _, tt := range tests { 466 t.Run(tt.name, func(t *testing.T) { 467 var manager Manager = createInMemoryDB(t) 468 defer func() { 469 if sqliteManager, ok := manager.(*SqliteManager); ok { 470 sqliteManager.db.Close() 471 } 472 }() 473 474 var finalErr error 475 for i, operation := range tt.operations { 476 if err := operation(manager); err != nil { 477 finalErr = err 478 t.Logf("Operation %d returned error: %v", i, err) 479 } 480 } 481 482 if tt.expectError && finalErr == nil { 483 t.Error("Expected error but got none") 484 } 485 if !tt.expectError && finalErr != nil { 486 t.Errorf("Unexpected error: %v", finalErr) 487 } 488 }) 489 } 490} 491 492// Integration test with table-driven scenarios 493func TestSqliteManager_Integration(t *testing.T) { 494 tests := []struct { 495 name string 496 scenario func(*testing.T, *SqliteManager) 497 }{ 498 { 499 name: "multi-repo secret management", 500 scenario: func(t *testing.T, manager *SqliteManager) { 501 repo1 := DidSlashRepo("example1.com/repo") 502 repo2 := DidSlashRepo("example2.com/repo") 503 504 secrets := []UnlockedSecret{ 505 createTestSecret(string(repo1), "db_password", "super_secret_123", "did:plc:admin"), 506 createTestSecret(string(repo1), "api_key", "api_key_456", "did:plc:user1"), 507 createTestSecret(string(repo2), "token", "bearer_token_789", "did:plc:user2"), 508 } 509 510 // Add all secrets 511 for _, secret := range secrets { 512 if err := manager.AddSecret(context.Background(), secret); err != nil { 513 t.Fatalf("Failed to add secret %s: %v", secret.Key, err) 514 } 515 } 516 517 // Verify counts 518 locked1, _ := manager.GetSecretsLocked(context.Background(), repo1) 519 locked2, _ := manager.GetSecretsLocked(context.Background(), repo2) 520 521 if len(locked1) != 2 { 522 t.Errorf("Expected 2 secrets for repo1, got %d", len(locked1)) 523 } 524 if len(locked2) != 1 { 525 t.Errorf("Expected 1 secret for repo2, got %d", len(locked2)) 526 } 527 528 // Remove and verify 529 secretToRemove := Secret[any]{Key: "db_password", Repo: repo1} 530 if err := manager.RemoveSecret(context.Background(), secretToRemove); err != nil { 531 t.Fatalf("Failed to remove secret: %v", err) 532 } 533 534 locked1After, _ := manager.GetSecretsLocked(context.Background(), repo1) 535 if len(locked1After) != 1 { 536 t.Errorf("Expected 1 secret for repo1 after removal, got %d", len(locked1After)) 537 } 538 if locked1After[0].Key != "api_key" { 539 t.Errorf("Expected remaining secret to be 'api_key', got %s", locked1After[0].Key) 540 } 541 }, 542 }, 543 { 544 name: "empty database operations", 545 scenario: func(t *testing.T, manager *SqliteManager) { 546 repo := DidSlashRepo("empty.test/repo") 547 548 // Operations on empty database should not error 549 locked, err := manager.GetSecretsLocked(context.Background(), repo) 550 if err != nil { 551 t.Errorf("GetSecretsLocked on empty DB failed: %v", err) 552 } 553 if len(locked) != 0 { 554 t.Errorf("Expected 0 secrets, got %d", len(locked)) 555 } 556 557 unlocked, err := manager.GetSecretsUnlocked(context.Background(), repo) 558 if err != nil { 559 t.Errorf("GetSecretsUnlocked on empty DB failed: %v", err) 560 } 561 if len(unlocked) != 0 { 562 t.Errorf("Expected 0 secrets, got %d", len(unlocked)) 563 } 564 565 // Remove from empty should return ErrKeyNotFound 566 nonExistent := Secret[any]{Key: "none", Repo: repo} 567 err = manager.RemoveSecret(context.Background(), nonExistent) 568 if err != ErrKeyNotFound { 569 t.Errorf("Expected ErrKeyNotFound, got %v", err) 570 } 571 }, 572 }, 573 } 574 575 for _, tt := range tests { 576 t.Run(tt.name, func(t *testing.T) { 577 manager := createInMemoryDB(t) 578 defer manager.db.Close() 579 tt.scenario(t, manager) 580 }) 581 } 582} 583 584func TestSqliteManager_StopperInterface(t *testing.T) { 585 manager := &SqliteManager{} 586 587 // Verify that SqliteManager does NOT implement the Stopper interface 588 _, ok := interface{}(manager).(Stopper) 589 assert.False(t, ok, "SqliteManager should NOT implement Stopper interface") 590}