gin/utils_test.go
c879873067877881111 3e6682d0f1 test(utils): cover EncodeToken error path in H.MarshalXML
H.MarshalXML returns early when the opening e.EncodeToken(start) call
fails, but no test covered that error path.

Because xml.Encoder buffers its output, the underlying writer is not
called until the buffer flushes. The test writes an 8KB token first to
push the encoder into an error state, then calls MarshalXML and asserts
the write error is propagated.
2026-07-19 14:31:26 +08:00

196 lines
5.4 KiB
Go

// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"math"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func init() {
SetMode(TestMode)
}
func BenchmarkParseAccept(b *testing.B) {
for b.Loop() {
parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
}
}
type testStruct struct {
T *testing.T
}
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
assert.Equal(t.T, http.MethodPost, req.Method)
assert.Equal(t.T, "/path", req.URL.Path)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "hello")
}
func TestWrap(t *testing.T) {
router := New()
router.POST("/path", WrapH(&testStruct{t}))
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
assert.Equal(t, http.MethodGet, req.Method)
assert.Equal(t, "/path2", req.URL.Path)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "hola!")
}))
w := PerformRequest(router, http.MethodPost, "/path")
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "hello", w.Body.String())
w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "hola!", w.Body.String())
}
func TestLastChar(t *testing.T) {
assert.Equal(t, uint8('a'), lastChar("hola"))
assert.Equal(t, uint8('s'), lastChar("adios"))
assert.Panics(t, func() { lastChar("") })
}
func TestParseAccept(t *testing.T) {
parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
assert.Len(t, parts, 4)
assert.Equal(t, "text/html", parts[0])
assert.Equal(t, "application/xhtml+xml", parts[1])
assert.Equal(t, "application/xml", parts[2])
assert.Equal(t, "*/*", parts[3])
}
func TestChooseData(t *testing.T) {
A := "a"
B := "b"
assert.Equal(t, A, chooseData(A, B))
assert.Equal(t, B, chooseData(nil, B))
assert.Panics(t, func() { chooseData(nil, nil) })
}
func TestFilterFlags(t *testing.T) {
result := filterFlags("text/html ")
assert.Equal(t, "text/html", result)
result = filterFlags("text/html;")
assert.Equal(t, "text/html", result)
}
func TestFunctionName(t *testing.T) {
assert.Regexp(t, `^(.*/vendor/)?github.com/gin-gonic/gin.somefunction$`, nameOfFunction(somefunction))
}
func somefunction() {
// this empty function is used by TestFunctionName()
}
func TestJoinPaths(t *testing.T) {
assert.Empty(t, joinPaths("", ""))
assert.Equal(t, "/", joinPaths("", "/"))
assert.Equal(t, "/a", joinPaths("/a", ""))
assert.Equal(t, "/a/", joinPaths("/a/", ""))
assert.Equal(t, "/a/", joinPaths("/a/", "/"))
assert.Equal(t, "/a/", joinPaths("/a", "/"))
assert.Equal(t, "/a/hola", joinPaths("/a", "/hola"))
assert.Equal(t, "/a/hola", joinPaths("/a/", "/hola"))
assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola/"))
assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola//"))
}
type bindTestStruct struct {
Foo string `form:"foo" binding:"required"`
Bar int `form:"bar" binding:"min=4"`
}
func TestBindMiddleware(t *testing.T) {
var value *bindTestStruct
var called bool
router := New()
router.GET("/", Bind(bindTestStruct{}), func(c *Context) {
called = true
value = c.MustGet(BindKey).(*bindTestStruct)
})
PerformRequest(router, http.MethodGet, "/?foo=hola&bar=10")
assert.True(t, called)
assert.Equal(t, "hola", value.Foo)
assert.Equal(t, 10, value.Bar)
called = false
PerformRequest(router, http.MethodGet, "/?foo=hola&bar=1")
assert.False(t, called)
assert.Panics(t, func() {
Bind(&bindTestStruct{})
})
}
func TestMarshalXMLforH(t *testing.T) {
h := H{
"": "test",
}
var b bytes.Buffer
enc := xml.NewEncoder(&b)
var x xml.StartElement
e := h.MarshalXML(enc, x)
assert.Error(t, e)
}
func TestMarshalXMLforHSuccess(t *testing.T) {
h := H{
"key1": "value1",
"key2": 123,
}
data, err := xml.Marshal(h)
require.NoError(t, err)
assert.Contains(t, string(data), "<key1>value1</key1>")
assert.Contains(t, string(data), "<key2>123</key2>")
}
// errXMLWriter always fails, to exercise encoder write-error paths.
type errXMLWriter struct{}
func (errXMLWriter) Write(_ []byte) (int, error) { return 0, errors.New("write failed") }
// TestMarshalXMLEncodeTokenError covers the branch where the opening
// EncodeToken(start) fails. xml.Encoder buffers its output, so the failing
// writer is only reached once the buffer overflows; we prime it with a large
// token first, which puts the encoder into an error state. MarshalXML's very
// first EncodeToken then returns that cached write error.
func TestMarshalXMLEncodeTokenError(t *testing.T) {
enc := xml.NewEncoder(errXMLWriter{})
// Overflow the encoder's internal buffer so it flushes to the failing
// writer and latches the error.
require.Error(t, enc.EncodeToken(xml.CharData(bytes.Repeat([]byte("a"), 8192))))
err := H{"key": "value"}.MarshalXML(enc, xml.StartElement{})
assert.Error(t, err)
}
func TestIsASCII(t *testing.T) {
assert.True(t, isASCII("test"))
assert.False(t, isASCII("🧡💛💚💙💜"))
}
func TestSafeInt8(t *testing.T) {
assert.Equal(t, int8(100), safeInt8(100))
assert.Equal(t, int8(math.MaxInt8), safeInt8(int(math.MaxInt8)+123))
}
func TestSafeUint16(t *testing.T) {
assert.Equal(t, uint16(100), safeUint16(100))
assert.Equal(t, uint16(math.MaxUint16), safeUint16(int(math.MaxUint16)+123))
}