diff --git a/binding/binding_test.go b/binding/binding_test.go index a9f8b9e3..e33bdbf6 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1403,6 +1403,26 @@ func TestPlainBinding(t *testing.T) { require.NoError(t, p.Bind(req, ptr)) } +func TestPlainBindingBindBody(t *testing.T) { + p := Plain + + var s string + require.NoError(t, p.BindBody([]byte("test string"), &s)) + assert.Equal(t, "test string", s) + + var bs []byte + require.NoError(t, p.BindBody([]byte("test []byte"), &bs)) + assert.Equal(t, []byte("test []byte"), bs) + + var i int + require.Error(t, p.BindBody([]byte("test fail"), &i)) + + require.NoError(t, p.BindBody([]byte(""), nil)) + + var ptr *string + require.NoError(t, p.BindBody([]byte(""), ptr)) +} + func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/codec/json/json_test.go b/codec/json/json_test.go new file mode 100644 index 00000000..e21dbbb7 --- /dev/null +++ b/codec/json/json_test.go @@ -0,0 +1,53 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package json + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJSONMarshal(t *testing.T) { + data := map[string]string{"key": "value"} + result, err := API.Marshal(data) + require.NoError(t, err) + assert.JSONEq(t, `{"key":"value"}`, string(result)) +} + +func TestJSONUnmarshal(t *testing.T) { + var data map[string]string + err := API.Unmarshal([]byte(`{"key":"value"}`), &data) + require.NoError(t, err) + assert.Equal(t, "value", data["key"]) +} + +func TestJSONMarshalIndent(t *testing.T) { + data := map[string]string{"key": "value"} + result, err := API.MarshalIndent(data, "", " ") + require.NoError(t, err) + assert.Contains(t, string(result), `"key": "value"`) +} + +func TestJSONNewEncoder(t *testing.T) { + var buf bytes.Buffer + encoder := API.NewEncoder(&buf) + require.NotNil(t, encoder) + err := encoder.Encode(map[string]string{"key": "value"}) + require.NoError(t, err) + assert.JSONEq(t, `{"key":"value"}`, buf.String()) +} + +func TestJSONNewDecoder(t *testing.T) { + buf := bytes.NewBufferString(`{"key":"value"}`) + decoder := API.NewDecoder(buf) + require.NotNil(t, decoder) + var data map[string]string + err := decoder.Decode(&data) + require.NoError(t, err) + assert.Equal(t, "value", data["key"]) +} diff --git a/context.go b/context.go index a00d1e55..92fb3704 100644 --- a/context.go +++ b/context.go @@ -1056,6 +1056,7 @@ func (c *Context) requestHeader(key string) string { /************************************/ // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. +// Uses http.StatusContinue constant for better code clarity. func bodyAllowedForStatus(status int) bool { switch { case status >= http.StatusContinue && status < http.StatusOK: diff --git a/context_test.go b/context_test.go index 41694585..6f6f233d 100644 --- a/context_test.go +++ b/context_test.go @@ -40,6 +40,12 @@ var _ context.Context = (*Context)(nil) var errTestRender = errors.New("TestRender") +type errReader int + +func (errReader) Read(p []byte) (n int, err error) { + return 0, errors.New("test error") +} + // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { @@ -1033,6 +1039,7 @@ func TestContextGetCookie(t *testing.T) { func TestContextBodyAllowedForStatus(t *testing.T) { assert.False(t, bodyAllowedForStatus(http.StatusProcessing)) assert.False(t, bodyAllowedForStatus(http.StatusNoContent)) + assert.False(t, bodyAllowedForStatus(http.StatusContinue)) assert.False(t, bodyAllowedForStatus(http.StatusNotModified)) assert.True(t, bodyAllowedForStatus(http.StatusInternalServerError)) } @@ -2947,6 +2954,17 @@ func TestContextGetRawData(t *testing.T) { assert.Equal(t, "Fetch binary post data", string(data)) } +func TestContextGetRawDataNilBody(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) + c.Request.Body = nil + + data, err := c.GetRawData() + require.Error(t, err) + assert.Nil(t, data) + assert.Equal(t, "cannot read nil body", err.Error()) +} + func TestContextRenderDataFromReader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -3535,6 +3553,24 @@ func TestContextSetCookieData(t *testing.T) { setCookie := c.Writer.Header().Get("Set-Cookie") assert.Contains(t, setCookie, "SameSite=None") }) + + // Test that SameSiteDefaultMode is replaced with context's SameSite + t.Run("SameSiteDefaultMode is replaced with context SameSite", func(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.SetSameSite(http.SameSiteLaxMode) + cookie := &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteDefaultMode, + } + c.SetCookieData(cookie) + setCookie := c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + }) } func TestGetMapFromFormData(t *testing.T) { @@ -3752,3 +3788,43 @@ func BenchmarkGetMapFromFormData(b *testing.B) { }) } } + +func TestInitFormCacheParseMultipartFormError(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("test")) + c.Request.Header.Set("Content-Type", "multipart/form-data; boundary=invalid") + c.engine.MaxMultipartMemory = -1 + c.initFormCache() + assert.NotNil(t, c.formCache) +} + +func TestFormFileParseMultipartFormError(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("test")) + c.Request.Header.Set("Content-Type", "multipart/form-data; boundary=invalid") + c.engine.MaxMultipartMemory = -1 + _, err := c.FormFile("file") + require.Error(t, err) +} + +func TestShouldBindBodyWithTypeAssertionFailure(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", strings.NewReader(`{"foo":"FOO"}`)) + c.Set(BodyBytesKey, "not a byte slice") + var obj struct { + Foo string `json:"foo"` + } + require.NoError(t, c.ShouldBindBodyWith(&obj, binding.JSON)) + assert.Equal(t, "FOO", obj.Foo) +} + +func TestShouldBindBodyWithReadError(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", errReader(0)) + var obj struct { + Foo string `json:"foo"` + } + require.Error(t, c.ShouldBindBodyWith(&obj, binding.JSON)) +} diff --git a/gin_test.go b/gin_test.go index 43c9494d..22d3b87c 100644 --- a/gin_test.go +++ b/gin_test.go @@ -1084,3 +1084,39 @@ func TestUpdateRouteTreesCalledOnce(t *testing.T) { assert.Equal(t, "ok", w.Body.String()) } } + +func TestServeErrorWritten(t *testing.T) { + SetMode(TestMode) + router := New() + router.Use(func(c *Context) { + c.Writer.WriteHeader(http.StatusNotFound) + _, _ = c.Writer.Write([]byte("custom error")) + c.Next() + }) + router.NoRoute(func(c *Context) { + c.Next() + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/notfound", nil) + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusNotFound, w.Code) + assert.Equal(t, "custom error", w.Body.String()) +} + +func TestServeErrorStatusMismatch(t *testing.T) { + SetMode(TestMode) + router := New() + router.Use(func(c *Context) { + c.Writer.WriteHeader(http.StatusInternalServerError) + c.Next() + }) + router.NoRoute(func(c *Context) { + c.Next() + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/notfound", nil) + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) +} diff --git a/logger_test.go b/logger_test.go index 53d0df95..2d22c6e1 100644 --- a/logger_test.go +++ b/logger_test.go @@ -329,6 +329,7 @@ func TestColorForLatency(t *testing.T) { assert.Equal(t, white, colorForLantency(time.Millisecond*20), "20ms should be white") assert.Equal(t, green, colorForLantency(time.Millisecond*150), "150ms should be green") assert.Equal(t, cyan, colorForLantency(time.Millisecond*250), "250ms should be cyan") + assert.Equal(t, blue, colorForLantency(time.Millisecond*400), "400ms should be blue") assert.Equal(t, yellow, colorForLantency(time.Millisecond*600), "600ms should be yellow") assert.Equal(t, magenta, colorForLantency(time.Millisecond*1500), "1.5s should be magenta") assert.Equal(t, red, colorForLantency(time.Second*3), "other things should be red") diff --git a/utils_test.go b/utils_test.go index 893ebc88..168b1850 100644 --- a/utils_test.go +++ b/utils_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func init() { @@ -145,6 +146,15 @@ func TestMarshalXMLforH(t *testing.T) { assert.Error(t, e) } +func TestMarshalXMLforHSuccess(t *testing.T) { + h := H{ + "key": "value", + } + data, err := xml.Marshal(h) + require.NoError(t, err) + assert.Contains(t, string(data), "value") +} + func TestIsASCII(t *testing.T) { assert.True(t, isASCII("test")) assert.False(t, isASCII("๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ"))