forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}