package client import ( "encoding/json" "os" "path/filepath" "strings" "testing" ) func writeConfigFile(t *testing.T, path, repoURL string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } data, err := json.Marshal(map[string]string{"repo_url": repoURL, "ref": "main"}) if err != nil { t.Fatal(err) } if err := os.WriteFile(path, data, 0o600); err != nil { t.Fatal(err) } } // runIsolated points HOME/XDG at empty temp dirs and clears the override env // so chain tests never see the real machine's config. func runIsolated(t *testing.T, fn func()) { t.Helper() base := t.TempDir() home := filepath.Join(base, "home") xdg := filepath.Join(base, "xdg-config") for _, d := range []string{home, xdg} { if err := os.MkdirAll(d, 0o755); err != nil { t.Fatal(err) } } t.Setenv("HOME", home) t.Setenv("XDG_CONFIG_HOME", xdg) t.Setenv(ConfigEnv, "") fn() } func TestUserConfigPathHonorsXDG(t *testing.T) { runIsolated(t, func() { base := t.TempDir() t.Setenv("XDG_CONFIG_HOME", base) want := filepath.Join(base, "agent-lib", "config.json") if got := UserConfigPath(); got != want { t.Errorf("UserConfigPath() = %q, want %q", got, want) } }) } func TestLoadClientConfigChainOrder(t *testing.T) { runIsolated(t, func() { base := t.TempDir() flagCfg := filepath.Join(base, "flag.json") envCfg := filepath.Join(base, "env.json") userCfg := filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "agent-lib", "config.json") writeConfigFile(t, flagCfg, "https://flag.example/repo") writeConfigFile(t, envCfg, "https://env.example/repo") writeConfigFile(t, userCfg, "https://user.example/repo") assertRepo := func(name, got, want string) { t.Helper() if got != want { t.Errorf("%s: repo_url = %q, want %q", name, got, want) } } cfg, err := LoadClientConfig(flagCfg) if err != nil { t.Fatal(err) } assertRepo("flag beats env and user", cfg.RepoURL, "https://flag.example/repo") t.Setenv(ConfigEnv, envCfg) cfg, err = LoadClientConfig("") if err != nil { t.Fatal(err) } assertRepo("env beats user", cfg.RepoURL, "https://env.example/repo") t.Setenv(ConfigEnv, "") cfg, err = LoadClientConfig("") if err != nil { t.Fatal(err) } assertRepo("user picked up without flags", cfg.RepoURL, "https://user.example/repo") }) } func TestLoadClientConfigMissingIsActionable(t *testing.T) { runIsolated(t, func() { _, err := LoadClientConfig("") if err == nil { t.Fatal("empty chain must fail") } var missing *MissingConfigError if e, ok := err.(*MissingConfigError); !ok { t.Fatalf("error must be MissingConfigError, got %T: %v", err, err) } else { missing = e } msg := missing.Error() userPath := UserConfigPath() for _, want := range []string{ "no client config found", userPath, DefaultConfigPath(), `"repo_url"`, "--repo", } { if !strings.Contains(msg, want) { t.Errorf("error message must mention %q:\n%s", want, msg) } } }) } func TestLoadOptionalConfigNilWhenEmpty(t *testing.T) { runIsolated(t, func() { cfg, err := LoadOptionalConfig("") if err != nil { t.Fatalf("empty chain must be (nil, nil), got %v", err) } if cfg != nil { t.Errorf("cfg must be nil, got %+v", cfg) } }) } func TestLoadOptionalConfigMalformedStillFails(t *testing.T) { runIsolated(t, func() { userCfg := filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "agent-lib", "config.json") writeConfigFile(t, userCfg, "") if _, err := LoadOptionalConfig(""); err == nil { t.Error("user config with empty repo_url must fail even in optional mode") } }) } func TestLoadClientConfigExplicitFlagMustExist(t *testing.T) { runIsolated(t, func() { _, err := LoadClientConfig(filepath.Join(t.TempDir(), "nope.json")) if err == nil || !strings.Contains(err.Error(), "nope.json") { t.Errorf("explicit --config pointing nowhere must name the path, got %v", err) } }) } func TestFillTokenAuthEnvOnlyFillsEmptyToken(t *testing.T) { t.Setenv("AGENT_LIB_GIT_TOKEN", "env-token") t.Setenv("AGENT_LIB_GIT_USER", "env-user") fromEnv := &ClientConfig{} fromEnv.FillTokenAuth() if fromEnv.Token != "env-token" || fromEnv.TokenUser != "env-user" { t.Errorf("empty token must be filled from env: %+v", fromEnv) } configured := &ClientConfig{Token: "config-token", TokenUser: "config-user"} configured.FillTokenAuth() if configured.Token != "config-token" || configured.TokenUser != "config-user" { t.Errorf("env must never override a config token: %+v", configured) } }