diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 0098b952..ea933e7e 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -21,7 +21,7 @@ jobs: with: go-version: "^1" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@v7 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser diff --git a/binding/binding_test.go b/binding/binding_test.go index a9f8b9e3..f90488cd 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -21,7 +21,7 @@ import ( "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" "google.golang.org/protobuf/proto" ) diff --git a/binding/bson.go b/binding/bson.go index 4a698247..464890f0 100644 --- a/binding/bson.go +++ b/binding/bson.go @@ -8,7 +8,7 @@ import ( "io" "net/http" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) type bsonBinding struct{} 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..44db7475 100644 --- a/context_test.go +++ b/context_test.go @@ -32,7 +32,7 @@ import ( testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" "google.golang.org/protobuf/proto" ) @@ -1031,6 +1031,7 @@ func TestContextGetCookie(t *testing.T) { } func TestContextBodyAllowedForStatus(t *testing.T) { + assert.False(t, bodyAllowedForStatus(http.StatusContinue)) assert.False(t, bodyAllowedForStatus(http.StatusProcessing)) assert.False(t, bodyAllowedForStatus(http.StatusNoContent)) assert.False(t, bodyAllowedForStatus(http.StatusNotModified)) @@ -2947,6 +2948,16 @@ 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) + + data, err := c.GetRawData() + assert.Nil(t, data) + require.Error(t, err) + assert.Equal(t, "cannot read nil body", err.Error()) +} + func TestContextRenderDataFromReader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -3535,6 +3546,24 @@ func TestContextSetCookieData(t *testing.T) { setCookie := c.Writer.Header().Get("Set-Cookie") assert.Contains(t, setCookie, "SameSite=None") }) + + // Test that SameSiteDefaultMode inherits from context's sameSite + t.Run("SameSiteDefaultMode inherits context sameSite", func(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.SetSameSite(http.SameSiteStrictMode) + 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, "SameSite=Strict") + }) } func TestGetMapFromFormData(t *testing.T) { diff --git a/docs/doc.md b/docs/doc.md index 449c8d02..7201df5c 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -22,6 +22,7 @@ - [How to write log file](#how-to-write-log-file) - [Custom Log Format](#custom-log-format) - [Controlling Log output coloring](#controlling-log-output-coloring) + - [Avoid logging query strings](#avoid-loging-query-strings) - [Model binding and validation](#model-binding-and-validation) - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) @@ -592,6 +593,20 @@ func main() { } ``` +### Avoid logging query strings + +```go +func main() { + router := gin.New() + + // SkipQueryString indicates that the logger should not log the query string. + // For example, /path?q=1 will be logged as /path + loggerConfig := gin.LoggerConfig{SkipQueryString: true} + + router.Use(gin.LoggerWithConfig(loggerConfig)) +} +``` + ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). diff --git a/go.mod b/go.mod index 19ff4752..0f278049 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.24.7 require ( github.com/bytedance/sonic v1.15.0 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.28.0 + github.com/go-playground/validator/v10 v10.30.1 github.com/goccy/go-json v0.10.5 github.com/goccy/go-yaml v1.19.2 github.com/json-iterator/go v1.1.12 @@ -17,7 +17,7 @@ require ( github.com/quic-go/quic-go v0.59.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 - go.mongodb.org/mongo-driver v1.17.9 + go.mongodb.org/mongo-driver/v2 v2.5.0 golang.org/x/net v0.50.0 google.golang.org/protobuf v1.36.10 ) @@ -29,7 +29,7 @@ require ( github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index ef6d6eff..71c07795 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= -github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -20,8 +20,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= -github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= @@ -71,8 +71,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= -go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= diff --git a/logger.go b/logger.go index 6441f7ea..cf92553a 100644 --- a/logger.go +++ b/logger.go @@ -48,6 +48,11 @@ type LoggerConfig struct { // Optional. SkipPaths []string + // SkipQueryString indicates that query strings should not be written + // for cases such as when API keys are passed via query strings. + // Optional. Default value is false. + SkipQueryString bool + // Skip is a Skipper that indicates which logs should not be written. // Optional. Skip Skipper @@ -298,7 +303,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param.BodySize = c.Writer.Size() - if raw != "" { + if raw != "" && !conf.SkipQueryString { path = path + "?" + raw } diff --git a/logger_test.go b/logger_test.go index 53d0df95..395d97e6 100644 --- a/logger_test.go +++ b/logger_test.go @@ -318,20 +318,21 @@ func TestColorForStatus(t *testing.T) { } func TestColorForLatency(t *testing.T) { - colorForLantency := func(latency time.Duration) string { + colorForLatency := func(latency time.Duration) string { p := LogFormatterParams{ Latency: latency, } return p.LatencyColor() } - assert.Equal(t, white, colorForLantency(time.Duration(0)), "0 should be white") - 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, 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") + assert.Equal(t, white, colorForLatency(time.Duration(0)), "0 should be white") + assert.Equal(t, white, colorForLatency(time.Millisecond*20), "20ms should be white") + assert.Equal(t, green, colorForLatency(time.Millisecond*150), "150ms should be green") + assert.Equal(t, cyan, colorForLatency(time.Millisecond*250), "250ms should be cyan") + assert.Equal(t, blue, colorForLatency(time.Millisecond*400), "400ms should be blue") + assert.Equal(t, yellow, colorForLatency(time.Millisecond*600), "600ms should be yellow") + assert.Equal(t, magenta, colorForLatency(time.Millisecond*1500), "1.5s should be magenta") + assert.Equal(t, red, colorForLatency(time.Second*3), "other things should be red") } func TestResetColor(t *testing.T) { @@ -471,3 +472,17 @@ func TestForceConsoleColor(t *testing.T) { // reset console color mode. consoleColorMode = autoColor } + +func TestLoggerWithConfigSkipQueryString(t *testing.T) { + buffer := new(strings.Builder) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + SkipQueryString: true, + })) + router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) }) + + PerformRequest(router, "GET", "/logged?a=21") + assert.Contains(t, buffer.String(), "200") + assert.NotContains(t, buffer.String(), "a=21") +} diff --git a/render/bson.go b/render/bson.go index 7332b8b2..07f02333 100644 --- a/render/bson.go +++ b/render/bson.go @@ -7,7 +7,7 @@ package render import ( "net/http" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) // BSON contains the given interface object. diff --git a/render/data.go b/render/data.go index a653ea30..2c0ad5e3 100644 --- a/render/data.go +++ b/render/data.go @@ -4,7 +4,10 @@ package render -import "net/http" +import ( + "net/http" + "strconv" +) // Data contains ContentType and bytes data. type Data struct { @@ -15,6 +18,9 @@ type Data struct { // Render (Data) writes data with custom ContentType. func (r Data) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) + if len(r.Data) > 0 { + w.Header().Set("Content-Length", strconv.Itoa(len(r.Data))) + } _, err = w.Write(r.Data) return } diff --git a/render/render_test.go b/render/render_test.go index 7213e48f..b48ab3d3 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -8,6 +8,7 @@ import ( "encoding/xml" "errors" "html/template" + "io" "net" "net/http" "net/http/httptest" @@ -15,17 +16,13 @@ import ( "strings" "testing" - "github.com/gin-gonic/gin/codec/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" "google.golang.org/protobuf/proto" ) -// TODO unit tests -// test errors - func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]any{ @@ -140,19 +137,44 @@ func TestRenderJsonpJSON(t *testing.T) { } type errorWriter struct { - bufString string + bufString string + ErrThreshold int // 1-based threshold. If 1, errors on the 1st Write call. + writeCount int *httptest.ResponseRecorder } var _ http.ResponseWriter = (*errorWriter)(nil) +func (w *errorWriter) Header() http.Header { + if w.ResponseRecorder == nil { + w.ResponseRecorder = httptest.NewRecorder() + } + return w.ResponseRecorder.Header() +} + +func (w *errorWriter) WriteHeader(statusCode int) { + if w.ResponseRecorder == nil { + w.ResponseRecorder = httptest.NewRecorder() + } + w.ResponseRecorder.WriteHeader(statusCode) +} + func (w *errorWriter) Write(buf []byte) (int, error) { - if string(buf) == w.bufString { - return 0, errors.New(`write "` + w.bufString + `" error`) + w.writeCount++ + if (w.bufString != "" && string(buf) == w.bufString) || (w.ErrThreshold > 0 && w.writeCount >= w.ErrThreshold) { + return 0, errors.New(`write error`) + } + if w.ResponseRecorder == nil { + w.ResponseRecorder = httptest.NewRecorder() } return w.ResponseRecorder.Write(buf) } +func (w *errorWriter) reset() { + w.writeCount = 0 + w.ResponseRecorder = httptest.NewRecorder() +} + func TestRenderJsonpJSONError(t *testing.T) { ew := &errorWriter{ ResponseRecorder: httptest.NewRecorder(), @@ -165,23 +187,33 @@ func TestRenderJsonpJSONError(t *testing.T) { }, } - cb := template.JSEscapeString(jsonpJSON.Callback) - ew.bufString = cb - err := jsonpJSON.Render(ew) // error was returned while writing callback - assert.Equal(t, `write "`+cb+`" error`, err.Error()) + // error was returned while writing callback + ew.reset() + ew.ErrThreshold = 1 + err := jsonpJSON.Render(ew) + require.Error(t, err) + assert.Equal(t, "write error", err.Error()) - ew.bufString = `(` + // error was returned while writing "(" + ew.reset() + ew.ErrThreshold = 2 err = jsonpJSON.Render(ew) - assert.Equal(t, `write "`+`(`+`" error`, err.Error()) + require.Error(t, err) + assert.Equal(t, "write error", err.Error()) - data, _ := json.API.Marshal(jsonpJSON.Data) // error was returned while writing data - ew.bufString = string(data) + // error was returned while writing data + ew.reset() + ew.ErrThreshold = 3 err = jsonpJSON.Render(ew) - assert.Equal(t, `write "`+string(data)+`" error`, err.Error()) + require.Error(t, err) + assert.Equal(t, "write error", err.Error()) - ew.bufString = `);` + // error was returned while writing ");" + ew.reset() + ew.ErrThreshold = 4 err = jsonpJSON.Render(ew) - assert.Equal(t, `write "`+`);`+`" error`, err.Error()) + require.Error(t, err) + assert.Equal(t, "write error", err.Error()) } func TestRenderJsonpJSONError2(t *testing.T) { @@ -385,6 +417,30 @@ func TestRenderBSON(t *testing.T) { assert.Equal(t, "application/bson", w.Header().Get("Content-Type")) } +func TestRenderBSONError(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + err := (BSON{data}).Render(w) + require.Error(t, err) +} + +func TestRenderBSONWriteError(t *testing.T) { + type testStruct struct { + Value string + } + data := &testStruct{Value: "test"} + + ew := &errorWriter{ + ErrThreshold: 1, + ResponseRecorder: httptest.NewRecorder(), + } + + err := (BSON{data}).Render(ew) + require.Error(t, err) + assert.Equal(t, "write error", err.Error()) +} + func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ @@ -401,6 +457,15 @@ func TestRenderXML(t *testing.T) { assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } +func TestRenderXMLError(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + err := (XML{data}).Render(w) + require.Error(t, err) + assert.Contains(t, err.Error(), "xml: unsupported type: chan int") +} + func TestRenderRedirect(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "/test-redirect", nil) require.NoError(t, err) @@ -453,6 +518,52 @@ func TestRenderData(t *testing.T) { require.NoError(t, err) assert.Equal(t, "#!PNG some raw data", w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) + assert.Equal(t, "19", w.Header().Get("Content-Length")) +} + +func TestRenderDataContentLength(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + size, err := strconv.Atoi(r.URL.Query().Get("size")) + assert.NoError(t, err) + + data := Data{ + ContentType: "application/octet-stream", + Data: make([]byte, size), + } + assert.NoError(t, data.Render(w)) + })) + t.Cleanup(srv.Close) + + for _, size := range []int{0, 1, 100, 100_000} { + t.Run(strconv.Itoa(size), func(t *testing.T) { + resp, err := http.Get(srv.URL + "?size=" + strconv.Itoa(size)) + require.NoError(t, err) + defer resp.Body.Close() + + assert.Equal(t, "application/octet-stream", resp.Header.Get("Content-Type")) + assert.Equal(t, strconv.Itoa(size), resp.Header.Get("Content-Length")) + + actual, err := io.Copy(io.Discard, resp.Body) + require.NoError(t, err) + assert.EqualValues(t, size, actual) + }) + } +} + +func TestRenderDataError(t *testing.T) { + ew := &errorWriter{ + ErrThreshold: 1, + ResponseRecorder: httptest.NewRecorder(), + } + data := []byte("#!PNG some raw data") + + err := (Data{ + ContentType: "image/png", + Data: data, + }).Render(ew) + + require.Error(t, err) + assert.Equal(t, "write error", err.Error()) } func TestRenderString(t *testing.T) { @@ -594,6 +705,32 @@ func TestRenderHTMLDebugPanics(t *testing.T) { assert.Panics(t, func() { htmlRender.Instance("", nil) }) } +func TestRenderHTMLTemplateError(t *testing.T) { + w := httptest.NewRecorder() + templ := template.Must(template.New("t").Parse(`Hello {{if .name}}{{.name.DoesNotExist}}{{end}}`)) + + htmlRender := HTMLProduction{Template: templ} + instance := htmlRender.Instance("t", map[string]any{ + "name": "alexandernyquist", + }) + + err := instance.Render(w) + require.Error(t, err) +} + +func TestRenderHTMLTemplateExecuteError(t *testing.T) { + w := httptest.NewRecorder() + templ := template.Must(template.New("t").Parse(`Hello {{.name.invalid}}`)) + + htmlRender := HTMLProduction{Template: templ} + instance := htmlRender.Instance("t", map[string]any{ + "name": "alexandernyquist", + }) + + err := instance.Render(w) + require.Error(t, err) +} + func TestRenderReader(t *testing.T) { w := httptest.NewRecorder() @@ -645,10 +782,10 @@ func TestRenderWriteError(t *testing.T) { prefix := "my-prefix:" r := SecureJSON{Data: data, Prefix: prefix} ew := &errorWriter{ - bufString: prefix, + ErrThreshold: 1, ResponseRecorder: httptest.NewRecorder(), } err := r.Render(ew) require.Error(t, err) - assert.Equal(t, `write "my-prefix:" error`, err.Error()) + assert.Equal(t, "write error", err.Error()) } diff --git a/utils_test.go b/utils_test.go index 893ebc88..e1f2c332 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,17 @@ func TestMarshalXMLforH(t *testing.T) { 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), "value1") + assert.Contains(t, string(data), "123") +} + func TestIsASCII(t *testing.T) { assert.True(t, isASCII("test")) assert.False(t, isASCII("๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ"))