From 6f1d5fe3cdb171a08928c3c9dd3fbcfc9ee1b521 Mon Sep 17 00:00:00 2001 From: Amirhf Date: Sat, 28 Feb 2026 05:41:57 +0330 Subject: [PATCH] test(render): add comprehensive error handling tests (#4541) * test(render): add comprehensive error handling tests Add error case tests for XML, Data, BSON, and HTML renderers to improve test coverage and ensure proper error handling: - TestRenderXMLError: validates XML marshal error handling for unsupported types - TestRenderDataError: validates Data write error handling - TestRenderBSONError: validates BSON marshal error handling for unsupported types - TestRenderBSONWriteError: validates BSON write error handling - TestRenderHTMLTemplateError: validates HTML template execution error with invalid field access - TestRenderHTMLTemplateExecuteError: validates HTML template execution error with invalid nested field All tests pass and maintain 100% coverage for the render package. * test(render): improve robustness of error handling tests based on PR feedback --------- Co-authored-by: Bo-Yi Wu Co-authored-by: AmirHossein Fallah --- render/render_test.go | 146 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 126 insertions(+), 20 deletions(-) diff --git a/render/render_test.go b/render/render_test.go index 9c3019eb..b48ab3d3 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -16,7 +16,6 @@ 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" @@ -24,9 +23,6 @@ import ( "google.golang.org/protobuf/proto" ) -// TODO unit tests -// test errors - func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]any{ @@ -141,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(), @@ -166,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) { @@ -386,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{ @@ -402,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) @@ -486,6 +550,22 @@ func TestRenderDataContentLength(t *testing.T) { } } +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) { w := httptest.NewRecorder() @@ -625,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() @@ -676,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()) }