855 lines
26 KiB
Go
855 lines
26 KiB
Go
package lazyhandler
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.jeffthecoder.xyz/public/lazyhandler/magic"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
type StatusPathValues struct {
|
|
Status int `pathvalue:"status"`
|
|
}
|
|
|
|
type HelloWorldRequest struct {
|
|
Name string `json:"name"`
|
|
}
|
|
type HelloWorldResponse struct {
|
|
Greeting string `json:"greeting"`
|
|
}
|
|
|
|
type HelloWorldParams struct {
|
|
Name *string `pathvalue:"name,match=" query:"name"`
|
|
}
|
|
|
|
type MultipleParams struct {
|
|
Numbers []int `query:"numbers"`
|
|
Strings []string `query:"strings"`
|
|
OptionalNumber *int `query:"optionalNumber"`
|
|
OptionalString *string `query:"optionalString"`
|
|
}
|
|
|
|
type QueryTypes struct {
|
|
IntField int `query:"int_field"`
|
|
StringField string `query:"string_field"`
|
|
BoolField bool `query:"bool_field"`
|
|
FloatField32 float32 `query:"float32_field"`
|
|
FloatField64 float64 `query:"float64_field"`
|
|
IntSlice []int `query:"int_slice"`
|
|
StringSlice []string `query:"string_slice"`
|
|
BoolSlice []bool `query:"bool_slice"`
|
|
OptionalInt *int `query:"optional_int"`
|
|
}
|
|
|
|
type IntegerPathValues struct {
|
|
IntField int `pathvalue:"int_val"`
|
|
Int8Field int8 `pathvalue:"int8_val"`
|
|
Int16Field int16 `pathvalue:"int16_val"`
|
|
Int32Field int32 `pathvalue:"int32_val"`
|
|
Int64Field int64 `pathvalue:"int64_val"`
|
|
UintField uint `pathvalue:"uint_val"`
|
|
Uint8Field uint8 `pathvalue:"uint8_val"`
|
|
Uint16Field uint16 `pathvalue:"uint16_val"`
|
|
Uint32Field uint32 `pathvalue:"uint32_val"`
|
|
Uint64Field uint64 `pathvalue:"uint64_val"`
|
|
}
|
|
|
|
type ErrorFromRequest struct{}
|
|
|
|
func (e ErrorFromRequest) FromRequest(r *http.Request) error {
|
|
return fmt.Errorf("custom error from request")
|
|
}
|
|
|
|
type PtrErrorFromRequest struct{}
|
|
|
|
func (e *PtrErrorFromRequest) FromRequest(r *http.Request) error {
|
|
return fmt.Errorf("custom error from pointer request")
|
|
}
|
|
|
|
func TestNotAFunc(t *testing.T) {
|
|
v, err := MagicHandler(1)
|
|
if err == nil {
|
|
t.Fatalf("expect not ok, get %v", v)
|
|
}
|
|
}
|
|
|
|
func TestEmptyFunc(t *testing.T) {
|
|
MagicHandler(func() {
|
|
|
|
})
|
|
}
|
|
|
|
type SomeType int
|
|
|
|
func (obj SomeType) Func() {
|
|
|
|
}
|
|
|
|
func TestMethod(t *testing.T) {
|
|
obj := SomeType(0)
|
|
MagicHandler(obj.Func)
|
|
}
|
|
|
|
func TestJsonResponse(t *testing.T) {
|
|
MustMagicHandler(func() magic.Json[map[string]any] {
|
|
return magic.Json[map[string]any]{}
|
|
})
|
|
}
|
|
|
|
type testCase struct {
|
|
Path string
|
|
Pattern string
|
|
|
|
CreateHandler func() http.Handler
|
|
ExpectPanicOnCreateHandler bool
|
|
|
|
NoCheck bool
|
|
MakeRequest func(string) *http.Request
|
|
CheckResponse func(*http.Response) error
|
|
}
|
|
|
|
var (
|
|
cases = []testCase{
|
|
{
|
|
Path: "/",
|
|
Pattern: "/{$}",
|
|
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func() (int, string, error) {
|
|
return 200, "hi", nil
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
r, err := http.NewRequest("GET", base+"/", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/error-checked-before-other-return-values",
|
|
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func() (int, string, error) {
|
|
// it checks errors first
|
|
return 200, "hi", fmt.Errorf("it is handled before other return values")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
r, err := http.NewRequest("GET", base+"/return-error", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/return-nil",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func() (magic.RespondWriter, error) {
|
|
// no response body, 200
|
|
return nil, nil
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/no-return",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func() {
|
|
// return nothing at all
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/no-error",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func() int {
|
|
// well, error is not needed
|
|
return http.StatusNoContent
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/respond-json",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func() magic.Json[map[string]any] {
|
|
return magic.Json[map[string]any]{
|
|
Data: map[string]any{
|
|
"message": "hello, world",
|
|
},
|
|
}
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/extract-json",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(body magic.Json[HelloWorldRequest]) magic.Json[HelloWorldResponse] {
|
|
return magic.Json[HelloWorldResponse]{
|
|
Data: HelloWorldResponse{
|
|
Greeting: "hello, " + body.Data.Name,
|
|
},
|
|
}
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/extract-optional",
|
|
CreateHandler: func() http.Handler {
|
|
// the parameter accepts a pointer to extractor, which indicates that it is optional
|
|
return MustMagicHandler(func(body *magic.Json[HelloWorldRequest]) magic.Json[HelloWorldResponse] {
|
|
name := "world"
|
|
if body != nil {
|
|
name = body.Data.Name
|
|
}
|
|
return magic.Json[HelloWorldResponse]{
|
|
Data: HelloWorldResponse{
|
|
Greeting: "hello, " + name,
|
|
},
|
|
}
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Path: "/inject-request-respond-status-from-http-request/418", // yeah...i'm a teapot, lol
|
|
Pattern: "/inject-request-respond-status-from-http-request/{status}",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(r *http.Request) (int, error) {
|
|
// inject *http.Request, respond with status only
|
|
return strconv.Atoi(r.PathValue("status"))
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Path: "/inject-path-value-respond-status/418", // yeah...i'm a teapot, lol
|
|
Pattern: "/inject-path-value-respond-status/{status}",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(pathValues magic.PathValue[StatusPathValues]) int {
|
|
return pathValues.Data.Status
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Path: "/inject-optional-path-value/rose", // name extracted
|
|
Pattern: "/inject-optional-path-value/{name}",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(pathValues magic.PathValue[HelloWorldParams]) magic.Json[HelloWorldResponse] {
|
|
name := "world"
|
|
if pathValues.Data.Name != nil {
|
|
name = *pathValues.Data.Name
|
|
}
|
|
return magic.Json[HelloWorldResponse]{
|
|
Data: HelloWorldResponse{
|
|
Greeting: "hello, " + name,
|
|
},
|
|
}
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Path: "/inject-optional-path-value", // name fallback to world
|
|
Pattern: "/inject-optional-path-value",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(pathValues magic.PathValue[HelloWorldParams]) magic.Json[HelloWorldResponse] {
|
|
name := "world"
|
|
if pathValues.Data.Name != nil {
|
|
name = *pathValues.Data.Name
|
|
}
|
|
return magic.Json[HelloWorldResponse]{
|
|
Data: HelloWorldResponse{
|
|
Greeting: "hello, " + name,
|
|
},
|
|
}
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/direct-response-writer-extractor",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(w http.ResponseWriter) {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/hahaha-it-act-just-like-http-handlerfunc",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/websocket",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(ws *websocket.Conn) {
|
|
|
|
})
|
|
},
|
|
NoCheck: true,
|
|
},
|
|
{
|
|
Pattern: "/multiple-respond-extractor-should-panic-on-create",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(conn *websocket.Conn) magic.Json[HelloWorldResponse] {
|
|
return magic.Json[HelloWorldResponse]{}
|
|
})
|
|
},
|
|
ExpectPanicOnCreateHandler: true,
|
|
},
|
|
// {
|
|
// Pattern: "/hello-query",
|
|
// CreateHandler: func() http.Handler {
|
|
// return MustMagicHandler(func(query magic.Query[HelloWorldParams]) magic.Json[HelloWorldResponse] {
|
|
// name := "world"
|
|
// if query.Data.Name != nil {
|
|
// name = *query.Data.Name
|
|
// }
|
|
// return magic.Json[HelloWorldResponse]{
|
|
// Data: HelloWorldResponse{
|
|
// Greeting: "hello, " + name,
|
|
// },
|
|
// }
|
|
// })
|
|
// },
|
|
// },
|
|
{
|
|
Pattern: "/hello-query",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[HelloWorldParams]) magic.Json[HelloWorldResponse] {
|
|
name := "world"
|
|
if query.Data.Name != nil {
|
|
name = *query.Data.Name
|
|
}
|
|
return magic.Json[HelloWorldResponse]{
|
|
Data: HelloWorldResponse{
|
|
Greeting: "hello, " + name,
|
|
},
|
|
}
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[MultipleParams]) {
|
|
// Add assertions for MultipleParams
|
|
if len(query.Data.Numbers) != 3 || query.Data.Numbers[0] != 1 || query.Data.Numbers[1] != 2 || query.Data.Numbers[2] != 3 {
|
|
panic(fmt.Sprintf("expected numbers [1 2 3], got %v", query.Data.Numbers))
|
|
}
|
|
if len(query.Data.Strings) != 2 || query.Data.Strings[0] != "a" || query.Data.Strings[1] != "b" {
|
|
panic(fmt.Sprintf("expected strings [a b], got %v", query.Data.Strings))
|
|
}
|
|
if query.Data.OptionalNumber == nil || *query.Data.OptionalNumber != 10 {
|
|
panic(fmt.Sprintf("expected optional number 10, got %v", query.Data.OptionalNumber))
|
|
}
|
|
if query.Data.OptionalString == nil || *query.Data.OptionalString != "optional" {
|
|
panic(fmt.Sprintf("expected optional string 'optional', got %v", query.Data.OptionalString))
|
|
}
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query?numbers=1&numbers=2&numbers=3&strings=a&strings=b&optionalNumber=10&optionalString=optional", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query-optional-missing",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[MultipleParams]) {
|
|
// Test missing optional fields
|
|
if query.Data.OptionalNumber != nil {
|
|
panic(fmt.Sprintf("expected optional number to be nil, got %v", query.Data.OptionalNumber))
|
|
}
|
|
if query.Data.OptionalString != nil {
|
|
panic(fmt.Sprintf("expected optional string to be nil, got %v", query.Data.OptionalString))
|
|
}
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query-optional-missing?numbers=1&strings=a", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query-types",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[QueryTypes]) {
|
|
// Test various data types and slices
|
|
if query.Data.IntField != 123 {
|
|
panic(fmt.Sprintf("expected int_field 123, got %d", query.Data.IntField))
|
|
}
|
|
if query.Data.StringField != "test_string" {
|
|
panic(fmt.Sprintf("expected string_field 'test_string', got '%s'", query.Data.StringField))
|
|
}
|
|
if !query.Data.BoolField {
|
|
panic(fmt.Sprintf("expected bool_field true, got %t", query.Data.BoolField))
|
|
}
|
|
if query.Data.FloatField32 != 1.23 {
|
|
panic(fmt.Sprintf("expected float32_field 1.23, got %f", query.Data.FloatField32))
|
|
}
|
|
if query.Data.FloatField64 != 4.56 {
|
|
panic(fmt.Sprintf("expected float64_field 4.56, got %f", query.Data.FloatField64))
|
|
}
|
|
if len(query.Data.IntSlice) != 2 || query.Data.IntSlice[0] != 1 || query.Data.IntSlice[1] != 2 {
|
|
panic(fmt.Sprintf("expected int_slice [1 2], got %v", query.Data.IntSlice))
|
|
}
|
|
if len(query.Data.StringSlice) != 2 || query.Data.StringSlice[0] != "x" || query.Data.StringSlice[1] != "y" {
|
|
panic(fmt.Sprintf("expected string_slice [x y], got %v", query.Data.StringSlice))
|
|
}
|
|
if len(query.Data.BoolSlice) != 2 || query.Data.BoolSlice[0] != true || query.Data.BoolSlice[1] != false {
|
|
panic(fmt.Sprintf("expected bool_slice [true false], got %v", query.Data.BoolSlice))
|
|
}
|
|
if query.Data.OptionalInt == nil || *query.Data.OptionalInt != 99 {
|
|
panic(fmt.Sprintf("expected optional_int 99, got %v", query.Data.OptionalInt))
|
|
}
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query-types?int_field=123&string_field=test_string&bool_field=true&float32_field=1.23&float64_field=4.56&int_slice=1&int_slice=2&string_slice=x&string_slice=y&bool_slice=true&bool_slice=false&optional_int=99", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query-types-missing-optional",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[QueryTypes]) {
|
|
// Test missing optional int
|
|
if query.Data.OptionalInt != nil {
|
|
panic(fmt.Sprintf("expected optional_int to be nil, got %v", query.Data.OptionalInt))
|
|
}
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query-types-missing-optional?int_field=123&string_field=test_string&bool_field=true&float32_field=1.23&float64_field=4.56&int_slice=1&int_slice=2&string_slice=x&string_slice=y&bool_slice=true&bool_slice=false", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query-invalid-int",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[QueryTypes]) {
|
|
panic("handler should not be called for invalid input")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query-invalid-int?int_field=abc&string_field=test", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query-invalid-bool",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[QueryTypes]) {
|
|
panic("handler should not be called for invalid input")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query-invalid-bool?int_field=123&string_field=test&bool_field=not_a_bool", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query-invalid-float",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[QueryTypes]) {
|
|
panic("handler should not be called for invalid input")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query-invalid-float?int_field=123&string_field=test&bool_field=true&float64_field=not_a_float", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-form",
|
|
CreateHandler: func() http.Handler {
|
|
type TestForm struct {
|
|
Name string `form:"name"`
|
|
Age int `form:"age"`
|
|
IsPro bool `form:"is_pro"`
|
|
}
|
|
return MustMagicHandler(func(form magic.Form[TestForm]) {
|
|
if form.Data.Name != "test" {
|
|
panic(fmt.Sprintf("expected name 'test', got '%s'", form.Data.Name))
|
|
}
|
|
if form.Data.Age != 30 {
|
|
panic(fmt.Sprintf("expected age 30, got %d", form.Data.Age))
|
|
}
|
|
if !form.Data.IsPro {
|
|
panic(fmt.Sprintf("expected is_pro true, got %t", form.Data.IsPro))
|
|
}
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
body := strings.NewReader("name=test&age=30&is_pro=true")
|
|
req, err := http.NewRequest("POST", base+"/test-form", body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
return req
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-form-advanced",
|
|
CreateHandler: func() http.Handler {
|
|
type TestFormAdvanced struct {
|
|
Names []string `form:"names"`
|
|
Ages [2]int `form:"ages"`
|
|
ID string `form:"id,match=^[a-f0-9]{8}$"`
|
|
Optional *string `form:"optional"`
|
|
Numbers []int `form:"numbers"`
|
|
Required string `form:"required"`
|
|
FixedSize [3]bool `form:"fixed_size"`
|
|
}
|
|
return MustMagicHandler(func(form magic.Form[TestFormAdvanced]) {
|
|
// Test successful extraction
|
|
if len(form.Data.Names) != 2 || form.Data.Names[0] != "alice" || form.Data.Names[1] != "bob" {
|
|
panic(fmt.Sprintf("expected names [alice bob], got %v", form.Data.Names))
|
|
}
|
|
if form.Data.Ages[0] != 25 || form.Data.Ages[1] != 30 {
|
|
panic(fmt.Sprintf("expected ages [25 30], got %v", form.Data.Ages))
|
|
}
|
|
if form.Data.ID != "abcdef01" {
|
|
panic(fmt.Sprintf("expected id 'abcdef01', got '%s'", form.Data.ID))
|
|
}
|
|
if form.Data.Optional == nil || *form.Data.Optional != "present" {
|
|
panic(fmt.Sprintf("expected optional 'present', got %v", form.Data.Optional))
|
|
}
|
|
if len(form.Data.Numbers) != 3 || form.Data.Numbers[0] != 1 || form.Data.Numbers[1] != 2 || form.Data.Numbers[2] != 3 {
|
|
panic(fmt.Sprintf("expected numbers [1 2 3], got %v", form.Data.Numbers))
|
|
}
|
|
if form.Data.Required != "required_value" {
|
|
panic(fmt.Sprintf("expected required 'required_value', got '%s'", form.Data.Required))
|
|
}
|
|
if form.Data.FixedSize[0] != true || form.Data.FixedSize[1] != false || form.Data.FixedSize[2] != true {
|
|
panic(fmt.Sprintf("expected fixed_size [true false true], got %v", form.Data.FixedSize))
|
|
}
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
body := strings.NewReader("names=alice&names=bob&ages=25&ages=30&id=abcdef01&optional=present&numbers=1&numbers=2&numbers=3&required=required_value&fixed_size=true&fixed_size=false&fixed_size=true")
|
|
req, err := http.NewRequest("POST", base+"/test-form-advanced", body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
return req
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-form-advanced-errors",
|
|
CreateHandler: func() http.Handler {
|
|
type TestFormAdvancedErrors struct {
|
|
Required string `form:"required"`
|
|
}
|
|
return MustMagicHandler(func(form magic.Form[TestFormAdvancedErrors]) {
|
|
// This handler should not be reached in case of errors,
|
|
// the magic handler should return an error before calling this.
|
|
panic("handler should not be called for error test cases")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
// This request is intentionally malformed to trigger errors
|
|
body := strings.NewReader("names=alice&ages=25&ages=30&id=invalid-id&numbers=1&required=required_value&fixed_size=true&fixed_size=false") // Missing one fixed_size value, invalid ID
|
|
req, err := http.NewRequest("POST", base+"/test-form-advanced-errors", body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
// Expecting an error response due to invalid form data
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
// Further checks on the error message body could be added here
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-json-non-struct",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(body magic.Json[string]) string {
|
|
return "Received: " + body.Data
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
body := strings.NewReader(`"a simple string"`)
|
|
req, err := http.NewRequest("POST", base+"/test-json-non-struct", body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusOK, resp.StatusCode)
|
|
}
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
bodyString := string(bodyBytes)
|
|
// The response is the string "Received: " + the JSON string,
|
|
// which includes the quotes from the original JSON body.
|
|
expected := `"Received: \"a simple string\""`
|
|
if !strings.Contains(bodyString, expected) {
|
|
return fmt.Errorf("expected response body to contain '%s', got '%s'", expected, bodyString)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-from-request-error",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(e ErrorFromRequest) {
|
|
panic("handler should not be called for error test cases")
|
|
})
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusInternalServerError {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusInternalServerError, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-ptr-from-request-error",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(e *PtrErrorFromRequest) {
|
|
panic("handler should not be called for error test cases")
|
|
})
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusInternalServerError {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusInternalServerError, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-form-invalid-int",
|
|
CreateHandler: func() http.Handler {
|
|
type TestForm struct {
|
|
Age int `form:"age"`
|
|
}
|
|
return MustMagicHandler(func(form magic.Form[TestForm]) {
|
|
panic("handler should not be called for invalid input")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
body := strings.NewReader("age=abc")
|
|
req, err := http.NewRequest("POST", base+"/test-form-invalid-int", body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-form-array-too-few",
|
|
CreateHandler: func() http.Handler {
|
|
type TestForm struct {
|
|
Ages [2]int `form:"ages"`
|
|
}
|
|
return MustMagicHandler(func(form magic.Form[TestForm]) {
|
|
panic("handler should not be called for invalid input")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
body := strings.NewReader("ages=25")
|
|
req, err := http.NewRequest("POST", base+"/test-form-array-too-few", body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-query-slice-invalid-entry",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(query magic.Query[QueryTypes]) {
|
|
panic("handler should not be called for invalid input")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
req, err := http.NewRequest("GET", base+"/test-query-slice-invalid-entry?int_slice=1&int_slice=abc&int_slice=3", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Pattern: "/test-double-body-read",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(body io.Reader, jsonBody magic.Json[HelloWorldRequest]) {
|
|
// This handler should panic because we are trying to read the body twice.
|
|
panic("handler should not be called")
|
|
})
|
|
},
|
|
ExpectPanicOnCreateHandler: true,
|
|
},
|
|
{
|
|
Pattern: "/websocket-fail",
|
|
CreateHandler: func() http.Handler {
|
|
return MustMagicHandler(func(ws *websocket.Conn) {
|
|
// This handler should not be called if websocket upgrade fails.
|
|
panic("handler should not be called")
|
|
})
|
|
},
|
|
MakeRequest: func(base string) *http.Request {
|
|
// A regular GET request without the upgrade headers will fail the websocket upgrade.
|
|
req, err := http.NewRequest("GET", base+"/websocket-fail", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req
|
|
},
|
|
CheckResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
return fmt.Errorf("expected status %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func TestActualFunctions(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
|
|
t.Run("register-routes", func(t *testing.T) {
|
|
for _, c := range cases {
|
|
path := c.Path
|
|
if path == "" {
|
|
path = c.Pattern
|
|
}
|
|
t.Run(strings.TrimPrefix(path, "/"), func(t *testing.T) {
|
|
defer func() {
|
|
if o := recover(); (o != nil) != c.ExpectPanicOnCreateHandler {
|
|
if c.ExpectPanicOnCreateHandler {
|
|
t.Fatal("expect panic but panic did not occur")
|
|
} else {
|
|
t.Fatalf("panic not expected: %v", o)
|
|
}
|
|
}
|
|
}()
|
|
if handler := c.CreateHandler(); handler != nil {
|
|
mux.Handle(c.Pattern, handler)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("check-actual-result", func(t *testing.T) {
|
|
server := httptest.NewTLSServer(mux)
|
|
client := server.Client()
|
|
for _, c := range cases {
|
|
if c.ExpectPanicOnCreateHandler || c.NoCheck {
|
|
continue
|
|
}
|
|
|
|
path := c.Path
|
|
if path == "" {
|
|
path = c.Pattern
|
|
}
|
|
t.Run(strings.TrimPrefix(path, "/"), func(t *testing.T) {
|
|
var req *http.Request
|
|
|
|
if c.MakeRequest != nil {
|
|
req = c.MakeRequest(server.URL)
|
|
} else {
|
|
request, err := http.NewRequest("GET", server.URL+path, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req = request
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp.Body.Close()
|
|
log.Println(resp)
|
|
if bytes, err := io.ReadAll(resp.Body); err == nil {
|
|
log.Println(string(bytes))
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
}
|