···
7
+
"github.com/bluesky-social/indigo/atproto/syntax"
10
+
func createInMemoryDB(t *testing.T) *SqliteManager {
12
+
manager, err := NewSQLiteManager(":memory:")
14
+
t.Fatalf("Failed to create in-memory manager: %v", err)
19
+
func createTestSecret(repo, key, value, createdBy string) UnlockedSecret {
20
+
return UnlockedSecret{
23
+
Repo: syntax.ATURI(repo),
24
+
CreatedAt: time.Now(),
25
+
CreatedBy: syntax.DID(createdBy),
29
+
// ensure that interface is satisfied
30
+
func TestManagerInterface(t *testing.T) {
31
+
var _ Manager = (*SqliteManager)(nil)
34
+
func TestNewSQLiteManager(t *testing.T) {
38
+
opts []SqliteManagerOpt
43
+
name: "default table name",
47
+
expectTable: "secrets",
50
+
name: "custom table name",
52
+
opts: []SqliteManagerOpt{WithTableName("custom_secrets")},
54
+
expectTable: "custom_secrets",
57
+
name: "invalid database path",
58
+
dbPath: "/invalid/path/to/database.db",
65
+
for _, tt := range tests {
66
+
t.Run(tt.name, func(t *testing.T) {
67
+
manager, err := NewSQLiteManager(tt.dbPath, tt.opts...)
70
+
t.Error("Expected error but got none")
76
+
t.Fatalf("Unexpected error: %v", err)
78
+
defer manager.db.Close()
80
+
if manager.tableName != tt.expectTable {
81
+
t.Errorf("Expected table name %s, got %s", tt.expectTable, manager.tableName)
87
+
func TestSqliteManager_AddSecret(t *testing.T) {
90
+
secrets []UnlockedSecret
94
+
name: "add single secret",
95
+
secrets: []UnlockedSecret{
96
+
createTestSecret("at://did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
98
+
expectError: []error{nil},
101
+
name: "add multiple unique secrets",
102
+
secrets: []UnlockedSecret{
103
+
createTestSecret("at://did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
104
+
createTestSecret("at://did:plc:foo/repo", "db_password", "password_456", "did:plc:example123"),
105
+
createTestSecret("at://other.com/repo", "api_key", "other_secret", "did:plc:other"),
107
+
expectError: []error{nil, nil, nil},
110
+
name: "add duplicate secret",
111
+
secrets: []UnlockedSecret{
112
+
createTestSecret("at://did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
113
+
createTestSecret("at://did:plc:foo/repo", "api_key", "different_value", "did:plc:example123"),
115
+
expectError: []error{nil, ErrKeyAlreadyPresent},
119
+
for _, tt := range tests {
120
+
t.Run(tt.name, func(t *testing.T) {
121
+
manager := createInMemoryDB(t)
122
+
defer manager.db.Close()
124
+
for i, secret := range tt.secrets {
125
+
err := manager.AddSecret(secret)
126
+
if err != tt.expectError[i] {
127
+
t.Errorf("Secret %d: expected error %v, got %v", i, tt.expectError[i], err)
134
+
func TestSqliteManager_RemoveSecret(t *testing.T) {
135
+
tests := []struct {
137
+
setupSecrets []UnlockedSecret
138
+
removeSecret Secret[any]
142
+
name: "remove existing secret",
143
+
setupSecrets: []UnlockedSecret{
144
+
createTestSecret("at://did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
146
+
removeSecret: Secret[any]{
148
+
Repo: syntax.ATURI("at://did:plc:foo/repo"),
153
+
name: "remove non-existent secret",
154
+
setupSecrets: []UnlockedSecret{
155
+
createTestSecret("at://did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
157
+
removeSecret: Secret[any]{
158
+
Key: "non_existent_key",
159
+
Repo: syntax.ATURI("at://did:plc:foo/repo"),
161
+
expectError: ErrKeyNotFound,
164
+
name: "remove from empty database",
165
+
setupSecrets: []UnlockedSecret{},
166
+
removeSecret: Secret[any]{
168
+
Repo: syntax.ATURI("at://did:plc:foo/repo"),
170
+
expectError: ErrKeyNotFound,
173
+
name: "remove secret from wrong repo",
174
+
setupSecrets: []UnlockedSecret{
175
+
createTestSecret("at://did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
177
+
removeSecret: Secret[any]{
179
+
Repo: syntax.ATURI("at://other.com/repo"),
181
+
expectError: ErrKeyNotFound,
185
+
for _, tt := range tests {
186
+
t.Run(tt.name, func(t *testing.T) {
187
+
manager := createInMemoryDB(t)
188
+
defer manager.db.Close()
191
+
for _, secret := range tt.setupSecrets {
192
+
if err := manager.AddSecret(secret); err != nil {
193
+
t.Fatalf("Failed to setup secret: %v", err)
198
+
err := manager.RemoveSecret(tt.removeSecret)
199
+
if err != tt.expectError {
200
+
t.Errorf("Expected error %v, got %v", tt.expectError, err)
206
+
func TestSqliteManager_GetSecretsLocked(t *testing.T) {
207
+
tests := []struct {
209
+
setupSecrets []UnlockedSecret
210
+
queryRepo syntax.ATURI
212
+
expectedKeys []string
216
+
name: "get secrets for repo with multiple secrets",
217
+
setupSecrets: []UnlockedSecret{
218
+
createTestSecret("at://did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
219
+
createTestSecret("at://did:plc:foo/repo", "key2", "value2", "did:plc:user2"),
220
+
createTestSecret("at://other.com/repo", "key3", "value3", "did:plc:user3"),
222
+
queryRepo: syntax.ATURI("at://did:plc:foo/repo"),
224
+
expectedKeys: []string{"key1", "key2"},
225
+
expectError: false,
228
+
name: "get secrets for repo with single secret",
229
+
setupSecrets: []UnlockedSecret{
230
+
createTestSecret("at://did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"),
231
+
createTestSecret("at://other.com/repo", "other_key", "other_value", "did:plc:user2"),
233
+
queryRepo: syntax.ATURI("at://did:plc:foo/repo"),
235
+
expectedKeys: []string{"single_key"},
236
+
expectError: false,
239
+
name: "get secrets for non-existent repo",
240
+
setupSecrets: []UnlockedSecret{
241
+
createTestSecret("at://did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
243
+
queryRepo: syntax.ATURI("at://nonexistent.com/repo"),
245
+
expectedKeys: []string{},
246
+
expectError: false,
249
+
name: "get secrets from empty database",
250
+
setupSecrets: []UnlockedSecret{},
251
+
queryRepo: syntax.ATURI("at://did:plc:foo/repo"),
253
+
expectedKeys: []string{},
254
+
expectError: false,
258
+
for _, tt := range tests {
259
+
t.Run(tt.name, func(t *testing.T) {
260
+
manager := createInMemoryDB(t)
261
+
defer manager.db.Close()
264
+
for _, secret := range tt.setupSecrets {
265
+
if err := manager.AddSecret(secret); err != nil {
266
+
t.Fatalf("Failed to setup secret: %v", err)
270
+
// Test getting locked secrets
271
+
lockedSecrets, err := manager.GetSecretsLocked(tt.queryRepo)
272
+
if tt.expectError && err == nil {
273
+
t.Error("Expected error but got none")
276
+
if !tt.expectError && err != nil {
277
+
t.Fatalf("Unexpected error: %v", err)
280
+
if len(lockedSecrets) != tt.expectedCount {
281
+
t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(lockedSecrets))
284
+
// Verify keys and that values are not present (locked)
285
+
foundKeys := make(map[string]bool)
286
+
for _, ls := range lockedSecrets {
287
+
foundKeys[ls.Key] = true
288
+
if ls.Repo != tt.queryRepo {
289
+
t.Errorf("Expected repo %s, got %s", tt.queryRepo, ls.Repo)
291
+
if ls.CreatedBy == "" {
292
+
t.Error("Expected CreatedBy to be present")
294
+
if ls.CreatedAt.IsZero() {
295
+
t.Error("Expected CreatedAt to be set")
299
+
for _, expectedKey := range tt.expectedKeys {
300
+
if !foundKeys[expectedKey] {
301
+
t.Errorf("Expected key %s not found", expectedKey)
308
+
func TestSqliteManager_GetSecretsUnlocked(t *testing.T) {
309
+
tests := []struct {
311
+
setupSecrets []UnlockedSecret
312
+
queryRepo syntax.ATURI
314
+
expectedSecrets map[string]string // key -> value
318
+
name: "get unlocked secrets for repo with multiple secrets",
319
+
setupSecrets: []UnlockedSecret{
320
+
createTestSecret("at://did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
321
+
createTestSecret("at://did:plc:foo/repo", "key2", "value2", "did:plc:user2"),
322
+
createTestSecret("at://other.com/repo", "key3", "value3", "did:plc:user3"),
324
+
queryRepo: syntax.ATURI("at://did:plc:foo/repo"),
326
+
expectedSecrets: map[string]string{
330
+
expectError: false,
333
+
name: "get unlocked secrets for repo with single secret",
334
+
setupSecrets: []UnlockedSecret{
335
+
createTestSecret("at://did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"),
336
+
createTestSecret("at://other.com/repo", "other_key", "other_value", "did:plc:user2"),
338
+
queryRepo: syntax.ATURI("at://did:plc:foo/repo"),
340
+
expectedSecrets: map[string]string{
341
+
"single_key": "single_value",
343
+
expectError: false,
346
+
name: "get unlocked secrets for non-existent repo",
347
+
setupSecrets: []UnlockedSecret{
348
+
createTestSecret("at://did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
350
+
queryRepo: syntax.ATURI("at://nonexistent.com/repo"),
352
+
expectedSecrets: map[string]string{},
353
+
expectError: false,
356
+
name: "get unlocked secrets from empty database",
357
+
setupSecrets: []UnlockedSecret{},
358
+
queryRepo: syntax.ATURI("at://did:plc:foo/repo"),
360
+
expectedSecrets: map[string]string{},
361
+
expectError: false,
365
+
for _, tt := range tests {
366
+
t.Run(tt.name, func(t *testing.T) {
367
+
manager := createInMemoryDB(t)
368
+
defer manager.db.Close()
371
+
for _, secret := range tt.setupSecrets {
372
+
if err := manager.AddSecret(secret); err != nil {
373
+
t.Fatalf("Failed to setup secret: %v", err)
377
+
// Test getting unlocked secrets
378
+
unlockedSecrets, err := manager.GetSecretsUnlocked(tt.queryRepo)
379
+
if tt.expectError && err == nil {
380
+
t.Error("Expected error but got none")
383
+
if !tt.expectError && err != nil {
384
+
t.Fatalf("Unexpected error: %v", err)
387
+
if len(unlockedSecrets) != tt.expectedCount {
388
+
t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(unlockedSecrets))
391
+
// Verify keys, values, and metadata
392
+
for _, us := range unlockedSecrets {
393
+
expectedValue, exists := tt.expectedSecrets[us.Key]
395
+
t.Errorf("Unexpected key: %s", us.Key)
398
+
if us.Value != expectedValue {
399
+
t.Errorf("Expected value %s for key %s, got %s", expectedValue, us.Key, us.Value)
401
+
if us.Repo != tt.queryRepo {
402
+
t.Errorf("Expected repo %s, got %s", tt.queryRepo, us.Repo)
404
+
if us.CreatedBy == "" {
405
+
t.Error("Expected CreatedBy to be present")
407
+
if us.CreatedAt.IsZero() {
408
+
t.Error("Expected CreatedAt to be set")
415
+
// Test that demonstrates interface usage with table-driven tests
416
+
func TestManagerInterface_Usage(t *testing.T) {
417
+
tests := []struct {
419
+
operations []func(Manager) error
423
+
name: "successful workflow",
424
+
operations: []func(Manager) error{
425
+
func(m Manager) error {
426
+
secret := createTestSecret("at://interface.test/repo", "test_key", "test_value", "did:plc:user")
427
+
return m.AddSecret(secret)
429
+
func(m Manager) error {
430
+
_, err := m.GetSecretsLocked(syntax.ATURI("at://interface.test/repo"))
433
+
func(m Manager) error {
434
+
_, err := m.GetSecretsUnlocked(syntax.ATURI("at://interface.test/repo"))
437
+
func(m Manager) error {
438
+
secret := Secret[any]{
440
+
Repo: syntax.ATURI("at://interface.test/repo"),
442
+
return m.RemoveSecret(secret)
445
+
expectError: false,
448
+
name: "error on duplicate key",
449
+
operations: []func(Manager) error{
450
+
func(m Manager) error {
451
+
secret := createTestSecret("at://interface.test/repo", "dup_key", "value1", "did:plc:user")
452
+
return m.AddSecret(secret)
454
+
func(m Manager) error {
455
+
secret := createTestSecret("at://interface.test/repo", "dup_key", "value2", "did:plc:user")
456
+
return m.AddSecret(secret) // Should return ErrKeyAlreadyPresent
463
+
for _, tt := range tests {
464
+
t.Run(tt.name, func(t *testing.T) {
465
+
var manager Manager = createInMemoryDB(t)
467
+
if sqliteManager, ok := manager.(*SqliteManager); ok {
468
+
sqliteManager.db.Close()
473
+
for i, operation := range tt.operations {
474
+
if err := operation(manager); err != nil {
476
+
t.Logf("Operation %d returned error: %v", i, err)
480
+
if tt.expectError && finalErr == nil {
481
+
t.Error("Expected error but got none")
483
+
if !tt.expectError && finalErr != nil {
484
+
t.Errorf("Unexpected error: %v", finalErr)
490
+
// Integration test with table-driven scenarios
491
+
func TestSqliteManager_Integration(t *testing.T) {
492
+
tests := []struct {
494
+
scenario func(*testing.T, *SqliteManager)
497
+
name: "multi-repo secret management",
498
+
scenario: func(t *testing.T, manager *SqliteManager) {
499
+
repo1 := syntax.ATURI("at://example1.com/repo")
500
+
repo2 := syntax.ATURI("at://example2.com/repo")
502
+
secrets := []UnlockedSecret{
503
+
createTestSecret(string(repo1), "db_password", "super_secret_123", "did:plc:admin"),
504
+
createTestSecret(string(repo1), "api_key", "api_key_456", "did:plc:user1"),
505
+
createTestSecret(string(repo2), "token", "bearer_token_789", "did:plc:user2"),
509
+
for _, secret := range secrets {
510
+
if err := manager.AddSecret(secret); err != nil {
511
+
t.Fatalf("Failed to add secret %s: %v", secret.Key, err)
516
+
locked1, _ := manager.GetSecretsLocked(repo1)
517
+
locked2, _ := manager.GetSecretsLocked(repo2)
519
+
if len(locked1) != 2 {
520
+
t.Errorf("Expected 2 secrets for repo1, got %d", len(locked1))
522
+
if len(locked2) != 1 {
523
+
t.Errorf("Expected 1 secret for repo2, got %d", len(locked2))
526
+
// Remove and verify
527
+
secretToRemove := Secret[any]{Key: "db_password", Repo: repo1}
528
+
if err := manager.RemoveSecret(secretToRemove); err != nil {
529
+
t.Fatalf("Failed to remove secret: %v", err)
532
+
locked1After, _ := manager.GetSecretsLocked(repo1)
533
+
if len(locked1After) != 1 {
534
+
t.Errorf("Expected 1 secret for repo1 after removal, got %d", len(locked1After))
536
+
if locked1After[0].Key != "api_key" {
537
+
t.Errorf("Expected remaining secret to be 'api_key', got %s", locked1After[0].Key)
542
+
name: "empty database operations",
543
+
scenario: func(t *testing.T, manager *SqliteManager) {
544
+
repo := syntax.ATURI("at://empty.test/repo")
546
+
// Operations on empty database should not error
547
+
locked, err := manager.GetSecretsLocked(repo)
549
+
t.Errorf("GetSecretsLocked on empty DB failed: %v", err)
551
+
if len(locked) != 0 {
552
+
t.Errorf("Expected 0 secrets, got %d", len(locked))
555
+
unlocked, err := manager.GetSecretsUnlocked(repo)
557
+
t.Errorf("GetSecretsUnlocked on empty DB failed: %v", err)
559
+
if len(unlocked) != 0 {
560
+
t.Errorf("Expected 0 secrets, got %d", len(unlocked))
563
+
// Remove from empty should return ErrKeyNotFound
564
+
nonExistent := Secret[any]{Key: "none", Repo: repo}
565
+
err = manager.RemoveSecret(nonExistent)
566
+
if err != ErrKeyNotFound {
567
+
t.Errorf("Expected ErrKeyNotFound, got %v", err)
573
+
for _, tt := range tests {
574
+
t.Run(tt.name, func(t *testing.T) {
575
+
manager := createInMemoryDB(t)
576
+
defer manager.db.Close()
577
+
tt.scenario(t, manager)