diff --git a/cli/cmd/context_test.go b/cli/cmd/context_test.go index e5edb445..c84502f8 100644 --- a/cli/cmd/context_test.go +++ b/cli/cmd/context_test.go @@ -71,6 +71,15 @@ func TestResolveContext(t *testing.T) { // holding a config whose only context, lab, points at a fake API. It returns // the output, the exit code, and how many requests reached the API. func runKatesCLI(t *testing.T, env []string, args ...string) (out string, code int, requests int64) { + t.Helper() + return runKatesCLIConfig(t, func(apiURL string) string { + return "current-context: lab\ncontexts:\n lab:\n url: " + apiURL + "\n" + }, env, args...) +} + +// runKatesCLIConfig is runKatesCLI with the config file config writes for the +// fake API's URL. +func runKatesCLIConfig(t *testing.T, config func(apiURL string) string, env []string, args ...string) (out string, code int, requests int64) { t.Helper() var hits atomic.Int64 api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -81,8 +90,7 @@ func runKatesCLI(t *testing.T, env []string, args ...string) (out string, code i t.Cleanup(api.Close) home := t.TempDir() - config := "current-context: lab\ncontexts:\n lab:\n url: " + api.URL + "\n" - if err := os.WriteFile(filepath.Join(home, ".kates.yaml"), []byte(config), 0o600); err != nil { + if err := os.WriteFile(filepath.Join(home, ".kates.yaml"), []byte(config(api.URL)), 0o600); err != nil { t.Fatal(err) } @@ -127,6 +135,38 @@ func TestUnknownContextReachesNoServer(t *testing.T) { } } +// A config kates could not parse used to send every command to +// http://localhost:8080, whatever answered there, without the API key of the +// context the file names. +func TestUnreadableConfigReachesNoServer(t *testing.T) { + broken := func(apiURL string) string { + return "current-context: lab\ncontexts:\n lab:\n url: " + apiURL + "\n [unclosed\n" + } + out, code, requests := runKatesCLIConfig(t, broken, nil, "health") + if code != 1 || requests != 0 { + t.Errorf("exit %d after %d requests, want 1 after none:\n%s", code, requests, out) + } + if !strings.Contains(out, "could not load config") || !strings.Contains(out, ".kates.yaml") { + t.Errorf("the error does not name the config file:\n%s", out) + } + + // --url and KATES_URL name the server themselves. + var hits atomic.Int64 + other := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + hits.Add(1) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"items":[],"page":0,"size":100,"total":0,"totalPages":0,"count":0}`)) + })) + t.Cleanup(other.Close) + if out, code, _ := runKatesCLIConfig(t, broken, nil, "--url", other.URL, "test", "list"); code != 0 || hits.Load() == 0 { + t.Errorf("--url: exit %d after %d requests, want 0 after some:\n%s", code, hits.Load(), out) + } + hits.Store(0) + if out, code, _ := runKatesCLIConfig(t, broken, []string{"KATES_URL=" + other.URL}, "test", "list"); code != 0 || hits.Load() == 0 { + t.Errorf("KATES_URL: exit %d after %d requests, want 0 after some:\n%s", code, hits.Load(), out) + } +} + // Commands that never call the API still run, so a context can be created // while KATES_CONTEXT already names it. func TestUnknownContextCanBeCreated(t *testing.T) { diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 068b3310..ab22fe45 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -63,12 +63,18 @@ func configPath() string { } func loadConfig() Config { + cfg, _ := loadConfigErr() + return cfg +} + +// loadConfigErr is loadConfig, also returning the error it warned about. +func loadConfigErr() (Config, error) { cfg, err := readConfig() if err != nil && !configWarned { fmt.Fprintf(os.Stderr, "Warning: could not load config %s: %v\n", configPath(), err) configWarned = true } - return cfg + return cfg, err } // configWarned keeps loadConfig's warning to one a process: the help function @@ -492,7 +498,7 @@ var rootCmd = &cobra.Command{ } } readContextEnv() - cfg := loadConfig() + cfg, cfgErr := loadConfigErr() ctxName, ctx, ctxErr := resolveContext(cfg) profiles, profilesFrom := activeProfiles(ctxName, ctx) urlOverridden := apiURL != "" @@ -503,6 +509,12 @@ var rootCmd = &cobra.Command{ apiURL = ctx.URL } } + // A file kates could not read or parse used to leave it on the + // built-in http://localhost:8080, without the API key of the context + // the file names, so its requests went to whatever answered there. + if cfgErr != nil && !urlOverridden { + ctxErr = fmt.Errorf("could not load config %s: %w. Fix the file, or pass --url or set KATES_URL", configPath(), cfgErr) + } activeContext = ctxName activeProfileNames, activeProfilesFrom = profiles, profilesFrom apiURLOverridden = urlOverridden