ezhandler/handler_test.go
Dan Jones fe7e464a62 Add JSONResponder type and tests
- Introduced JSONResponder type in handler.go, combining JSON body parsing with ResponseHelper handling.
- Implemented ServeHTTP for JSONResponder to automatically decode JSON requests and return structured responses.
- Added comprehensive unit tests for JSONResponder in handler_test.go, covering successful responses, invalid JSON, empty bodies, and error propagation.
- Ensured all new tests adhere to existing project conventions and pass linting checks.
2025-07-08 16:41:08 -05:00

224 lines
6.2 KiB
Go

package ezhandler_test
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"codeberg.org/danjones000/ezhandler"
"github.com/stretchr/testify/assert"
)
func TestHandlerFunc_ServeHTTP(t *testing.T) {
tests := []struct {
name string
handlerFunc ezhandler.HandlerFunc
expectedErr error
}{
{
name: "no error",
handlerFunc: ezhandler.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusOK)
return nil
}),
expectedErr: nil,
},
{
name: "with error",
handlerFunc: ezhandler.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
return errTest
}),
expectedErr: errTest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
rec := httptest.NewRecorder()
err := tt.handlerFunc.ServeHTTP(rec, req)
if tt.expectedErr != nil {
assert.Equal(t, tt.expectedErr, err)
} else {
assert.NoError(t, err)
}
})
}
}
type testBody struct {
Name string `json:"name"`
Age int `json:"age"`
}
type jsonBodyTest struct {
name string
requestBody string
handler ezhandler.JSONBodyHandler[testBody]
expectedStatus int
expectedBody string
expectedErr error
expectedErrContains string
additionAssert func(*assert.Assertions, testBody)
}
var jsonBodyHandlerTests = []jsonBodyTest{
{
name: "successful decoding and handler execution",
requestBody: `{"name":"John Doe","age":30}`,
handler: ezhandler.JSONBodyHandler[testBody](func(w http.ResponseWriter, r *http.Request, body testBody) error {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}),
expectedStatus: http.StatusOK,
expectedBody: "success",
expectedErr: nil,
additionAssert: func(as *assert.Assertions, body testBody) {
as.Equal("John Doe", body.Name)
as.Equal(30, body.Age)
},
},
{
name: "invalid JSON body",
requestBody: `{"name":"John Doe","age":}`,
handler: ezhandler.JSONBodyHandler[testBody](func(w http.ResponseWriter, r *http.Request, body testBody) error {
return nil // Should not be called
}),
expectedErrContains: "invalid character",
},
{
name: "empty body for struct type",
requestBody: "",
handler: ezhandler.JSONBodyHandler[testBody](func(w http.ResponseWriter, r *http.Request, body testBody) error {
return nil // Should not be called
}),
expectedErr: io.EOF,
},
{
name: "handler function returns error",
requestBody: `{"name":"Jane Doe","age":25}`,
handler: ezhandler.JSONBodyHandler[testBody](func(w http.ResponseWriter, r *http.Request, body testBody) error {
return errTest
}),
expectedErr: errTest,
},
}
func TestJSONBodyHandler_ServeHTTP(t *testing.T) {
for _, tc := range jsonBodyHandlerTests {
runJSONBodyHandlerTest(t, tc)
}
}
func runJSONBodyHandlerTest(tt *testing.T, tc jsonBodyTest) {
tt.Run(tc.name, func(t *testing.T) {
handler := ezhandler.JSONBodyHandler[testBody](func(w http.ResponseWriter, r *http.Request, body testBody) error {
if err := tc.handler(w, r, body); err != nil {
return err
}
if tc.additionAssert != nil {
as := assert.New(t)
tc.additionAssert(as, body)
}
return nil
})
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tc.requestBody))
rec := httptest.NewRecorder()
err := handler.ServeHTTP(rec, req)
switch {
case tc.expectedErr != nil:
assert.Equal(t, tc.expectedErr, err)
case tc.expectedErrContains != "":
assert.ErrorContains(t, err, tc.expectedErrContains)
default:
assert.NoError(t, err)
assert.Equal(t, tc.expectedStatus, rec.Code)
assert.Equal(t, tc.expectedBody, rec.Body.String())
}
})
}
type jsonBodyResponderTest struct {
name string
requestBody string
handler ezhandler.JSONResponder[testBody]
expectedStatus int
expectedBody testBody
expectedErr error
expectedErrContains string
}
var jsonBodyResponderTests = []jsonBodyResponderTest{
{
name: "successful decoding and response",
requestBody: `{"name":"John Doe","age":30}`,
handler: ezhandler.JSONResponder[testBody](func(r *http.Request, body testBody) (ezhandler.ResponseHelper, error) {
return ezhandler.JSONResponse(body), nil
}),
expectedStatus: http.StatusOK,
expectedBody: testBody{Name: "John Doe", Age: 30},
expectedErr: nil,
},
{
name: "invalid JSON body",
requestBody: `{"name":"John Doe","age":}`,
handler: ezhandler.JSONResponder[testBody](func(r *http.Request, body testBody) (ezhandler.ResponseHelper, error) {
return nil, nil // Should not be called
}),
expectedErrContains: "invalid character",
},
{
name: "empty body for struct type",
requestBody: "",
handler: ezhandler.JSONResponder[testBody](func(r *http.Request, body testBody) (ezhandler.ResponseHelper, error) {
return nil, nil // Should not be called
}),
expectedErr: io.EOF,
},
{
name: "handler function returns error",
requestBody: `{"name":"Jane Doe","age":25}`,
handler: ezhandler.JSONResponder[testBody](func(r *http.Request, body testBody) (ezhandler.ResponseHelper, error) {
return nil, errTest
}),
expectedErr: errTest,
},
}
func TestJSONResponder_ServeHTTP(t *testing.T) {
for _, tc := range jsonBodyResponderTests {
runJSONResponderTest(t, tc)
}
}
func runJSONResponderTest(tt *testing.T, tc jsonBodyResponderTest) {
tt.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tc.requestBody))
rec := httptest.NewRecorder()
err := tc.handler.ServeHTTP(rec, req)
switch {
case tc.expectedErr != nil:
assert.Equal(t, tc.expectedErr, err)
case tc.expectedErrContains != "":
assert.ErrorContains(t, err, tc.expectedErrContains)
default:
assert.NoError(t, err)
assert.Equal(t, tc.expectedStatus, rec.Code)
// Marshal expectedBody to JSON string for comparison
expectedBodyBytes, marshalErr := json.Marshal(tc.expectedBody)
assert.NoError(t, marshalErr)
assert.JSONEq(t, string(expectedBodyBytes), rec.Body.String())
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
}
})
}