Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkg/util/httputil/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ func NewResponseRecorder(w http.ResponseWriter) *ResponseRecorder {
}

// Write writes the response body to the captured buffer and the underlying ResponseWriter.
// Mirrors net/http behaviour: if WriteHeader has not been called, status defaults to 200.
func (r *ResponseRecorder) Write(b []byte) (int, error) {
if r.Status == 0 {
r.Status = http.StatusOK
}
r.Body.Write(b)
return r.ResponseWriter.Write(b)
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/util/httputil/recorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ func TestResponseRecorder_Write(t *testing.T) {
assert.Equal(t, testBody, rr.Body.Bytes())
}

func TestResponseRecorder_Write_DefaultsStatusTo200(t *testing.T) {
// Write without a prior WriteHeader must record 200, mirroring net/http behaviour.
rr := NewResponseRecorder(httptest.NewRecorder())
_, err := rr.Write([]byte("hello"))
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rr.Status)
}

func TestResponseRecorder_Write_PreservesExplicitStatus(t *testing.T) {
rr := NewResponseRecorder(httptest.NewRecorder())
rr.WriteHeader(http.StatusCreated)
_, err := rr.Write([]byte("created"))
require.NoError(t, err)
assert.Equal(t, http.StatusCreated, rr.Status)
}

func TestResponseRecorder_WriteHeader(t *testing.T) {
rr := NewResponseRecorder(httptest.NewRecorder())
rr.WriteHeader(http.StatusOK)
Expand Down
3 changes: 1 addition & 2 deletions pkg/webkit/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ func (k *Kit) Plug(plugs ...Plug) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := NewContext(w, r)
h := plug(func(c *Context) error {
r = c.Request
next.ServeHTTP(w, r)
next.ServeHTTP(c.Response, c.Request)
return nil
})
if err := h(ctx); err != nil {
Expand Down
24 changes: 24 additions & 0 deletions pkg/webkit/mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ func TestKit_Plug(t *testing.T) {
assert.Equal(t, http.StatusOK, rr.Code)
})

t.Run("Response writer swap in plug is visible to downstream handler", func(t *testing.T) {
// Regression: Plug used to call next.ServeHTTP(w, r) with the captured outer
// writer, discarding any ctx.Response swap made by the plug (e.g. a recorder).
t.Parallel()
app := New()

var recorded int
app.Plug(func(next Handler) Handler {
return func(ctx *Context) error {
rr := httptest.NewRecorder()
ctx.Response = rr
err := next(ctx)
recorded = rr.Code
return err
}
})
app.Get("/", func(ctx *Context) error {
return ctx.String(http.StatusCreated, "hi")
})

app.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
assert.Equal(t, http.StatusCreated, recorded, "plug's recorder must see the status written by the handler")
})

t.Run("Runs on OPTIONS preflight to GET-only route", func(t *testing.T) {
t.Parallel()
app := New()
Expand Down
Loading