package secrets_test import ( "context" "errors" "os" "path/filepath" "testing" "git.d-ma.be/mathias/tapir/internal/adapters/secrets" ) func TestPutThenGet(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", "secrets.json") s := secrets.NewFileStore(path) if err := s.Put("youtube/refresh_token", "rt-123"); err != nil { t.Fatalf("Put: %v", err) } got, err := s.Get(context.Background(), "youtube/refresh_token") if err != nil { t.Fatalf("Get: %v", err) } if got != "rt-123" { t.Errorf("Get = %q, want %q", got, "rt-123") } } func TestGetUnknownRef(t *testing.T) { s := secrets.NewFileStore(filepath.Join(t.TempDir(), "secrets.json")) _, err := s.Get(context.Background(), "missing") if !errors.Is(err, secrets.ErrNotFound) { t.Errorf("err = %v, want ErrNotFound", err) } } func TestPutIsOwnerOnly(t *testing.T) { path := filepath.Join(t.TempDir(), "secrets.json") s := secrets.NewFileStore(path) if err := s.Put("k", "v"); err != nil { t.Fatalf("Put: %v", err) } info, err := os.Stat(path) if err != nil { t.Fatalf("Stat: %v", err) } if perm := info.Mode().Perm(); perm != 0o600 { t.Errorf("file perm = %o, want 0600 (token must not be world-readable)", perm) } } func TestDeleteRemovesRefAndLeavesOthers(t *testing.T) { path := filepath.Join(t.TempDir(), "secrets.json") s := secrets.NewFileStore(path) if err := s.Put("youtube/u1/refresh_token", "rt-1"); err != nil { t.Fatalf("Put: %v", err) } if err := s.Put("youtube/u2/refresh_token", "rt-2"); err != nil { t.Fatalf("Put: %v", err) } if err := s.Delete("youtube/u1/refresh_token"); err != nil { t.Fatalf("Delete: %v", err) } // The deleted ref is gone (persisted: re-open from disk)... s2 := secrets.NewFileStore(path) if _, err := s2.Get(context.Background(), "youtube/u1/refresh_token"); !errors.Is(err, secrets.ErrNotFound) { t.Errorf("Get deleted ref: err = %v, want ErrNotFound", err) } // ...and the other user's secret survives. if got, err := s2.Get(context.Background(), "youtube/u2/refresh_token"); err != nil || got != "rt-2" { t.Errorf("Get surviving ref = (%q, %v), want (%q, nil)", got, err, "rt-2") } } func TestDeleteAbsentRefIsNoop(t *testing.T) { // Deleting an unknown ref — or from a file that does not exist yet — is a // no-op, not an error (mirrors store.DeleteConnection semantics). s := secrets.NewFileStore(filepath.Join(t.TempDir(), "secrets.json")) if err := s.Delete("missing"); err != nil { t.Errorf("Delete absent ref: %v, want nil", err) } } func TestPutMergesEntries(t *testing.T) { path := filepath.Join(t.TempDir(), "secrets.json") s := secrets.NewFileStore(path) if err := s.Put("a", "1"); err != nil { t.Fatalf("Put a: %v", err) } if err := s.Put("b", "2"); err != nil { t.Fatalf("Put b: %v", err) } // Re-open from disk to prove persistence, not in-memory state. s2 := secrets.NewFileStore(path) for k, want := range map[string]string{"a": "1", "b": "2"} { got, err := s2.Get(context.Background(), k) if err != nil { t.Fatalf("Get %q: %v", k, err) } if got != want { t.Errorf("Get %q = %q, want %q", k, got, want) } } }