From 501fd137de561028e34d192b02de22db5d836a83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:36:40 +0000 Subject: [PATCH 1/2] Initial plan From a5c75ec9dbe39d9f0df174e531ea39b4bbd3063f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:39:10 +0000 Subject: [PATCH 2/2] test: codify shared response-writer wrapper pattern --- internal/httputil/response_writer.go | 4 +++- internal/server/response_writer.go | 5 +++-- internal/server/response_writer_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/internal/httputil/response_writer.go b/internal/httputil/response_writer.go index 515d566b5..7a21697d5 100644 --- a/internal/httputil/response_writer.go +++ b/internal/httputil/response_writer.go @@ -8,7 +8,9 @@ import "net/http" // interfaces (e.g. http.Flusher, http.Hijacker) on the underlying writer. // // Embed BaseResponseWriter in package-specific types to avoid duplicating -// this status-capture boilerplate. +// this status-capture boilerplate. Package-specific wrappers can then add +// their own concerns, such as server response body buffering or tracing-only +// status capture, while reusing the same net/http-compatible behavior. type BaseResponseWriter struct { http.ResponseWriter // StatusCode holds the captured HTTP status code. It is set by WriteHeader diff --git a/internal/server/response_writer.go b/internal/server/response_writer.go index ce46d753f..68493041b 100644 --- a/internal/server/response_writer.go +++ b/internal/server/response_writer.go @@ -11,8 +11,9 @@ import ( var logResponseWriter = logger.ForFile() // responseWriter wraps http.ResponseWriter to capture response body and status code. -// It embeds httputil.BaseResponseWriter for shared status-code capture logic, and -// adds body buffering for debug logging. +// It embeds httputil.BaseResponseWriter for shared status-code capture logic and +// optional-interface passthrough via Unwrap, and adds body buffering for debug +// logging. type responseWriter struct { httputil.BaseResponseWriter body bytes.Buffer diff --git a/internal/server/response_writer_test.go b/internal/server/response_writer_test.go index 7feeee722..79be2bd4b 100644 --- a/internal/server/response_writer_test.go +++ b/internal/server/response_writer_test.go @@ -9,6 +9,13 @@ import ( "github.com/stretchr/testify/require" ) +type mockFlusher struct { + *httptest.ResponseRecorder + flushed bool +} + +func (m *mockFlusher) Flush() { m.flushed = true } + func TestNewResponseWriter(t *testing.T) { t.Parallel() @@ -76,6 +83,7 @@ func TestResponseWriter_WriteImplicitStatus200(t *testing.T) { require.NoError(t, err) // net/http sets status 200 on first Write if WriteHeader wasn't called + assert.Equal(t, http.StatusOK, w.StatusCode) assert.Equal(t, http.StatusOK, rr.Code) } @@ -127,3 +135,19 @@ func TestResponseWriter_HeaderPassthrough(t *testing.T) { assert.Equal(t, "application/json", rr.Header().Get("Content-Type")) } + +func TestResponseWriter_UnwrapExposesOptionalInterfaces(t *testing.T) { + t.Parallel() + + rr := &mockFlusher{ResponseRecorder: httptest.NewRecorder()} + w := newResponseWriter(rr) + + _, err := w.Write([]byte("content")) + require.NoError(t, err) + + rc := http.NewResponseController(w) + err = rc.Flush() + require.NoError(t, err) + assert.True(t, rr.flushed) + assert.Equal(t, []byte("content"), w.Body()) +}