From 5ea164a39fc416b9ed4f6fbc498d12067644c746 Mon Sep 17 00:00:00 2001 From: jpustelnik Date: Tue, 10 Nov 2026 08:58:41 +0100 Subject: [PATCH 1/2] fix(api-client): skip connectivity request when a proxy is configured When a proxy is set, NewApiClient sent GET / to the endpoint as a connectivity check. Jira Data Center behind SSO redirects the API root to a login page, so the check failed with "stopped after 10 redirects" and the connection could not be used. Plugins already test their own API endpoints, so drop the extra request. Fixes apache/devlake#8632 Co-Authored-By: Claude Opus 5.5 Signed-off-by: jpustelnik --- backend/helpers/pluginhelper/api/api_client.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/backend/helpers/pluginhelper/api/api_client.go b/backend/helpers/pluginhelper/api/api_client.go index 9249c06f6de..9f08f1e534a 100644 --- a/backend/helpers/pluginhelper/api/api_client.go +++ b/backend/helpers/pluginhelper/api/api_client.go @@ -142,14 +142,8 @@ func NewApiClient( if err != nil { return nil, errors.Convert(err) } - // check connectivity - res, err := apiClient.Get("/", nil, nil) - if err != nil { - return nil, err - } - if res.StatusCode == http.StatusBadGateway { - return nil, errors.BadInput.New(fmt.Sprintf("fail to connect to %v via %v", endpoint, proxy)) - } + // No connectivity request here: endpoints behind SSO may redirect the + // API root to a login page and fail. Plugins test their own API instead. } else { // check connectivity parsedUrl, err := url.Parse(endpoint) From 6093e9e6db0b97a49adf7b19d0574b08f2378ab4 Mon Sep 17 00:00:00 2001 From: jpustelnik Date: Tue, 10 Nov 2026 09:06:12 +0100 Subject: [PATCH 2/2] test(api-client): cover proxy client creation without endpoint probe Regression test for apache/devlake#8632. A fake proxy redirects every request to itself, like an SSO login loop. NewApiClient must succeed and send nothing through it; later requests must still use the proxy. Co-Authored-By: Claude Opus 5.5 Signed-off-by: jpustelnik --- .../pluginhelper/api/api_client_test.go | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/backend/helpers/pluginhelper/api/api_client_test.go b/backend/helpers/pluginhelper/api/api_client_test.go index 3b876c98cd8..9e0b1380017 100644 --- a/backend/helpers/pluginhelper/api/api_client_test.go +++ b/backend/helpers/pluginhelper/api/api_client_test.go @@ -18,11 +18,18 @@ limitations under the License. package api import ( + "context" + "net/http" + "net/http/httptest" + "sync/atomic" "testing" + "github.com/apache/devlake/core/config" "github.com/apache/devlake/core/errors" + implcontext "github.com/apache/devlake/impls/context" "github.com/apache/devlake/impls/logruslog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestApiClientBlackList(t *testing.T) { @@ -95,3 +102,29 @@ func TestApiClientBlackList(t *testing.T) { }) } } + +// TestNewApiClientWithProxyDoesNotProbeEndpoint is a regression test for +// apache/devlake#8632: creating a client with a proxy must not send a request +// to the endpoint root. Jira behind SSO redirects that root to a login page, +// which made client creation fail with "stopped after 10 redirects". +func TestNewApiClientWithProxyDoesNotProbeEndpoint(t *testing.T) { + var proxied atomic.Int32 + // Behaves like a proxy in front of an SSO-protected server: every request + // is redirected back to itself, so any probe would end in a redirect loop. + proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + proxied.Add(1) + http.Redirect(w, r, r.URL.String(), http.StatusFound) + })) + defer proxy.Close() + + br := implcontext.NewDefaultBasicRes(config.GetConfig(), logruslog.Global, nil) + client, err := NewApiClient(context.Background(), "http://jira.example.invalid/rest/", nil, 0, proxy.URL, br) + + require.Nil(t, err) + require.NotNil(t, client) + assert.Equal(t, int32(0), proxied.Load(), "NewApiClient must not send requests through the proxy") + + // Requests made later still go through the proxy. + _, _ = client.Get("api/2/serverInfo", nil, nil) + assert.Greater(t, proxied.Load(), int32(0), "requests should be routed through the proxy") +}