137 lines
3.9 KiB
Go
137 lines
3.9 KiB
Go
package clicmd
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestSaveConfigIsOwnerOnlyAndAtomic(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "nested")
|
|
path := filepath.Join(dir, "config.json")
|
|
|
|
want := ConfigFile{Server: "https://p.example.com", Token: "pgs_abcdefghijklmnop_secret", Output: "json"}
|
|
if err := SaveConfig(path, want); err != nil {
|
|
t.Fatalf("SaveConfig: %v", err)
|
|
}
|
|
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if perm := fi.Mode().Perm(); perm != 0o600 {
|
|
t.Errorf("config mode = %#o, want 0600 — it holds a token", perm)
|
|
}
|
|
if di, err := os.Stat(dir); err == nil {
|
|
if perm := di.Mode().Perm(); perm&0o077 != 0 {
|
|
t.Errorf("config directory mode = %#o, want no group or other bits", perm)
|
|
}
|
|
}
|
|
|
|
got, err := LoadConfig(path, io.Discard)
|
|
if err != nil {
|
|
t.Fatalf("LoadConfig: %v", err)
|
|
}
|
|
if got != want {
|
|
t.Errorf("round trip = %+v, want %+v", got, want)
|
|
}
|
|
|
|
// The temporary file is written in the destination directory; leaving one
|
|
// behind would leave a mode-0600 copy of the token lying around.
|
|
ents, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(ents) != 1 || ents[0].Name() != "config.json" {
|
|
names := make([]string, len(ents))
|
|
for i, e := range ents {
|
|
names[i] = e.Name()
|
|
}
|
|
t.Errorf("directory contains %q, want only config.json", names)
|
|
}
|
|
}
|
|
|
|
func TestSaveConfigReplacesInPlace(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.json")
|
|
if err := SaveConfig(path, ConfigFile{Server: "https://one.example.com"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := SaveConfig(path, ConfigFile{Server: "https://two.example.com"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := LoadConfig(path, io.Discard)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Server != "https://two.example.com" {
|
|
t.Errorf("server = %q, want the second write", got.Server)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfig(t *testing.T) {
|
|
t.Run("a missing file is the normal state on CI", func(t *testing.T) {
|
|
got, err := LoadConfig(filepath.Join(t.TempDir(), "absent.json"), io.Discard)
|
|
if err != nil {
|
|
t.Fatalf("err = %v, want nil", err)
|
|
}
|
|
if got != (ConfigFile{}) {
|
|
t.Errorf("got %+v, want the zero value", got)
|
|
}
|
|
})
|
|
|
|
t.Run("an empty path means there is no config file", func(t *testing.T) {
|
|
if _, err := LoadConfig("", io.Discard); err != nil {
|
|
t.Fatalf("err = %v, want nil", err)
|
|
}
|
|
})
|
|
|
|
t.Run("malformed JSON is reported, not ignored", func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.json")
|
|
os.WriteFile(path, []byte("{not json"), 0o600)
|
|
if _, err := LoadConfig(path, io.Discard); err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
})
|
|
|
|
t.Run("a readable-by-others config warns", func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.json")
|
|
os.WriteFile(path, []byte(`{"token":"pgs_abcdefghijklmnop_secret"}`), 0o644)
|
|
var warn strings.Builder
|
|
if _, err := LoadConfig(path, &warn); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(warn.String(), "chmod 600") {
|
|
t.Errorf("warning = %q, want it to say how to fix the mode", warn.String())
|
|
}
|
|
if strings.Contains(warn.String(), "secret") {
|
|
t.Error("the warning printed the token it was warning about")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRedactToken(t *testing.T) {
|
|
cases := []struct{ in, want string }{
|
|
{"pgs_abcdefghijklmnop_thesecrethalf", "pgs_abcdefghijklmnop_…"},
|
|
{"", ""},
|
|
{"garbage", "…"},
|
|
{"pgs_onlytwo", "…"},
|
|
// The secret half is base64url, so it can itself contain underscores;
|
|
// SplitN with n=3 keeps them in the part that gets dropped.
|
|
{"pgs_abcdefghijklmnop_a_b_c", "pgs_abcdefghijklmnop_…"},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := redactToken(tc.in); got != tc.want {
|
|
t.Errorf("redactToken(%q) = %q, want %q", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDefaultConfigPathPrefersEnv(t *testing.T) {
|
|
t.Setenv("PAGES_CONFIG", "/tmp/explicit.json")
|
|
if got := DefaultConfigPath(); got != "/tmp/explicit.json" {
|
|
t.Errorf("DefaultConfigPath = %q", got)
|
|
}
|
|
}
|