diff --git a/cmd/doctor/doctor_auth_test.go b/cmd/doctor/doctor_auth_test.go index 6c295be..ba15117 100644 --- a/cmd/doctor/doctor_auth_test.go +++ b/cmd/doctor/doctor_auth_test.go @@ -33,6 +33,8 @@ func authLine(out string) string { func TestDoctorCmd_AuthFailsWhenCredentialIsRefused(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.URL.Path == khhttp.CredentialProbePath { @@ -55,6 +57,8 @@ func TestDoctorCmd_AuthFailsWhenCredentialIsRefused(t *testing.T) { func TestDoctorCmd_AuthPassesWhenCredentialIsAccepted(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -71,6 +75,8 @@ func TestDoctorCmd_AuthPassesWhenCredentialIsAccepted(t *testing.T) { func TestDoctorCmd_AuthDoesNotProbeTheAnonymousTolerantEndpoint(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. // Doctor fans its checks out across goroutines, so the handler runs // concurrently and the record of seen paths has to be guarded. var mu sync.Mutex diff --git a/cmd/doctor/doctor_test.go b/cmd/doctor/doctor_test.go index dd24ddf..fc2e4de 100644 --- a/cmd/doctor/doctor_test.go +++ b/cmd/doctor/doctor_test.go @@ -43,7 +43,8 @@ func newDoctorFactory(ios *iostreams.IOStreams, svr *httptest.Server) *cmdutil.F // all 6 check names appear in output and the command exits 0. func TestDoctorCmd_AllPass(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch r.URL.Path { @@ -85,7 +86,8 @@ func TestDoctorCmd_AllPass(t *testing.T) { // TestDoctorCmd_OneFail verifies that a failing check causes a non-zero exit. func TestDoctorCmd_OneFail(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -122,7 +124,8 @@ func TestDoctorCmd_OneFail(t *testing.T) { // TestDoctorCmd_WarnOnly verifies that warnings alone yield exit 0. func TestDoctorCmd_WarnOnly(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -162,7 +165,8 @@ func TestDoctorCmd_WarnOnly(t *testing.T) { // TestDoctorCmd_JSON verifies --json outputs a JSON array with exactly 6 objects. func TestDoctorCmd_JSON(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -208,7 +212,8 @@ func TestDoctorCmd_Timeout(t *testing.T) { t.Skip("skipping timeout test in short mode") } t.Setenv("XDG_CONFIG_HOME", t.TempDir()) - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -247,7 +252,8 @@ func TestDoctorCmd_Timeout(t *testing.T) { // TestDoctorCmd_Output verifies all 6 check names appear in non-JSON output. func TestDoctorCmd_Output(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -281,7 +287,8 @@ func TestDoctorCmd_NoLocalJSONFlag(t *testing.T) { // TestDoctorCmd_ExitCodeOnFail verifies SilentError is returned on [fail] checks. func TestDoctorCmd_ExitCodeOnFail(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { @@ -333,14 +340,28 @@ func walletServer(t *testing.T, userBody string) *httptest.Server { })) } +// setHome points the home directory at home for the duration of the test. +// os.UserHomeDir reads $HOME on Unix but %USERPROFILE% on Windows, so a +// fixture that sets only HOME leaves the real profile in play on Windows. +func setHome(t *testing.T, home string) { + t.Helper() + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) +} + func runDoctor(t *testing.T, svr *httptest.Server) string { t.Helper() t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + // Cleared unconditionally. ResolveHost reads KH_HOST ahead of the factory's + // DefaultHost, so an exported KH_HOST sends these checks to the live host + // instead of svr. That is unrelated to the temporary profile below, so it + // does not belong behind KH_TEST_KEEP_HOME. + t.Setenv("KH_HOST", "") // HOME too: the agentic check resolves ~/.keeperhub/wallet.json through // os.UserHomeDir, so without this the suite reads a real credential off the // developer's machine and signs a live request with it. if os.Getenv("KH_TEST_KEEP_HOME") == "" { - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) } ios, outBuf, _, _ := iostreams.Test() tc := doctor.NewTestableCmd(newDoctorFactory(ios, svr)) @@ -389,7 +410,7 @@ func agenticWallet(t *testing.T) (secret, subOrg, addr string) { t.Helper() secret, subOrg, addr = "s3cr3t-test-key", "sub_abc123", "0xABCD1234EF567890ABCD1234EF567890ABCD1234" home := t.TempDir() - t.Setenv("HOME", home) + setHome(t, home) t.Setenv("KH_TEST_KEEP_HOME", "1") require.NoError(t, os.MkdirAll(filepath.Join(home, ".keeperhub"), 0o700)) body := `{"subOrgId":"` + subOrg + `","walletAddress":"` + addr + `","hmacSecret":"` + secret + `"}` @@ -461,7 +482,7 @@ func TestDoctorCmd_AgenticWalletRejectedSignature(t *testing.T) { } func TestDoctorCmd_AgenticWalletNotConfigured(t *testing.T) { - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) svr := walletServer(t, `{"walletAddress":"0xABC"}`) defer svr.Close() @@ -536,7 +557,7 @@ func TestDoctorCmd_AgenticUnknownSubOrgIsActionable(t *testing.T) { // Absent is the normal state on most installs, so it must not warn. func TestDoctorCmd_AgenticNotConfiguredIsNotAWarning(t *testing.T) { - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) svr := walletServer(t, `{"walletAddress":"0xABC"}`) defer svr.Close() diff --git a/cmd/doctor/doctor_version_test.go b/cmd/doctor/doctor_version_test.go index 4b5e6eb..0a2c0df 100644 --- a/cmd/doctor/doctor_version_test.go +++ b/cmd/doctor/doctor_version_test.go @@ -59,6 +59,8 @@ func versionDoctorFactory(ios *iostreams.IOStreams, appVersion, minimumHeader st func TestDoctorCmd_CLIVersionPassesWhenServerAdvertisesNoMinimum(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. ios, outBuf, _, _ := iostreams.Test() tc := doctor.NewTestableCmd(versionDoctorFactory(ios, "1.2.3", "")) require.NoError(t, tc.Execute([]string{})) @@ -70,6 +72,8 @@ func TestDoctorCmd_CLIVersionPassesWhenServerAdvertisesNoMinimum(t *testing.T) { func TestDoctorCmd_CLIVersionWarnsWhenBelowServerMinimum(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. ios, outBuf, _, _ := iostreams.Test() tc := doctor.NewTestableCmd(versionDoctorFactory(ios, "0.3.0", "0.11.1")) require.NoError(t, tc.Execute([]string{})) @@ -83,6 +87,8 @@ func TestDoctorCmd_CLIVersionWarnsWhenBelowServerMinimum(t *testing.T) { func TestDoctorCmd_CLIVersionPassesWhenAtServerMinimum(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. ios, outBuf, _, _ := iostreams.Test() tc := doctor.NewTestableCmd(versionDoctorFactory(ios, "0.11.1", "0.11.1")) require.NoError(t, tc.Execute([]string{})) @@ -94,6 +100,8 @@ func TestDoctorCmd_CLIVersionPassesWhenAtServerMinimum(t *testing.T) { func TestDoctorCmd_CLIVersionReportsDevBuildWithoutComparing(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + setHome(t, t.TempDir()) + t.Setenv("KH_HOST", "") // ResolveHost reads it ahead of the factory's DefaultHost. ios, outBuf, _, _ := iostreams.Test() // A minimum far above any real release: if dev builds were compared, // this would incorrectly warn. diff --git a/internal/agentic/wallet_test.go b/internal/agentic/wallet_test.go index 7f573a4..6c5b863 100644 --- a/internal/agentic/wallet_test.go +++ b/internal/agentic/wallet_test.go @@ -49,10 +49,19 @@ func TestSign_EveryFieldIsBound(t *testing.T) { } } +// setHome points the home directory at home for the duration of the test. +// os.UserHomeDir reads $HOME on Unix but %USERPROFILE% on Windows, so a +// fixture that sets only HOME leaves the real profile in play on Windows. +func setHome(t *testing.T, home string) { + t.Helper() + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) +} + func writeWallet(t *testing.T, body string) { t.Helper() home := t.TempDir() - t.Setenv("HOME", home) + setHome(t, home) require.NoError(t, os.MkdirAll(filepath.Join(home, ".keeperhub"), 0o700)) require.NoError(t, os.WriteFile( filepath.Join(home, ".keeperhub", agentic.ConfigName), []byte(body), 0o600, @@ -70,7 +79,7 @@ func TestLoad_ReadsTheWallet(t *testing.T) { } func TestLoad_MissingFileIsNotConfigured(t *testing.T) { - t.Setenv("HOME", t.TempDir()) + setHome(t, t.TempDir()) _, err := agentic.Load()