✨ Refactor ResponseHelper and add ResponderHandler
- Modified ResponseHelper.Body signature to return an io.Reader and an error. - Updated ResponseHandler.ServeHTTP to handle errors from ResponseHelper.Body. - Implemented JSONResponse and JSONResponseWithStatus functions for easier JSON responses. - Added comprehensive unit tests for JSON response handling, including error scenarios. - Extended Helper.ResponderHandler with a new test case to ensure proper error handling and response generation. - Resolved linting issues related to dynamic error definition and function length in tests.
This commit is contained in:
parent
a136616088
commit
1c16a893d6
5 changed files with 270 additions and 1 deletions
163
response_test.go
Normal file
163
response_test.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
package ezhandler_test
|
||||
|
||||
import (
|
||||
"codeberg.org/danjones000/ezhandler"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var errCircularMarshal = errors.New("json: unsupported value: encountered a cycle")
|
||||
|
||||
func TestJSONResponse(t *testing.T) {
|
||||
type testData struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
data := testData{Message: "Hello", Code: 200}
|
||||
respHelper := ezhandler.JSONResponse(data)
|
||||
|
||||
assert.Equal(t, http.StatusOK, respHelper.Status())
|
||||
|
||||
headers := respHelper.Headers()
|
||||
assert.Equal(t, "application/json", headers.Get("Content-Type"))
|
||||
|
||||
body, err := respHelper.Body()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, body)
|
||||
|
||||
var decodedData testData
|
||||
err = json.NewDecoder(body).Decode(&decodedData)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, data, decodedData)
|
||||
}
|
||||
|
||||
func TestJSONResponseWithStatus(t *testing.T) {
|
||||
type testData struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
data := testData{Key: "value"}
|
||||
customStatus := http.StatusCreated
|
||||
respHelper := ezhandler.JSONResponseWithStatus(data, customStatus)
|
||||
|
||||
assert.Equal(t, customStatus, respHelper.Status())
|
||||
|
||||
headers := respHelper.Headers()
|
||||
assert.Equal(t, "application/json", headers.Get("Content-Type"))
|
||||
|
||||
body, err := respHelper.Body()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, body)
|
||||
|
||||
var decodedData testData
|
||||
err = json.NewDecoder(body).Decode(&decodedData)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, data, decodedData)
|
||||
}
|
||||
|
||||
func TestJSONResponse_BodyError(t *testing.T) {
|
||||
// This type cannot be marshaled to JSON due to a circular reference
|
||||
type Circular struct {
|
||||
Self *Circular
|
||||
}
|
||||
data := Circular{}
|
||||
data.Self = &data
|
||||
|
||||
respHelper := ezhandler.JSONResponse(data)
|
||||
|
||||
body, err := respHelper.Body()
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, body)
|
||||
}
|
||||
|
||||
func TestResponseHandler_ServeHTTP_JSONResponse(t *testing.T) {
|
||||
type testData struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
handler ezhandler.ResponseHandler
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "successful JSON response",
|
||||
handler: ezhandler.ResponseHandler(func(r *http.Request) (ezhandler.ResponseHelper, error) {
|
||||
return ezhandler.JSONResponse(testData{Message: "success"}), nil
|
||||
}),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: "{\"message\":\"success\"}",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "JSON response with custom status",
|
||||
handler: ezhandler.ResponseHandler(func(r *http.Request) (ezhandler.ResponseHelper, error) {
|
||||
return ezhandler.JSONResponseWithStatus(testData{Message: "created"}, http.StatusCreated), nil
|
||||
}),
|
||||
expectedStatus: http.StatusCreated,
|
||||
expectedBody: "{\"message\":\"created\"}",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "error from ResponseHandler",
|
||||
handler: ezhandler.ResponseHandler(func(r *http.Request) (ezhandler.ResponseHelper, error) {
|
||||
return nil, errTest // Using errTest from helper_test.go
|
||||
}),
|
||||
expectedStatus: http.StatusOK, // Status won't be set if handler returns error before writing header
|
||||
expectedBody: "",
|
||||
expectedError: errTest,
|
||||
},
|
||||
{
|
||||
name: "error from Body() method",
|
||||
handler: ezhandler.ResponseHandler(func(r *http.Request) (ezhandler.ResponseHelper, error) {
|
||||
type Circular struct {
|
||||
Self *Circular
|
||||
}
|
||||
data := Circular{}
|
||||
data.Self = &data
|
||||
return ezhandler.JSONResponse(data), nil
|
||||
}),
|
||||
expectedStatus: http.StatusOK, // Status won't be set if Body() returns error
|
||||
expectedBody: "",
|
||||
expectedError: errCircularMarshal, // The error is returned by ServeHTTP, not the handler func
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
runResponseHandlerTest(t, tt)
|
||||
}
|
||||
}
|
||||
|
||||
func runResponseHandlerTest(t *testing.T, tt struct {
|
||||
name string
|
||||
handler ezhandler.ResponseHandler
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
expectedError error
|
||||
}) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
err := tt.handler.ServeHTTP(rec, req)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
assert.ErrorContains(t, err, tt.expectedError.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedStatus, rec.Code)
|
||||
assert.Equal(t, tt.expectedBody, rec.Body.String())
|
||||
if tt.expectedBody != "" {
|
||||
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue