From 11c48d1e6dcd659289c7cf62a06ce4415e58c814 Mon Sep 17 00:00:00 2001 From: "devsy-app[bot]" <277138668+devsy-app[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:20:20 +0000 Subject: [PATCH 1/3] test: add PostWithRetry coverage in credentials pkg/credentials/PostWithRetry had no test coverage. Add a request_test.go covering the success path, non-200 (no retry) path, the retry-then-recover path (port closed then a server comes up between attempts), and the exhausted-retries path (ECONNREFUSED wrapped and backoff elapsed). --- pkg/credentials/request_test.go | 117 ++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 pkg/credentials/request_test.go diff --git a/pkg/credentials/request_test.go b/pkg/credentials/request_test.go new file mode 100644 index 000000000..4f7235aca --- /dev/null +++ b/pkg/credentials/request_test.go @@ -0,0 +1,117 @@ +package credentials + +import ( + "errors" + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func portFromURL(t *testing.T, rawURL string) int { + t.Helper() + u, err := url.Parse(rawURL) + require.NoError(t, err) + port, err := strconv.Atoi(u.Port()) + require.NoError(t, err) + return port +} + +func TestPostWithRetry_ReturnsBodyOnSuccess(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("payload")) + })) + t.Cleanup(server.Close) + + out, err := PostWithRetry(portFromURL(t, server.URL), "endpoint", http.NoBody) + require.NoError(t, err) + assert.Equal(t, []byte("payload"), out) +} + +func TestPostWithRetry_ReturnsErrorOnNon200(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "boom", http.StatusInternalServerError) + })) + t.Cleanup(server.Close) + + start := time.Now() + out, err := PostWithRetry(portFromURL(t, server.URL), "endpoint", http.NoBody) + elapsed := time.Since(start) + + require.Error(t, err) + assert.Nil(t, out) + assert.Contains(t, err.Error(), "500") + assert.Contains(t, err.Error(), "endpoint") + // A non-200 is not connection-refused, so it must not be retried. + assert.Less(t, elapsed, 500*time.Millisecond, "non-200 response must not trigger retries") +} + +// TestPostWithRetry_RetriesConnectionRefusedThenSucceeds proves the retry loop +// recovers when the credentials server comes up between attempts: the port is +// initially closed (connection refused) and a server is bound on it shortly +// after, so a later retry succeeds. +func TestPostWithRetry_RetriesConnectionRefusedThenSucceeds(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + port := ln.Addr().(*net.TCPAddr).Port + require.NoError(t, ln.Close()) + + srvCh := make(chan *http.Server, 1) + go func() { + time.Sleep(150 * time.Millisecond) + srvLn, listenErr := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if listenErr != nil { + srvCh <- nil + return + } + srv := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("recovered")) + }), + } + srvCh <- srv + _ = srv.Serve(srvLn) + }() + t.Cleanup(func() { + if srv := <-srvCh; srv != nil { + _ = srv.Close() + } + }) + + out, err := PostWithRetry(port, "endpoint", http.NoBody) + require.NoError(t, err) + assert.Equal(t, []byte("recovered"), out) +} + +func TestPostWithRetry_ExhaustsRetriesOnConnectionRefused(t *testing.T) { + // A port with no listener yields ECONNREFUSED on every attempt; the loop + // must exhaust its retries and return an error wrapping ECONNREFUSED. + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + port := ln.Addr().(*net.TCPAddr).Port + require.NoError(t, ln.Close()) + + start := time.Now() + out, err := PostWithRetry(port, "endpoint", http.NoBody) + elapsed := time.Since(start) + + require.Error(t, err) + assert.Nil(t, out) + assert.True(t, errors.Is(err, syscall.ECONNREFUSED), + "error must wrap ECONNREFUSED after exhausting retries, got: %v", err) + // At least one backoff step must elapse before giving up. + assert.Greater( + t, + elapsed, + 200*time.Millisecond, + "connection-refused must be retried before failing", + ) +} From 38055d20f65c116796621c56bf3214233bf2633b Mon Sep 17 00:00:00 2001 From: "devsy-app[bot]" <277138668+devsy-app[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:41:58 +0000 Subject: [PATCH 2/3] style(credentials): set server read header timeout Set ReadHeaderTimeout on the test http.Server so gosec G112 (Potential Slowloris Attack because ReadHeaderTimeout is not configured) passes. Lint fix only; no behavior change. This commit was created by an AI agent (OpenHands) on behalf of the user. --- pkg/credentials/request_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/credentials/request_test.go b/pkg/credentials/request_test.go index 4f7235aca..087c2acbe 100644 --- a/pkg/credentials/request_test.go +++ b/pkg/credentials/request_test.go @@ -73,6 +73,7 @@ func TestPostWithRetry_RetriesConnectionRefusedThenSucceeds(t *testing.T) { return } srv := &http.Server{ + ReadHeaderTimeout: 5 * time.Second, Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("recovered")) }), From c12694e4f15a52a6143b42651bec91a02a49ca4d Mon Sep 17 00:00:00 2001 From: Samuel K Date: Thu, 13 Aug 2026 22:15:42 -0500 Subject: [PATCH 3/3] chore: cleanup comments --- pkg/credentials/request_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pkg/credentials/request_test.go b/pkg/credentials/request_test.go index 087c2acbe..eea39d916 100644 --- a/pkg/credentials/request_test.go +++ b/pkg/credentials/request_test.go @@ -50,14 +50,10 @@ func TestPostWithRetry_ReturnsErrorOnNon200(t *testing.T) { assert.Nil(t, out) assert.Contains(t, err.Error(), "500") assert.Contains(t, err.Error(), "endpoint") - // A non-200 is not connection-refused, so it must not be retried. + assert.Less(t, elapsed, 500*time.Millisecond, "non-200 response must not trigger retries") } -// TestPostWithRetry_RetriesConnectionRefusedThenSucceeds proves the retry loop -// recovers when the credentials server comes up between attempts: the port is -// initially closed (connection refused) and a server is bound on it shortly -// after, so a later retry succeeds. func TestPostWithRetry_RetriesConnectionRefusedThenSucceeds(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) @@ -93,8 +89,6 @@ func TestPostWithRetry_RetriesConnectionRefusedThenSucceeds(t *testing.T) { } func TestPostWithRetry_ExhaustsRetriesOnConnectionRefused(t *testing.T) { - // A port with no listener yields ECONNREFUSED on every attempt; the loop - // must exhaust its retries and return an error wrapping ECONNREFUSED. ln, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) port := ln.Addr().(*net.TCPAddr).Port @@ -108,7 +102,7 @@ func TestPostWithRetry_ExhaustsRetriesOnConnectionRefused(t *testing.T) { assert.Nil(t, out) assert.True(t, errors.Is(err, syscall.ECONNREFUSED), "error must wrap ECONNREFUSED after exhausting retries, got: %v", err) - // At least one backoff step must elapse before giving up. + assert.Greater( t, elapsed,