From 4337fdc259351aafa9402c7555b90152b8943702 Mon Sep 17 00:00:00 2001 From: li-jin-quan <18617339965@163.com> Date: Tue, 15 Sep 2026 14:57:04 +0800 Subject: [PATCH 1/4] test: #104 set USERPROFILE alongside HOME in the doctor and agentic fixtures The doctor and agentic suites isolate themselves from a real wallet by pointing HOME at a temp dir, but the code under test resolves that path with os.UserHomeDir, which reads $HOME on Unix and %USERPROFILE% on Windows. On Windows the redirection never took effect: 8 doctor tests and 5 agentic tests fail on a clean checkout, and the agentic suite reads the developer's real %USERPROFILE%\.keeperhub\wallet.json while it runs. Both packages now carry a four-line setHome helper that sets the two variables, used at all 13 fixture sites. runDoctor keeps the call inside the existing KH_TEST_KEEP_HOME conditional and agenticWallet keeps setting that flag - same shape as before, both variables instead of one. Verified on Windows (go 1.26.3), with a real wallet file present in the profile throughout: the two packages go from 13 failures to green, and go test ./... passes across all 35 packages. --- cmd/doctor/doctor_test.go | 31 ++++++++++++++++++++----------- internal/agentic/wallet_test.go | 13 +++++++++++-- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/cmd/doctor/doctor_test.go b/cmd/doctor/doctor_test.go index dd24ddf..4002d9f 100644 --- a/cmd/doctor/doctor_test.go +++ b/cmd/doctor/doctor_test.go @@ -43,7 +43,7 @@ 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()) 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 +85,7 @@ 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()) svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -122,7 +122,7 @@ 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()) svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -162,7 +162,7 @@ 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()) svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -208,7 +208,7 @@ 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()) svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -247,7 +247,7 @@ 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()) svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -281,7 +281,7 @@ 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()) svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { @@ -333,6 +333,15 @@ 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()) @@ -340,7 +349,7 @@ func runDoctor(t *testing.T, svr *httptest.Server) string { // 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 +398,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 +470,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 +545,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/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() From 456e9bf43b78c9dd5726788e77ef995a1fba00cc Mon Sep 17 00:00:00 2001 From: Li Jinquan <18617339965@163.com> Date: Wed, 16 Sep 2026 13:33:21 +0800 Subject: [PATCH 2/4] test: clear KH_HOST in the doctor fixtures ResolveHost reads KH_HOST ahead of the factory's DefaultHost, so an exported KH_HOST sent doctor's checks to the live host instead of the httptest server. Reproduced with KH_HOST=https://app.keeperhub.com go test ./cmd/doctor/ \ -run TestDoctorCmd_AgenticWalletSignatureIsAccepted the suite reaches app.keeperhub.com and the assertion fails with nothing pointing at the cause. setHome now clears KH_HOST, and the three probes in doctor_auth_test.go that build their own server get the same line. --- cmd/doctor/doctor_auth_test.go | 3 +++ cmd/doctor/doctor_test.go | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/cmd/doctor/doctor_auth_test.go b/cmd/doctor/doctor_auth_test.go index 6c295be..5059998 100644 --- a/cmd/doctor/doctor_auth_test.go +++ b/cmd/doctor/doctor_auth_test.go @@ -33,6 +33,7 @@ func authLine(out string) string { func TestDoctorCmd_AuthFailsWhenCredentialIsRefused(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", 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 +56,7 @@ func TestDoctorCmd_AuthFailsWhenCredentialIsRefused(t *testing.T) { func TestDoctorCmd_AuthPassesWhenCredentialIsAccepted(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", 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 +73,7 @@ func TestDoctorCmd_AuthPassesWhenCredentialIsAccepted(t *testing.T) { func TestDoctorCmd_AuthDoesNotProbeTheAnonymousTolerantEndpoint(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", 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 4002d9f..7cccf2f 100644 --- a/cmd/doctor/doctor_test.go +++ b/cmd/doctor/doctor_test.go @@ -336,10 +336,16 @@ 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. +// +// It also clears KH_HOST. ResolveHost reads that variable ahead of the +// factory's DefaultHost, so an exported KH_HOST sends these checks to the live +// host instead of the test server and the assertions fail with nothing +// pointing at the cause. func setHome(t *testing.T, home string) { t.Helper() t.Setenv("HOME", home) t.Setenv("USERPROFILE", home) + t.Setenv("KH_HOST", "") } func runDoctor(t *testing.T, svr *httptest.Server) string { From 365aa5cad5da065901bc5d5efe9c4710a3c85f0a Mon Sep 17 00:00:00 2001 From: Li Jinquan <18617339965@163.com> Date: Wed, 16 Sep 2026 15:46:57 +0800 Subject: [PATCH 3/4] test: clear KH_HOST in the CLI-version fixtures too The four TestDoctorCmd_CLIVersion* tests isolate with XDG_CONFIG_HOME alone, so an exported KH_HOST sent checkCLIVersion to that host instead of the server versionDoctorFactory pins in Config.DefaultHost. Checked against a local server advertising 0.12.0: KH_HOST=http://127.0.0.1:18099 go test ./cmd/doctor/ \ -run TestDoctorCmd_CLIVersion --- FAIL: TestDoctorCmd_CLIVersionWarnsWhenBelowServerMinimum --- FAIL: TestDoctorCmd_CLIVersionPassesWhenAtServerMinimum Against app.keeperhub.com they happen to pass today, because its floor is 0.11.1 -- exactly the version the fixtures mock. They break as soon as that floor moves past a mocked version. Same one-line fix as the auth tests. --- cmd/doctor/doctor_version_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/doctor/doctor_version_test.go b/cmd/doctor/doctor_version_test.go index 4b5e6eb..d6b969c 100644 --- a/cmd/doctor/doctor_version_test.go +++ b/cmd/doctor/doctor_version_test.go @@ -59,6 +59,7 @@ func versionDoctorFactory(ios *iostreams.IOStreams, appVersion, minimumHeader st func TestDoctorCmd_CLIVersionPassesWhenServerAdvertisesNoMinimum(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", 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 +71,7 @@ func TestDoctorCmd_CLIVersionPassesWhenServerAdvertisesNoMinimum(t *testing.T) { func TestDoctorCmd_CLIVersionWarnsWhenBelowServerMinimum(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", 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 +85,7 @@ func TestDoctorCmd_CLIVersionWarnsWhenBelowServerMinimum(t *testing.T) { func TestDoctorCmd_CLIVersionPassesWhenAtServerMinimum(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", 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 +97,7 @@ func TestDoctorCmd_CLIVersionPassesWhenAtServerMinimum(t *testing.T) { func TestDoctorCmd_CLIVersionReportsDevBuildWithoutComparing(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", 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. From 303e75c1ee542963dd6f8bd938a00ef13642b940 Mon Sep 17 00:00:00 2001 From: li-jin-quan <18617339965@163.com> Date: Thu, 17 Sep 2026 16:49:58 +0800 Subject: [PATCH 4/4] test: keep KH_HOST out of setHome and clear it at the direct-Execute fixtures setHome is about the home directory, and runDoctor gates it on KH_TEST_KEEP_HOME, so carrying KH_HOST inside it handed the host guard a conditional that only exists for the profile. Drop it from setHome and clear it unconditionally at the top of runDoctor instead. Removing it from setHome also removed the clear that seven fixtures were getting for free: the seven in doctor_test.go that call tc.Execute themselves rather than runDoctor - AllPass, OneFail, WarnOnly, JSON, Timeout, Output and ExitCodeOnFail. Each now clears KH_HOST on its own line, beside its setHome call. The three probes in doctor_auth_test.go and the four TestDoctorCmd_CLIVersion* tests already carry their own clear from the previous two commits; this only adds setHome to them, which is the home half they were missing. Reproduced on Windows, go 1.26.3, by exporting KH_HOST and taking those seven clears back off: KH_HOST=https://live.invalid go test ./cmd/doctor/ --- FAIL: TestDoctorCmd_AllPass --- FAIL: TestDoctorCmd_WarnOnly --- FAIL: TestDoctorCmd_JSON Green with them, and green on 365aa5c either way, because setHome used to supply the clear as a side effect. go test ./... passes across the 35 packages, go vet ./... is clean, and go test -race ./cmd/doctor/ ./internal/agentic/ passes with a C toolchain on PATH, which the opening commit said this machine did not have. --- cmd/doctor/doctor_auth_test.go | 3 +++ cmd/doctor/doctor_test.go | 18 ++++++++++++------ cmd/doctor/doctor_version_test.go | 4 ++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmd/doctor/doctor_auth_test.go b/cmd/doctor/doctor_auth_test.go index 5059998..ba15117 100644 --- a/cmd/doctor/doctor_auth_test.go +++ b/cmd/doctor/doctor_auth_test.go @@ -33,6 +33,7 @@ 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") @@ -56,6 +57,7 @@ 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") @@ -73,6 +75,7 @@ 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. diff --git a/cmd/doctor/doctor_test.go b/cmd/doctor/doctor_test.go index 7cccf2f..fc2e4de 100644 --- a/cmd/doctor/doctor_test.go +++ b/cmd/doctor/doctor_test.go @@ -44,6 +44,7 @@ func newDoctorFactory(ios *iostreams.IOStreams, svr *httptest.Server) *cmdutil.F func TestDoctorCmd_AllPass(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") switch r.URL.Path { @@ -86,6 +87,7 @@ func TestDoctorCmd_AllPass(t *testing.T) { func TestDoctorCmd_OneFail(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") @@ -123,6 +125,7 @@ func TestDoctorCmd_OneFail(t *testing.T) { func TestDoctorCmd_WarnOnly(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") @@ -163,6 +166,7 @@ func TestDoctorCmd_WarnOnly(t *testing.T) { func TestDoctorCmd_JSON(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") @@ -209,6 +213,7 @@ func TestDoctorCmd_Timeout(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") @@ -248,6 +253,7 @@ func TestDoctorCmd_Timeout(t *testing.T) { func TestDoctorCmd_Output(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") @@ -282,6 +288,7 @@ func TestDoctorCmd_NoLocalJSONFlag(t *testing.T) { func TestDoctorCmd_ExitCodeOnFail(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) { switch r.URL.Path { @@ -336,21 +343,20 @@ 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. -// -// It also clears KH_HOST. ResolveHost reads that variable ahead of the -// factory's DefaultHost, so an exported KH_HOST sends these checks to the live -// host instead of the test server and the assertions fail with nothing -// pointing at the cause. func setHome(t *testing.T, home string) { t.Helper() t.Setenv("HOME", home) t.Setenv("USERPROFILE", home) - t.Setenv("KH_HOST", "") } 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. diff --git a/cmd/doctor/doctor_version_test.go b/cmd/doctor/doctor_version_test.go index d6b969c..0a2c0df 100644 --- a/cmd/doctor/doctor_version_test.go +++ b/cmd/doctor/doctor_version_test.go @@ -59,6 +59,7 @@ 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", "")) @@ -71,6 +72,7 @@ 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")) @@ -85,6 +87,7 @@ 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")) @@ -97,6 +100,7 @@ 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,