diff --git a/pkg/net/request.go b/pkg/net/request.go index 48c72d6..da6d2ea 100644 --- a/pkg/net/request.go +++ b/pkg/net/request.go @@ -110,7 +110,7 @@ func executeRequest[T any](req *stdhttp.Request, config *CurlConf[T]) (res *T, s } b, err := io.ReadAll(resp.Body) if err != nil { - return res, 0, fmt.Errorf("failed to read response body: %w", err) + return res, resp.StatusCode, fmt.Errorf("failed to read response body: %w", err) } var result T if strings.TrimSpace(strings.ReplaceAll(string(b), "\"", "")) != "" { diff --git a/pkg/net/request_test.go b/pkg/net/request_test.go index b7ae3cc..86970ed 100644 --- a/pkg/net/request_test.go +++ b/pkg/net/request_test.go @@ -3,6 +3,7 @@ package net_test import ( "context" "encoding/json" + "errors" "fmt" "net/http" "net/http/httptest" @@ -337,3 +338,36 @@ func newTestServer(t *testing.T, handler func(w http.ResponseWriter, r *http.Req t.Cleanup(srv.Close) return srv } + +// errReadCloser fails on the first Read so the body cannot be consumed. +type errReadCloser struct{} + +func (errReadCloser) Read([]byte) (int, error) { return 0, errors.New("connection reset") } +func (errReadCloser) Close() error { return nil } + +// bodyFailRoundTripper answers with a real status code but an unreadable body. +type bodyFailRoundTripper struct{ status int } + +func (rt bodyFailRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: rt.status, + Header: make(http.Header), + Body: errReadCloser{}, + }, nil +} + +func TestCurlReportsStatusWhenBodyReadFails(t *testing.T) { + // given a server that answers 503 but whose body cannot be read + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + client := &http.Client{Transport: bodyFailRoundTripper{status: http.StatusServiceUnavailable}} + + // when + res, code, err := netpkg.GetCurl[TestResponse](ctx, "http://example.invalid/test", nil, + netpkg.WithHTTPClient[TestResponse](client)) + + // then the caller still learns the status, as it does for a decode failure + require.ErrorContains(t, err, "failed to read response body") + require.Equal(t, http.StatusServiceUnavailable, code, "status code should survive a body read failure") + require.Nil(t, res) +}