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)) } }) } }) }