package manager import ( "context" "sync" "testing" "time" "cloud.campbellwireless.net/git/barkstack/treatvault/internal/provider" "cloud.campbellwireless.net/git/barkstack/treatvault/internal/vault" "filippo.io/age" ) func TestRunSynchronizesExternalEncryptedFileChanges(t *testing.T) { identity, err := age.GenerateX25519Identity() if err != nil { t.Fatal(err) } path := t.TempDir() + "/secrets.age" if err := vault.Initialize(path, identity); err != nil { t.Fatal(err) } store := vault.New(path, identity) provider := &recordingProvider{synced: make(chan vault.Snapshot, 4)} manager := New(store, provider, 10*time.Millisecond) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go manager.Run(ctx) initial := waitSnapshot(t, provider.synced) if len(initial.Secrets) != 0 { t.Fatalf("initial secrets = %#v", initial.Secrets) } if _, err := store.Set("database_password", []byte("changed outside manager")); err != nil { t.Fatal(err) } changed := waitSnapshot(t, provider.synced) if string(changed.Secrets["database_password"].Value) != "changed outside manager" { t.Fatalf("changed snapshot = %#v", changed) } } func TestDeleteRefusesSecretInUse(t *testing.T) { identity, err := age.GenerateX25519Identity() if err != nil { t.Fatal(err) } path := t.TempDir() + "/secrets.age" if err := vault.Initialize(path, identity); err != nil { t.Fatal(err) } store := vault.New(path, identity) if _, err := store.Set("database_password", []byte("value")); err != nil { t.Fatal(err) } manager := New(store, &recordingProvider{inUse: true}, time.Second) if err := manager.Delete(context.Background(), "database_password"); !IsSecretInUse(err) { t.Fatalf("Delete() error = %v", err) } loaded, err := store.Load() if err != nil { t.Fatal(err) } if _, exists := loaded.Secrets["database_password"]; !exists { t.Fatal("in-use secret was removed from encrypted file") } } type recordingProvider struct { mu sync.Mutex synced chan vault.Snapshot inUse bool } func (p *recordingProvider) Sync(_ context.Context, snapshot vault.Snapshot) ([]provider.SecretState, error) { if p.synced != nil { p.synced <- snapshot } states := make([]provider.SecretState, 0, len(snapshot.Secrets)) for name, record := range snapshot.Secrets { states = append(states, provider.SecretState{Name: name, Revision: record.Revision, InUse: p.inUse}) } return states, nil } func (p *recordingProvider) InUse(context.Context, string) (bool, error) { return p.inUse, nil } func waitSnapshot(t *testing.T, snapshots <-chan vault.Snapshot) vault.Snapshot { t.Helper() select { case snapshot := <-snapshots: return snapshot case <-time.After(time.Second): t.Fatal("timed out waiting for synchronization") return vault.Snapshot{} } }