package config import ( "os" "path/filepath" "testing" ) // writeUserConfig points XDG_CONFIG_HOME at a temporary directory and puts a // config file in it, so tests never touch the real one. func writeUserConfig(t *testing.T, body string) string { t.Helper() dir := t.TempDir() t.Setenv("XDG_CONFIG_HOME", dir) if body == "" { return dir } cfgDir := filepath.Join(dir, "money") if err := os.MkdirAll(cfgDir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(cfgDir, "config.toml"), []byte(body), 0o644); err != nil { t.Fatal(err) } return dir } func TestResolveRootPrecedence(t *testing.T) { home, err := os.UserHomeDir() if err != nil { t.Skip("no home directory in this environment") } t.Run("flag beats everything", func(t *testing.T) { writeUserConfig(t, "root = \"/from/config\"\n") t.Setenv("MONEY_ROOT", "/from/env") root, source, err := ResolveRoot("/from/flag") if err != nil { t.Fatal(err) } if root != "/from/flag" || source != RootFromFlag { t.Errorf("got %q from %q, want /from/flag from the flag", root, source) } }) t.Run("env beats config", func(t *testing.T) { writeUserConfig(t, "root = \"/from/config\"\n") t.Setenv("MONEY_ROOT", "/from/env") root, source, err := ResolveRoot("") if err != nil { t.Fatal(err) } if root != "/from/env" || source != RootFromEnv { t.Errorf("got %q from %q, want /from/env from the environment", root, source) } }) t.Run("config beats the default", func(t *testing.T) { writeUserConfig(t, "root = \"/from/config\"\n") t.Setenv("MONEY_ROOT", "") root, source, err := ResolveRoot("") if err != nil { t.Fatal(err) } if root != "/from/config" || source != RootFromConfig { t.Errorf("got %q from %q, want /from/config from the config file", root, source) } }) t.Run("default when nothing is set", func(t *testing.T) { writeUserConfig(t, "") // XDG dir exists but holds no config file t.Setenv("MONEY_ROOT", "") root, source, err := ResolveRoot("") if err != nil { t.Fatal(err) } want := filepath.Join(home, "money") if root != want || source != RootFromDefault { t.Errorf("got %q from %q, want %q from the default", root, source, want) } }) // A hand-written config file is likely to contain a tilde, which no shell // has expanded for us. t.Run("tilde in the config file is expanded", func(t *testing.T) { writeUserConfig(t, "root = \"~/finances\"\n") t.Setenv("MONEY_ROOT", "") root, _, err := ResolveRoot("") if err != nil { t.Fatal(err) } want := filepath.Join(home, "finances") if root != want { t.Errorf("root = %q, want %q", root, want) } }) t.Run("relative paths become absolute", func(t *testing.T) { writeUserConfig(t, "") t.Setenv("MONEY_ROOT", "some/relative/dir") root, _, err := ResolveRoot("") if err != nil { t.Fatal(err) } if !filepath.IsAbs(root) { t.Errorf("root = %q, want an absolute path", root) } }) } func TestUserConfigPathFollowsXDG(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", "/custom/config") path, err := UserConfigPath() if err != nil { t.Fatal(err) } if want := "/custom/config/money/config.toml"; path != want { t.Errorf("path = %q, want %q", path, want) } // Unset, it falls back to ~/.config. t.Setenv("XDG_CONFIG_HOME", "") home, err := os.UserHomeDir() if err != nil { t.Skip("no home directory in this environment") } path, err = UserConfigPath() if err != nil { t.Fatal(err) } if want := filepath.Join(home, ".config", "money", "config.toml"); path != want { t.Errorf("path = %q, want %q", path, want) } } // A missing config file is normal, not an error. func TestLoadUserConfigMissingFile(t *testing.T) { writeUserConfig(t, "") c, path, err := LoadUserConfig() if err != nil { t.Fatalf("a missing config file should not be an error: %v", err) } if c.Root != "" { t.Errorf("root = %q, want empty", c.Root) } if path == "" { t.Error("expected the path to be reported even when the file is absent") } } // A malformed config file is an error, so a typo does not silently send the // tool to the wrong directory. func TestLoadUserConfigMalformed(t *testing.T) { writeUserConfig(t, "root = this is not valid toml\n") if _, _, err := LoadUserConfig(); err == nil { t.Error("expected an error for a malformed config file") } }