package app_test import ( "io" "os" "path/filepath" "strings" "testing" "proxyctl/internal/app" "proxyctl/internal/profile" ) func TestProfileConfigurationUsesIsolatedXDGPathOwnerOnlyAndAtomicReplacement(t *testing.T) { xdg := t.TempDir() t.Setenv("XDG_CONFIG_HOME", xdg) path, err := profile.ConfigPath() if err != nil { t.Fatalf("resolve XDG configuration: %v", err) } wantPath := filepath.Join(xdg, "proxyctl", "profiles.json") if path != wantPath { t.Fatalf("configuration path = %q, want %q", path, wantPath) } application, err := app.New(profile.NewFileStore(path)) if err != nil { t.Fatal(err) } created := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{ Name: "private", Mode: profile.SingleMode, SingleURL: "http://alice:secret@proxy.example:8080", }}) if created.Err != nil { t.Fatal(created.Err) } assertMode(t, filepath.Dir(path), 0o700) assertMode(t, path, 0o600) data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "\n \"profiles\"") { t.Fatalf("Profile configuration is not human-readable JSON:\n%s", data) } oldFile, err := os.Open(path) if err != nil { t.Fatal(err) } defer oldFile.Close() updated := application.Dispatch(app.Intent{Kind: app.UpdateProfile, Name: "private", Profile: profile.Profile{ Name: "renamed", Mode: profile.SingleMode, SingleURL: "http://alice:new-secret@proxy.example:8080", }}) if updated.Err != nil { t.Fatal(updated.Err) } oldData, err := io.ReadAll(oldFile) if err != nil { t.Fatal(err) } newData, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if !strings.Contains(string(oldData), `"name": "private"`) || !strings.Contains(string(newData), `"name": "renamed"`) { t.Fatalf("configuration was not atomically replaced; old=%s new=%s", oldData, newData) } matches, err := filepath.Glob(filepath.Join(filepath.Dir(path), ".profiles-*")) if err != nil || len(matches) != 0 { t.Fatalf("temporary Profile files left behind: %v (%v)", matches, err) } } func assertMode(t *testing.T, path string, want os.FileMode) { t.Helper() info, err := os.Stat(path) if err != nil { t.Fatal(err) } if got := info.Mode().Perm(); got != want { t.Fatalf("mode for %s = %#o, want %#o", path, got, want) } }