From 946ba38d9df9cc5bb13117bdd1cfd8ce2408d99c Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Mon, 20 Apr 2026 11:42:17 +0200 Subject: [PATCH 1/6] determine if we are using the Default endpoint or not --- cmd/src/main.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/src/main.go b/cmd/src/main.go index 68478c9276..ac558e5ead 100644 --- a/cmd/src/main.go +++ b/cmd/src/main.go @@ -19,6 +19,8 @@ import ( "github.com/sourcegraph/src-cli/internal/oauth" ) +const SGDotComEndpoint = "https://sourcegraph.com" + const usageText = `src is a tool that provides access to Sourcegraph instances. For more information, see https://github.com/sourcegraph/src-cli @@ -270,7 +272,7 @@ func readConfig() (*config, error) { endpointStr = envEndpoint } if endpointStr == "" { - endpointStr = "https://sourcegraph.com" + endpointStr = SGDotComEndpoint } if envProxy != "" { proxyStr = envProxy @@ -348,6 +350,15 @@ func isCI() bool { return ok && value != "" } +func DefaultEndpointConfigured(cfg *config) bool { + _, ok := os.LookupEnv("SRC_ENDPOINT") + if ok { + return false + } + return cfg.endpointURL != nil && cfg.endpointURL.String() == SGDotComEndpoint + +} + // isValidUnixSocket checks if the given path is a valid Unix socket. // // Parameters: From 41a1e841691ea1ccf4d58596c95644ed76ff7228 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Mon, 20 Apr 2026 11:42:17 +0200 Subject: [PATCH 2/6] move notifce of config file deprecation earlier --- cmd/src/login.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/src/login.go b/cmd/src/login.go index 9818c245b6..32dfbcced5 100644 --- a/cmd/src/login.go +++ b/cmd/src/login.go @@ -51,6 +51,11 @@ Examples: } var loginEndpointURL *url.URL + if cfg.configFilePath != "" { + fmt.Fprintln(os.Stderr) + fmt.Fprintf(os.Stderr, "⚠️ Warning: Configuring src with a JSON file is deprecated. Please migrate to using the env vars SRC_ENDPOINT, SRC_ACCESS_TOKEN, and SRC_PROXY instead, and then remove %s. See https://github.com/sourcegraph/src-cli#readme for more information.\n", cfg.configFilePath) + } + if flagSet.NArg() >= 1 { arg := flagSet.Arg(0) u, err := parseEndpoint(arg) @@ -104,11 +109,6 @@ func loginCmd(ctx context.Context, p loginParams) error { return err } - if p.cfg.configFilePath != "" { - fmt.Fprintln(p.out) - fmt.Fprintf(p.out, "⚠️ Warning: Configuring src with a JSON file is deprecated. Please migrate to using the env vars SRC_ENDPOINT, SRC_ACCESS_TOKEN, and SRC_PROXY instead, and then remove %s. See https://github.com/sourcegraph/src-cli#readme for more information.\n", p.cfg.configFilePath) - } - _, flow := selectLoginFlow(p) if err := flow(ctx, p); err != nil { return err From 2e86aaf70c62ebc1df02e5f9cda1ee57a1c8576b Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Mon, 20 Apr 2026 11:42:17 +0200 Subject: [PATCH 3/6] Move endpoint validation to happen before running loginCmd - update config endpointURL if endpointURL is set either as a flag or positional argument - warn the user about endpoint conflict or that the default endpoint is being used --- cmd/src/login.go | 46 +++++++++++++++++++++++---------------- cmd/src/login_validate.go | 7 ------ 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/cmd/src/login.go b/cmd/src/login.go index 32dfbcced5..c8cb933114 100644 --- a/cmd/src/login.go +++ b/cmd/src/login.go @@ -50,7 +50,6 @@ Examples: return err } - var loginEndpointURL *url.URL if cfg.configFilePath != "" { fmt.Fprintln(os.Stderr) fmt.Fprintf(os.Stderr, "⚠️ Warning: Configuring src with a JSON file is deprecated. Please migrate to using the env vars SRC_ENDPOINT, SRC_ACCESS_TOKEN, and SRC_PROXY instead, and then remove %s. See https://github.com/sourcegraph/src-cli#readme for more information.\n", cfg.configFilePath) @@ -58,22 +57,36 @@ Examples: if flagSet.NArg() >= 1 { arg := flagSet.Arg(0) - u, err := parseEndpoint(arg) + loginEndpointURL, err := parseEndpoint(arg) if err != nil { return cmderrors.Usage(fmt.Sprintf("invalid endpoint URL: %s", arg)) } - loginEndpointURL = u + + hasEndpointURLConflict := cfg.endpointURL.String() != loginEndpointURL.String() + + if hasEndpointURLConflict { + // If the default is configured it means SRC_ENDPOINT is not set + if DefaultEndpointConfigured(cfg) { + fmt.Fprintf(os.Stderr, "⚠️ Warning: No SRC_ENDPOINT is configured in the environment. Logging in using %q.\n", loginEndpointURL) + fmt.Fprintf(os.Stderr, "\n💡 Tip: To use this endpoint in your shell, run:\n\n export SRC_ENDPOINT=%s\n\nNOTE: By default src will use %q if SRC_ENDPOINT is not set.\n", loginEndpointURL, SGDotComEndpoint) + } else { + fmt.Fprintf(os.Stderr, "⚠️ Warning: Logging into %s instead of the configured endpoint %s.\n", loginEndpointURL, cfg.endpointURL) + fmt.Fprintf(os.Stderr, "\n💡 Tip: To use this endpoint in your shell, run:\n\n export SRC_ENDPOINT=%s\n\n", loginEndpointURL) + } + } + + // An explicit endpoint on the CLI overrides the configured endpoint for this login. + cfg.endpointURL = loginEndpointURL } client := cfg.apiClient(apiFlags, io.Discard) return loginCmd(context.Background(), loginParams{ - cfg: cfg, - client: client, - out: os.Stdout, - apiFlags: apiFlags, - oauthClient: oauth.NewClient(oauth.DefaultClientID), - loginEndpointURL: loginEndpointURL, + cfg: cfg, + client: client, + out: os.Stdout, + apiFlags: apiFlags, + oauthClient: oauth.NewClient(oauth.DefaultClientID), }) } @@ -85,12 +98,11 @@ Examples: } type loginParams struct { - cfg *config - client api.Client - out io.Writer - apiFlags *api.Flags - oauthClient oauth.Client - loginEndpointURL *url.URL + cfg *config + client api.Client + out io.Writer + apiFlags *api.Flags + oauthClient oauth.Client } type loginFlow func(context.Context, loginParams) error @@ -113,15 +125,11 @@ func loginCmd(ctx context.Context, p loginParams) error { if err := flow(ctx, p); err != nil { return err } - fmt.Fprintf(p.out, "\n💡 Tip: To use this endpoint in your shell, run:\n\n export SRC_ENDPOINT=%s\n\n", p.cfg.endpointURL) return nil } // selectLoginFlow decides what login flow to run based on configured AuthMode. func selectLoginFlow(p loginParams) (loginFlowKind, loginFlow) { - if p.loginEndpointURL != nil && p.loginEndpointURL.String() != p.cfg.endpointURL.String() { - return loginFlowEndpointConflict, runEndpointConflictLogin - } switch p.cfg.AuthMode() { case AuthModeOAuth: return loginFlowOAuth, runOAuthLogin diff --git a/cmd/src/login_validate.go b/cmd/src/login_validate.go index 9aa65cdcef..4b373c6247 100644 --- a/cmd/src/login_validate.go +++ b/cmd/src/login_validate.go @@ -18,13 +18,6 @@ func runMissingAuthLogin(_ context.Context, p loginParams) error { return cmderrors.ExitCode1 } -func runEndpointConflictLogin(_ context.Context, p loginParams) error { - fmt.Fprintln(p.out) - printLoginProblem(p.out, fmt.Sprintf("The configured endpoint is %s, not %s.", p.cfg.endpointURL, p.loginEndpointURL)) - fmt.Fprintln(p.out, loginAccessTokenMessage(p.loginEndpointURL)) - return cmderrors.ExitCode1 -} - func runValidatedLogin(ctx context.Context, p loginParams) error { return validateCurrentUser(ctx, p.client, p.out, p.cfg.endpointURL) } From 3608a6e2e44ab49fbb5ebef58c9f87f55eec2383 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Mon, 20 Apr 2026 11:42:17 +0200 Subject: [PATCH 4/6] fix tests and remove unused login funcs --- cmd/src/login.go | 24 +--- cmd/src/login_test.go | 225 ++++++++++++++++++++++++++------------ cmd/src/login_validate.go | 7 -- 3 files changed, 162 insertions(+), 94 deletions(-) diff --git a/cmd/src/login.go b/cmd/src/login.go index c8cb933114..cd6e0c09db 100644 --- a/cmd/src/login.go +++ b/cmd/src/login.go @@ -107,21 +107,12 @@ type loginParams struct { type loginFlow func(context.Context, loginParams) error -type loginFlowKind int - -const ( - loginFlowOAuth loginFlowKind = iota - loginFlowMissingAuth - loginFlowEndpointConflict - loginFlowValidate -) - func loginCmd(ctx context.Context, p loginParams) error { if err := p.cfg.requireCIAccessToken(); err != nil { return err } - _, flow := selectLoginFlow(p) + flow := selectLoginFlow(p) if err := flow(ctx, p); err != nil { return err } @@ -129,15 +120,12 @@ func loginCmd(ctx context.Context, p loginParams) error { } // selectLoginFlow decides what login flow to run based on configured AuthMode. -func selectLoginFlow(p loginParams) (loginFlowKind, loginFlow) { - switch p.cfg.AuthMode() { - case AuthModeOAuth: - return loginFlowOAuth, runOAuthLogin - case AuthModeAccessToken: - return loginFlowValidate, runValidatedLogin - default: - return loginFlowMissingAuth, runMissingAuthLogin + +func selectLoginFlow(p loginParams) loginFlow { + if p.cfg.AuthMode() == AuthModeAccessToken { + return runValidatedLogin } + return runOAuthLogin } func printLoginProblem(out io.Writer, problem string) { diff --git a/cmd/src/login_test.go b/cmd/src/login_test.go index 1250d1adb9..a405e4959e 100644 --- a/cmd/src/login_test.go +++ b/cmd/src/login_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "strings" "testing" "time" @@ -25,34 +26,86 @@ func mustParseURL(t *testing.T, raw string) *url.URL { return u } +func loginCommand(t *testing.T) *command { + t.Helper() + for _, cmd := range commands { + if cmd.matches("login") { + return cmd + } + } + t.Fatal("login command not found") + return nil +} + +func captureProcessOutput(t *testing.T, fn func() error) (stdout string, stderr string, err error) { + t.Helper() + + stdoutR, stdoutW, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + stderrR, stderrW, err := os.Pipe() + if err != nil { + _ = stdoutR.Close() + _ = stdoutW.Close() + t.Fatal(err) + } + + oldStdout := os.Stdout + oldStderr := os.Stderr + os.Stdout = stdoutW + os.Stderr = stderrW + defer func() { + os.Stdout = oldStdout + os.Stderr = oldStderr + }() + + err = fn() + + _ = stdoutW.Close() + _ = stderrW.Close() + + stdoutBytes, readErr := io.ReadAll(stdoutR) + if readErr != nil { + t.Fatal(readErr) + } + stderrBytes, readErr := io.ReadAll(stderrR) + if readErr != nil { + t.Fatal(readErr) + } + + return strings.TrimSpace(string(stdoutBytes)), strings.TrimSpace(string(stderrBytes)), err +} + +func runLoginHandler(t *testing.T, cfgValue *config, args ...string) (stdout string, stderr string, err error) { + t.Helper() + + oldCfg := cfg + cfg = cfgValue + t.Cleanup(func() { cfg = oldCfg }) + + return captureProcessOutput(t, func() error { + return loginCommand(t).handler(args) + }) +} + func TestLogin(t *testing.T) { - check := func(t *testing.T, cfg *config, endpointArgURL *url.URL) (output string, err error) { + check := func(t *testing.T, cfg *config) (output string, err error) { t.Helper() var out bytes.Buffer err = loginCmd(context.Background(), loginParams{ - cfg: cfg, - client: cfg.apiClient(nil, io.Discard), - out: &out, - oauthClient: fakeOAuthClient{startErr: fmt.Errorf("oauth unavailable")}, - loginEndpointURL: endpointArgURL, + cfg: cfg, + client: cfg.apiClient(nil, io.Discard), + out: &out, + oauthClient: fakeOAuthClient{startErr: fmt.Errorf("oauth unavailable")}, }) return strings.TrimSpace(out.String()), err } - t.Run("different endpoint in config vs. arg", func(t *testing.T) { - out, err := check(t, &config{endpointURL: &url.URL{Scheme: "https", Host: "example.com"}}, &url.URL{Scheme: "https", Host: "sourcegraph.example.com"}) - if err == nil { - t.Fatal(err) - } - if !strings.Contains(out, "The configured endpoint is https://example.com, not https://sourcegraph.example.com.") { - t.Errorf("got output %q, want configured endpoint error", out) - } - }) - t.Run("no access token triggers oauth flow", func(t *testing.T) { u := &url.URL{Scheme: "https", Host: "example.com"} - out, err := check(t, &config{endpointURL: u}, u) + out, err := check(t, &config{endpointURL: u}) if err == nil { t.Fatal(err) } @@ -63,7 +116,7 @@ func TestLogin(t *testing.T) { t.Run("CI requires access token", func(t *testing.T) { u := &url.URL{Scheme: "https", Host: "example.com"} - out, err := check(t, &config{endpointURL: u, inCI: true}, u) + out, err := check(t, &config{endpointURL: u, inCI: true}) if err != errCIAccessTokenRequired { t.Fatalf("err = %v, want %v", err, errCIAccessTokenRequired) } @@ -72,20 +125,6 @@ func TestLogin(t *testing.T) { } }) - t.Run("warning when using config file", func(t *testing.T) { - endpoint := &url.URL{Scheme: "https", Host: "example.com"} - out, err := check(t, &config{endpointURL: endpoint, configFilePath: "f"}, endpoint) - if err != cmderrors.ExitCode1 { - t.Fatal(err) - } - if !strings.Contains(out, "Configuring src with a JSON file is deprecated") { - t.Errorf("got output %q, want deprecation warning", out) - } - if !strings.Contains(out, "OAuth Device flow authentication failed:") { - t.Errorf("got output %q, want oauth failure output", out) - } - }) - t.Run("invalid access token", func(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "", http.StatusUnauthorized) @@ -93,7 +132,7 @@ func TestLogin(t *testing.T) { defer s.Close() u := mustParseURL(t, s.URL) - out, err := check(t, &config{endpointURL: u, accessToken: "x"}, u) + out, err := check(t, &config{endpointURL: u, accessToken: "x"}) if err != cmderrors.ExitCode1 { t.Fatal(err) } @@ -111,11 +150,11 @@ func TestLogin(t *testing.T) { defer s.Close() u := mustParseURL(t, s.URL) - out, err := check(t, &config{endpointURL: u, accessToken: "x"}, u) + out, err := check(t, &config{endpointURL: u, accessToken: "x"}) if err != nil { t.Fatal(err) } - wantOut := "✔︎ Authenticated as alice on $ENDPOINT\n\n\n💡 Tip: To use this endpoint in your shell, run:\n\n export SRC_ENDPOINT=$ENDPOINT" + wantOut := "✔︎ Authenticated as alice on $ENDPOINT" wantOut = strings.ReplaceAll(wantOut, "$ENDPOINT", s.URL) if out != wantOut { t.Errorf("got output %q, want %q", out, wantOut) @@ -156,7 +195,7 @@ func TestLogin(t *testing.T) { t.Fatal("expected stored oauth token to avoid device flow") } gotOut := strings.TrimSpace(out.String()) - wantOut := "✔︎ Authenticated as alice on $ENDPOINT\n\n\n✔︎ Authenticated with OAuth credentials\n\n💡 Tip: To use this endpoint in your shell, run:\n\n export SRC_ENDPOINT=$ENDPOINT" + wantOut := "✔︎ Authenticated as alice on $ENDPOINT\n\n\n✔︎ Authenticated with OAuth credentials" wantOut = strings.ReplaceAll(wantOut, "$ENDPOINT", s.URL) if gotOut != wantOut { t.Errorf("got output %q, want %q", gotOut, wantOut) @@ -164,6 +203,87 @@ func TestLogin(t *testing.T) { }) } +func TestLoginHandler(t *testing.T) { + t.Run("warns when login endpoint differs from configured endpoint", func(t *testing.T) { + t.Setenv("SRC_ENDPOINT", "https://example.com") + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, `{"data":{"currentUser":{"username":"alice"}}}`) + })) + defer s.Close() + + stdout, stderr, err := runLoginHandler(t, &config{ + endpointURL: mustParseURL(t, "https://example.com"), + accessToken: "x", + }, s.URL) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(stderr, "Warning: Logging into "+s.URL+" instead of the configured endpoint https://example.com.") { + t.Fatalf("stderr = %q, want endpoint warning", stderr) + } + if !strings.Contains(stderr, "export SRC_ENDPOINT="+s.URL) { + t.Fatalf("stderr = %q, want shell tip", stderr) + } + if !strings.Contains(stdout, "✔︎ Authenticated as alice on "+s.URL) { + t.Fatalf("stdout = %q, want validation output", stdout) + } + }) + + t.Run("warns when no SRC_ENDPOINT is configured in the environment", func(t *testing.T) { + if oldValue, ok := os.LookupEnv("SRC_ENDPOINT"); ok { + _ = os.Unsetenv("SRC_ENDPOINT") + t.Cleanup(func() { _ = os.Setenv("SRC_ENDPOINT", oldValue) }) + } else { + _ = os.Unsetenv("SRC_ENDPOINT") + } + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, `{"data":{"currentUser":{"username":"alice"}}}`) + })) + defer s.Close() + + stdout, stderr, err := runLoginHandler(t, &config{ + endpointURL: mustParseURL(t, SGDotComEndpoint), + accessToken: "x", + }, s.URL) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(stderr, "Warning: No SRC_ENDPOINT is configured in the environment. Logging in using \""+s.URL+"\".") { + t.Fatalf("stderr = %q, want default-endpoint warning", stderr) + } + if !strings.Contains(stderr, "NOTE: By default src will use \""+SGDotComEndpoint+"\" if SRC_ENDPOINT is not set.") { + t.Fatalf("stderr = %q, want default endpoint note", stderr) + } + if !strings.Contains(stdout, "✔︎ Authenticated as alice on "+s.URL) { + t.Fatalf("stdout = %q, want validation output", stdout) + } + }) + + t.Run("warns when using config file", func(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, `{"data":{"currentUser":{"username":"alice"}}}`) + })) + defer s.Close() + + stdout, stderr, err := runLoginHandler(t, &config{ + endpointURL: mustParseURL(t, s.URL), + accessToken: "x", + configFilePath: "f", + }) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(stderr, "Configuring src with a JSON file is deprecated") { + t.Fatalf("stderr = %q, want deprecation warning", stderr) + } + if !strings.Contains(stdout, "✔︎ Authenticated as alice on "+s.URL) { + t.Fatalf("stdout = %q, want validation output", stdout) + } + }) +} + type fakeOAuthClient struct { startErr error startCalled *bool @@ -192,39 +312,6 @@ func (f fakeOAuthClient) Refresh(context.Context, *oauth.Token) (*oauth.TokenRes return nil, fmt.Errorf("unexpected call to Refresh") } -func TestSelectLoginFlow(t *testing.T) { - t.Run("uses oauth flow when no access token is configured", func(t *testing.T) { - params := loginParams{ - cfg: &config{endpointURL: mustParseURL(t, "https://example.com")}, - } - - if got, _ := selectLoginFlow(params); got != loginFlowOAuth { - t.Fatalf("flow = %v, want %v", got, loginFlowOAuth) - } - }) - - t.Run("uses endpoint conflict flow when auth exists for a different endpoint", func(t *testing.T) { - params := loginParams{ - cfg: &config{endpointURL: mustParseURL(t, "https://example.com"), accessToken: "x"}, - loginEndpointURL: mustParseURL(t, "https://sourcegraph.example.com"), - } - - if got, _ := selectLoginFlow(params); got != loginFlowEndpointConflict { - t.Fatalf("flow = %v, want %v", got, loginFlowEndpointConflict) - } - }) - - t.Run("uses validation flow when auth exists for the selected endpoint", func(t *testing.T) { - params := loginParams{ - cfg: &config{endpointURL: mustParseURL(t, "https://example.com"), accessToken: "x"}, - } - - if got, _ := selectLoginFlow(params); got != loginFlowValidate { - t.Fatalf("flow = %v, want %v", got, loginFlowValidate) - } - }) -} - func TestValidateBrowserURL(t *testing.T) { tests := []struct { name string diff --git a/cmd/src/login_validate.go b/cmd/src/login_validate.go index 4b373c6247..095ea7ab22 100644 --- a/cmd/src/login_validate.go +++ b/cmd/src/login_validate.go @@ -11,13 +11,6 @@ import ( "github.com/sourcegraph/src-cli/internal/cmderrors" ) -func runMissingAuthLogin(_ context.Context, p loginParams) error { - fmt.Fprintln(p.out) - printLoginProblem(p.out, "No access token is configured.") - fmt.Fprintln(p.out, loginAccessTokenMessage(p.cfg.endpointURL)) - return cmderrors.ExitCode1 -} - func runValidatedLogin(ctx context.Context, p loginParams) error { return validateCurrentUser(ctx, p.client, p.out, p.cfg.endpointURL) } From c3bf70a00a39fefb8cefff397082deb5b590687d Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Mon, 20 Apr 2026 11:42:17 +0200 Subject: [PATCH 5/6] move DefaultEndpointConfigured to be attr on struct --- cmd/src/login.go | 2 +- cmd/src/main.go | 25 +++++++++---------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/cmd/src/login.go b/cmd/src/login.go index cd6e0c09db..889994af51 100644 --- a/cmd/src/login.go +++ b/cmd/src/login.go @@ -66,7 +66,7 @@ Examples: if hasEndpointURLConflict { // If the default is configured it means SRC_ENDPOINT is not set - if DefaultEndpointConfigured(cfg) { + if cfg.usingDefaultEndpoint { fmt.Fprintf(os.Stderr, "⚠️ Warning: No SRC_ENDPOINT is configured in the environment. Logging in using %q.\n", loginEndpointURL) fmt.Fprintf(os.Stderr, "\n💡 Tip: To use this endpoint in your shell, run:\n\n export SRC_ENDPOINT=%s\n\nNOTE: By default src will use %q if SRC_ENDPOINT is not set.\n", loginEndpointURL, SGDotComEndpoint) } else { diff --git a/cmd/src/main.go b/cmd/src/main.go index ac558e5ead..fd217ba51f 100644 --- a/cmd/src/main.go +++ b/cmd/src/main.go @@ -143,13 +143,14 @@ var cfg *config // config holds the resolved configuration used at runtime. type config struct { - accessToken string - additionalHeaders map[string]string - proxyURL *url.URL - proxyPath string - configFilePath string - endpointURL *url.URL // always non-nil; defaults to https://sourcegraph.com via readConfig - inCI bool + accessToken string + additionalHeaders map[string]string + proxyURL *url.URL + proxyPath string + configFilePath string + endpointURL *url.URL // always non-nil; defaults to https://sourcegraph.com via readConfig + usingDefaultEndpoint bool + inCI bool } // configFromFile holds the config as read from the config file, @@ -273,6 +274,7 @@ func readConfig() (*config, error) { } if endpointStr == "" { endpointStr = SGDotComEndpoint + cfg.usingDefaultEndpoint = true } if envProxy != "" { proxyStr = envProxy @@ -350,15 +352,6 @@ func isCI() bool { return ok && value != "" } -func DefaultEndpointConfigured(cfg *config) bool { - _, ok := os.LookupEnv("SRC_ENDPOINT") - if ok { - return false - } - return cfg.endpointURL != nil && cfg.endpointURL.String() == SGDotComEndpoint - -} - // isValidUnixSocket checks if the given path is a valid Unix socket. // // Parameters: From 89f7c8f0acb4aa637696df7650af0d2952caca98 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Mon, 20 Apr 2026 13:32:03 +0200 Subject: [PATCH 6/6] fix tests --- cmd/src/login_test.go | 145 ------------------------------------------ cmd/src/main_test.go | 40 +++++++----- 2 files changed, 24 insertions(+), 161 deletions(-) diff --git a/cmd/src/login_test.go b/cmd/src/login_test.go index a405e4959e..5dba8b464b 100644 --- a/cmd/src/login_test.go +++ b/cmd/src/login_test.go @@ -8,7 +8,6 @@ import ( "net/http" "net/http/httptest" "net/url" - "os" "strings" "testing" "time" @@ -26,69 +25,6 @@ func mustParseURL(t *testing.T, raw string) *url.URL { return u } -func loginCommand(t *testing.T) *command { - t.Helper() - for _, cmd := range commands { - if cmd.matches("login") { - return cmd - } - } - t.Fatal("login command not found") - return nil -} - -func captureProcessOutput(t *testing.T, fn func() error) (stdout string, stderr string, err error) { - t.Helper() - - stdoutR, stdoutW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - stderrR, stderrW, err := os.Pipe() - if err != nil { - _ = stdoutR.Close() - _ = stdoutW.Close() - t.Fatal(err) - } - - oldStdout := os.Stdout - oldStderr := os.Stderr - os.Stdout = stdoutW - os.Stderr = stderrW - defer func() { - os.Stdout = oldStdout - os.Stderr = oldStderr - }() - - err = fn() - - _ = stdoutW.Close() - _ = stderrW.Close() - - stdoutBytes, readErr := io.ReadAll(stdoutR) - if readErr != nil { - t.Fatal(readErr) - } - stderrBytes, readErr := io.ReadAll(stderrR) - if readErr != nil { - t.Fatal(readErr) - } - - return strings.TrimSpace(string(stdoutBytes)), strings.TrimSpace(string(stderrBytes)), err -} - -func runLoginHandler(t *testing.T, cfgValue *config, args ...string) (stdout string, stderr string, err error) { - t.Helper() - - oldCfg := cfg - cfg = cfgValue - t.Cleanup(func() { cfg = oldCfg }) - - return captureProcessOutput(t, func() error { - return loginCommand(t).handler(args) - }) -} - func TestLogin(t *testing.T) { check := func(t *testing.T, cfg *config) (output string, err error) { t.Helper() @@ -203,87 +139,6 @@ func TestLogin(t *testing.T) { }) } -func TestLoginHandler(t *testing.T) { - t.Run("warns when login endpoint differs from configured endpoint", func(t *testing.T) { - t.Setenv("SRC_ENDPOINT", "https://example.com") - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, `{"data":{"currentUser":{"username":"alice"}}}`) - })) - defer s.Close() - - stdout, stderr, err := runLoginHandler(t, &config{ - endpointURL: mustParseURL(t, "https://example.com"), - accessToken: "x", - }, s.URL) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(stderr, "Warning: Logging into "+s.URL+" instead of the configured endpoint https://example.com.") { - t.Fatalf("stderr = %q, want endpoint warning", stderr) - } - if !strings.Contains(stderr, "export SRC_ENDPOINT="+s.URL) { - t.Fatalf("stderr = %q, want shell tip", stderr) - } - if !strings.Contains(stdout, "✔︎ Authenticated as alice on "+s.URL) { - t.Fatalf("stdout = %q, want validation output", stdout) - } - }) - - t.Run("warns when no SRC_ENDPOINT is configured in the environment", func(t *testing.T) { - if oldValue, ok := os.LookupEnv("SRC_ENDPOINT"); ok { - _ = os.Unsetenv("SRC_ENDPOINT") - t.Cleanup(func() { _ = os.Setenv("SRC_ENDPOINT", oldValue) }) - } else { - _ = os.Unsetenv("SRC_ENDPOINT") - } - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, `{"data":{"currentUser":{"username":"alice"}}}`) - })) - defer s.Close() - - stdout, stderr, err := runLoginHandler(t, &config{ - endpointURL: mustParseURL(t, SGDotComEndpoint), - accessToken: "x", - }, s.URL) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(stderr, "Warning: No SRC_ENDPOINT is configured in the environment. Logging in using \""+s.URL+"\".") { - t.Fatalf("stderr = %q, want default-endpoint warning", stderr) - } - if !strings.Contains(stderr, "NOTE: By default src will use \""+SGDotComEndpoint+"\" if SRC_ENDPOINT is not set.") { - t.Fatalf("stderr = %q, want default endpoint note", stderr) - } - if !strings.Contains(stdout, "✔︎ Authenticated as alice on "+s.URL) { - t.Fatalf("stdout = %q, want validation output", stdout) - } - }) - - t.Run("warns when using config file", func(t *testing.T) { - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, `{"data":{"currentUser":{"username":"alice"}}}`) - })) - defer s.Close() - - stdout, stderr, err := runLoginHandler(t, &config{ - endpointURL: mustParseURL(t, s.URL), - accessToken: "x", - configFilePath: "f", - }) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(stderr, "Configuring src with a JSON file is deprecated") { - t.Fatalf("stderr = %q, want deprecation warning", stderr) - } - if !strings.Contains(stdout, "✔︎ Authenticated as alice on "+s.URL) { - t.Fatalf("stdout = %q, want validation output", stdout) - } - }) -} - type fakeOAuthClient struct { startErr error startCalled *bool diff --git a/cmd/src/main_test.go b/cmd/src/main_test.go index c0b29822b0..0b23cb9938 100644 --- a/cmd/src/main_test.go +++ b/cmd/src/main_test.go @@ -51,7 +51,8 @@ func TestReadConfig(t *testing.T) { Scheme: "https", Host: "sourcegraph.com", }, - additionalHeaders: map[string]string{}, + usingDefaultEndpoint: true, + additionalHeaders: map[string]string{}, }, }, { @@ -149,8 +150,9 @@ func TestReadConfig(t *testing.T) { Scheme: "https", Host: "sourcegraph.com", }, - accessToken: "abc", - additionalHeaders: map[string]string{}, + usingDefaultEndpoint: true, + accessToken: "abc", + additionalHeaders: map[string]string{}, }, }, { @@ -173,8 +175,9 @@ func TestReadConfig(t *testing.T) { Scheme: "https", Host: "sourcegraph.com", }, - accessToken: "", - proxyPath: "", + usingDefaultEndpoint: true, + accessToken: "", + proxyPath: "", proxyURL: &url.URL{ Scheme: "https", Host: "proxy.com:8080", @@ -209,9 +212,10 @@ func TestReadConfig(t *testing.T) { Scheme: "https", Host: "sourcegraph.com", }, - proxyPath: socketPath, - proxyURL: nil, - additionalHeaders: map[string]string{}, + usingDefaultEndpoint: true, + proxyPath: socketPath, + proxyURL: nil, + additionalHeaders: map[string]string{}, }, }, { @@ -222,9 +226,10 @@ func TestReadConfig(t *testing.T) { Scheme: "https", Host: "sourcegraph.com", }, - proxyPath: socketPath, - proxyURL: nil, - additionalHeaders: map[string]string{}, + usingDefaultEndpoint: true, + proxyPath: socketPath, + proxyURL: nil, + additionalHeaders: map[string]string{}, }, }, { @@ -235,7 +240,8 @@ func TestReadConfig(t *testing.T) { Scheme: "https", Host: "sourcegraph.com", }, - proxyPath: "", + usingDefaultEndpoint: true, + proxyPath: "", proxyURL: &url.URL{ Scheme: "socks5", Host: "localhost:1080", @@ -251,7 +257,8 @@ func TestReadConfig(t *testing.T) { Scheme: "https", Host: "sourcegraph.com", }, - proxyPath: "", + usingDefaultEndpoint: true, + proxyPath: "", proxyURL: &url.URL{ Scheme: "socks5h", Host: "localhost:1080", @@ -331,9 +338,10 @@ func TestReadConfig(t *testing.T) { name: "CI does not require access token during config read", envCI: "1", want: &config{ - endpointURL: &url.URL{Scheme: "https", Host: "sourcegraph.com"}, - additionalHeaders: map[string]string{}, - inCI: true, + endpointURL: &url.URL{Scheme: "https", Host: "sourcegraph.com"}, + usingDefaultEndpoint: true, + additionalHeaders: map[string]string{}, + inCI: true, }, }, {