Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591package secrets
import ( "context" "testing" "time"
"github.com/alecthomas/assert/v2" "github.com/bluesky-social/indigo/atproto/syntax")
func createInMemoryDB(t *testing.T) *SqliteManager { t.Helper() manager, err := NewSQLiteManager(":memory:") if err != nil { t.Fatalf("Failed to create in-memory manager: %v", err) } return manager}
func createTestSecret(repo, key, value, createdBy string) UnlockedSecret { return UnlockedSecret{ Key: key, Value: value, Repo: RepoIdentifier(repo), CreatedAt: time.Now(), CreatedBy: syntax.DID(createdBy), }}
// ensure that interface is satisfiedfunc TestManagerInterface(t *testing.T) { var _ Manager = (*SqliteManager)(nil)}
func TestNewSQLiteManager(t *testing.T) { tests := []struct { name string dbPath string opts []SqliteManagerOpt expectError bool expectTable string }{ { name: "default table name", dbPath: ":memory:", opts: nil, expectError: false, expectTable: "secrets", }, { name: "custom table name", dbPath: ":memory:", opts: []SqliteManagerOpt{WithTableName("custom_secrets")}, expectError: false, expectTable: "custom_secrets", }, { name: "invalid database path", dbPath: "/invalid/path/to/database.db", opts: nil, expectError: true, expectTable: "", }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { manager, err := NewSQLiteManager(tt.dbPath, tt.opts...) if tt.expectError { if err == nil { t.Error("Expected error but got none") } return }
if err != nil { t.Fatalf("Unexpected error: %v", err) } defer manager.db.Close()
if manager.tableName != tt.expectTable { t.Errorf("Expected table name %s, got %s", tt.expectTable, manager.tableName) } }) }}
func TestSqliteManager_AddSecret(t *testing.T) { tests := []struct { name string secrets []UnlockedSecret expectError []error }{ { name: "add single secret", secrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), }, expectError: []error{nil}, }, { name: "add multiple unique secrets", secrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), createTestSecret("did:plc:foo/repo", "db_password", "password_456", "did:plc:example123"), createTestSecret("other.com/repo", "api_key", "other_secret", "did:plc:other"), }, expectError: []error{nil, nil, nil}, }, { name: "add duplicate secret", secrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), createTestSecret("did:plc:foo/repo", "api_key", "different_value", "did:plc:example123"), }, expectError: []error{nil, ErrKeyAlreadyPresent}, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { manager := createInMemoryDB(t) defer manager.db.Close()
for i, secret := range tt.secrets { err := manager.AddSecret(context.Background(), secret) if err != tt.expectError[i] { t.Errorf("Secret %d: expected error %v, got %v", i, tt.expectError[i], err) } } }) }}
func TestSqliteManager_RemoveSecret(t *testing.T) { tests := []struct { name string setupSecrets []UnlockedSecret removeSecret Secret[any] expectError error }{ { name: "remove existing secret", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), }, removeSecret: Secret[any]{ Key: "api_key", Repo: RepoIdentifier("did:plc:foo/repo"), }, expectError: nil, }, { name: "remove non-existent secret", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), }, removeSecret: Secret[any]{ Key: "non_existent_key", Repo: RepoIdentifier("did:plc:foo/repo"), }, expectError: ErrKeyNotFound, }, { name: "remove from empty database", setupSecrets: []UnlockedSecret{}, removeSecret: Secret[any]{ Key: "any_key", Repo: RepoIdentifier("did:plc:foo/repo"), }, expectError: ErrKeyNotFound, }, { name: "remove secret from wrong repo", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"), }, removeSecret: Secret[any]{ Key: "api_key", Repo: RepoIdentifier("other.com/repo"), }, expectError: ErrKeyNotFound, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { manager := createInMemoryDB(t) defer manager.db.Close()
// Setup secrets for _, secret := range tt.setupSecrets { if err := manager.AddSecret(context.Background(), secret); err != nil { t.Fatalf("Failed to setup secret: %v", err) } }
// Test removal err := manager.RemoveSecret(context.Background(), tt.removeSecret) if err != tt.expectError { t.Errorf("Expected error %v, got %v", tt.expectError, err) } }) }}
func TestSqliteManager_GetSecretsLocked(t *testing.T) { tests := []struct { name string setupSecrets []UnlockedSecret queryRepo RepoIdentifier expectedCount int expectedKeys []string expectError bool }{ { name: "get secrets for repo with multiple secrets", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), createTestSecret("did:plc:foo/repo", "key2", "value2", "did:plc:user2"), createTestSecret("other.com/repo", "key3", "value3", "did:plc:user3"), }, queryRepo: RepoIdentifier("did:plc:foo/repo"), expectedCount: 2, expectedKeys: []string{"key1", "key2"}, expectError: false, }, { name: "get secrets for repo with single secret", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"), createTestSecret("other.com/repo", "other_key", "other_value", "did:plc:user2"), }, queryRepo: RepoIdentifier("did:plc:foo/repo"), expectedCount: 1, expectedKeys: []string{"single_key"}, expectError: false, }, { name: "get secrets for non-existent repo", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), }, queryRepo: RepoIdentifier("nonexistent.com/repo"), expectedCount: 0, expectedKeys: []string{}, expectError: false, }, { name: "get secrets from empty database", setupSecrets: []UnlockedSecret{}, queryRepo: RepoIdentifier("did:plc:foo/repo"), expectedCount: 0, expectedKeys: []string{}, expectError: false, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { manager := createInMemoryDB(t) defer manager.db.Close()
// Setup secrets for _, secret := range tt.setupSecrets { if err := manager.AddSecret(context.Background(), secret); err != nil { t.Fatalf("Failed to setup secret: %v", err) } }
// Test getting locked secrets lockedSecrets, err := manager.GetSecretsLocked(context.Background(), tt.queryRepo) if tt.expectError && err == nil { t.Error("Expected error but got none") return } if !tt.expectError && err != nil { t.Fatalf("Unexpected error: %v", err) }
if len(lockedSecrets) != tt.expectedCount { t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(lockedSecrets)) }
// Verify keys and that values are not present (locked) foundKeys := make(map[string]bool) for _, ls := range lockedSecrets { foundKeys[ls.Key] = true if ls.Repo != tt.queryRepo { t.Errorf("Expected repo %s, got %s", tt.queryRepo, ls.Repo) } if ls.CreatedBy == "" { t.Error("Expected CreatedBy to be present") } if ls.CreatedAt.IsZero() { t.Error("Expected CreatedAt to be set") } }
for _, expectedKey := range tt.expectedKeys { if !foundKeys[expectedKey] { t.Errorf("Expected key %s not found", expectedKey) } } }) }}
func TestSqliteManager_GetSecretsUnlocked(t *testing.T) { tests := []struct { name string setupSecrets []UnlockedSecret queryRepo RepoIdentifier expectedCount int expectedSecrets map[string]string // key -> value expectError bool }{ { name: "get unlocked secrets for repo with multiple secrets", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), createTestSecret("did:plc:foo/repo", "key2", "value2", "did:plc:user2"), createTestSecret("other.com/repo", "key3", "value3", "did:plc:user3"), }, queryRepo: RepoIdentifier("did:plc:foo/repo"), expectedCount: 2, expectedSecrets: map[string]string{ "key1": "value1", "key2": "value2", }, expectError: false, }, { name: "get unlocked secrets for repo with single secret", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"), createTestSecret("other.com/repo", "other_key", "other_value", "did:plc:user2"), }, queryRepo: RepoIdentifier("did:plc:foo/repo"), expectedCount: 1, expectedSecrets: map[string]string{ "single_key": "single_value", }, expectError: false, }, { name: "get unlocked secrets for non-existent repo", setupSecrets: []UnlockedSecret{ createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"), }, queryRepo: RepoIdentifier("nonexistent.com/repo"), expectedCount: 0, expectedSecrets: map[string]string{}, expectError: false, }, { name: "get unlocked secrets from empty database", setupSecrets: []UnlockedSecret{}, queryRepo: RepoIdentifier("did:plc:foo/repo"), expectedCount: 0, expectedSecrets: map[string]string{}, expectError: false, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { manager := createInMemoryDB(t) defer manager.db.Close()
// Setup secrets for _, secret := range tt.setupSecrets { if err := manager.AddSecret(context.Background(), secret); err != nil { t.Fatalf("Failed to setup secret: %v", err) } }
// Test getting unlocked secrets unlockedSecrets, err := manager.GetSecretsUnlocked(context.Background(), tt.queryRepo) if tt.expectError && err == nil { t.Error("Expected error but got none") return } if !tt.expectError && err != nil { t.Fatalf("Unexpected error: %v", err) }
if len(unlockedSecrets) != tt.expectedCount { t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(unlockedSecrets)) }
// Verify keys, values, and metadata for _, us := range unlockedSecrets { expectedValue, exists := tt.expectedSecrets[us.Key] if !exists { t.Errorf("Unexpected key: %s", us.Key) continue } if us.Value != expectedValue { t.Errorf("Expected value %s for key %s, got %s", expectedValue, us.Key, us.Value) } if us.Repo != tt.queryRepo { t.Errorf("Expected repo %s, got %s", tt.queryRepo, us.Repo) } if us.CreatedBy == "" { t.Error("Expected CreatedBy to be present") } if us.CreatedAt.IsZero() { t.Error("Expected CreatedAt to be set") } } }) }}
// Test that demonstrates interface usage with table-driven testsfunc TestManagerInterface_Usage(t *testing.T) { tests := []struct { name string operations []func(Manager) error expectError bool }{ { name: "successful workflow", operations: []func(Manager) error{ func(m Manager) error { secret := createTestSecret("interface.test/repo", "test_key", "test_value", "did:plc:user") return m.AddSecret(context.Background(), secret) }, func(m Manager) error { _, err := m.GetSecretsLocked(context.Background(), RepoIdentifier("interface.test/repo")) return err }, func(m Manager) error { _, err := m.GetSecretsUnlocked(context.Background(), RepoIdentifier("interface.test/repo")) return err }, func(m Manager) error { secret := Secret[any]{ Key: "test_key", Repo: RepoIdentifier("interface.test/repo"), } return m.RemoveSecret(context.Background(), secret) }, }, expectError: false, }, { name: "error on duplicate key", operations: []func(Manager) error{ func(m Manager) error { secret := createTestSecret("interface.test/repo", "dup_key", "value1", "did:plc:user") return m.AddSecret(context.Background(), secret) }, func(m Manager) error { secret := createTestSecret("interface.test/repo", "dup_key", "value2", "did:plc:user") return m.AddSecret(context.Background(), secret) // Should return ErrKeyAlreadyPresent }, }, expectError: true, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var manager Manager = createInMemoryDB(t) defer func() { if sqliteManager, ok := manager.(*SqliteManager); ok { sqliteManager.db.Close() } }()
var finalErr error for i, operation := range tt.operations { if err := operation(manager); err != nil { finalErr = err t.Logf("Operation %d returned error: %v", i, err) } }
if tt.expectError && finalErr == nil { t.Error("Expected error but got none") } if !tt.expectError && finalErr != nil { t.Errorf("Unexpected error: %v", finalErr) } }) }}
// Integration test with table-driven scenariosfunc TestSqliteManager_Integration(t *testing.T) { tests := []struct { name string scenario func(*testing.T, *SqliteManager) }{ { name: "multi-repo secret management", scenario: func(t *testing.T, manager *SqliteManager) { repo1 := RepoIdentifier("example1.com/repo") repo2 := RepoIdentifier("example2.com/repo")
secrets := []UnlockedSecret{ createTestSecret(string(repo1), "db_password", "super_secret_123", "did:plc:admin"), createTestSecret(string(repo1), "api_key", "api_key_456", "did:plc:user1"), createTestSecret(string(repo2), "token", "bearer_token_789", "did:plc:user2"), }
// Add all secrets for _, secret := range secrets { if err := manager.AddSecret(context.Background(), secret); err != nil { t.Fatalf("Failed to add secret %s: %v", secret.Key, err) } }
// Verify counts locked1, _ := manager.GetSecretsLocked(context.Background(), repo1) locked2, _ := manager.GetSecretsLocked(context.Background(), repo2)
if len(locked1) != 2 { t.Errorf("Expected 2 secrets for repo1, got %d", len(locked1)) } if len(locked2) != 1 { t.Errorf("Expected 1 secret for repo2, got %d", len(locked2)) }
// Remove and verify secretToRemove := Secret[any]{Key: "db_password", Repo: repo1} if err := manager.RemoveSecret(context.Background(), secretToRemove); err != nil { t.Fatalf("Failed to remove secret: %v", err) }
locked1After, _ := manager.GetSecretsLocked(context.Background(), repo1) if len(locked1After) != 1 { t.Errorf("Expected 1 secret for repo1 after removal, got %d", len(locked1After)) } if locked1After[0].Key != "api_key" { t.Errorf("Expected remaining secret to be 'api_key', got %s", locked1After[0].Key) } }, }, { name: "empty database operations", scenario: func(t *testing.T, manager *SqliteManager) { repo := RepoIdentifier("empty.test/repo")
// Operations on empty database should not error locked, err := manager.GetSecretsLocked(context.Background(), repo) if err != nil { t.Errorf("GetSecretsLocked on empty DB failed: %v", err) } if len(locked) != 0 { t.Errorf("Expected 0 secrets, got %d", len(locked)) }
unlocked, err := manager.GetSecretsUnlocked(context.Background(), repo) if err != nil { t.Errorf("GetSecretsUnlocked on empty DB failed: %v", err) } if len(unlocked) != 0 { t.Errorf("Expected 0 secrets, got %d", len(unlocked)) }
// Remove from empty should return ErrKeyNotFound nonExistent := Secret[any]{Key: "none", Repo: repo} err = manager.RemoveSecret(context.Background(), nonExistent) if err != ErrKeyNotFound { t.Errorf("Expected ErrKeyNotFound, got %v", err) } }, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { manager := createInMemoryDB(t) defer manager.db.Close() tt.scenario(t, manager) }) }}
func TestSqliteManager_StopperInterface(t *testing.T) { manager := &SqliteManager{}
// Verify that SqliteManager does NOT implement the Stopper interface _, ok := interface{}(manager).(Stopper) assert.False(t, ok, "SqliteManager should NOT implement Stopper interface")}