...

Source file src/github.com/gin-gonic/gin/render/render_test.go

Documentation: github.com/gin-gonic/gin/render

     1  // Copyright 2014 Manu Martinez-Almeida. All rights reserved.
     2  // Use of this source code is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package render
     6  
     7  import (
     8  	"encoding/xml"
     9  	"errors"
    10  	"html/template"
    11  	"net"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"strconv"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/gin-gonic/gin/internal/json"
    19  	testdata "github.com/gin-gonic/gin/testdata/protoexample"
    20  	"github.com/stretchr/testify/assert"
    21  	"google.golang.org/protobuf/proto"
    22  )
    23  
    24  // TODO unit tests
    25  // test errors
    26  
    27  func TestRenderJSON(t *testing.T) {
    28  	w := httptest.NewRecorder()
    29  	data := map[string]any{
    30  		"foo":  "bar",
    31  		"html": "<b>",
    32  	}
    33  
    34  	(JSON{data}).WriteContentType(w)
    35  	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
    36  
    37  	err := (JSON{data}).Render(w)
    38  
    39  	assert.NoError(t, err)
    40  	assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
    41  	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
    42  }
    43  
    44  func TestRenderJSONError(t *testing.T) {
    45  	w := httptest.NewRecorder()
    46  	data := make(chan int)
    47  
    48  	// json: unsupported type: chan int
    49  	assert.Error(t, (JSON{data}).Render(w))
    50  }
    51  
    52  func TestRenderIndentedJSON(t *testing.T) {
    53  	w := httptest.NewRecorder()
    54  	data := map[string]any{
    55  		"foo": "bar",
    56  		"bar": "foo",
    57  	}
    58  
    59  	err := (IndentedJSON{data}).Render(w)
    60  
    61  	assert.NoError(t, err)
    62  	assert.Equal(t, "{\n    \"bar\": \"foo\",\n    \"foo\": \"bar\"\n}", w.Body.String())
    63  	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
    64  }
    65  
    66  func TestRenderIndentedJSONPanics(t *testing.T) {
    67  	w := httptest.NewRecorder()
    68  	data := make(chan int)
    69  
    70  	// json: unsupported type: chan int
    71  	err := (IndentedJSON{data}).Render(w)
    72  	assert.Error(t, err)
    73  }
    74  
    75  func TestRenderSecureJSON(t *testing.T) {
    76  	w1 := httptest.NewRecorder()
    77  	data := map[string]any{
    78  		"foo": "bar",
    79  	}
    80  
    81  	(SecureJSON{"while(1);", data}).WriteContentType(w1)
    82  	assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
    83  
    84  	err1 := (SecureJSON{"while(1);", data}).Render(w1)
    85  
    86  	assert.NoError(t, err1)
    87  	assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String())
    88  	assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
    89  
    90  	w2 := httptest.NewRecorder()
    91  	datas := []map[string]any{{
    92  		"foo": "bar",
    93  	}, {
    94  		"bar": "foo",
    95  	}}
    96  
    97  	err2 := (SecureJSON{"while(1);", datas}).Render(w2)
    98  	assert.NoError(t, err2)
    99  	assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String())
   100  	assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
   101  }
   102  
   103  func TestRenderSecureJSONFail(t *testing.T) {
   104  	w := httptest.NewRecorder()
   105  	data := make(chan int)
   106  
   107  	// json: unsupported type: chan int
   108  	err := (SecureJSON{"while(1);", data}).Render(w)
   109  	assert.Error(t, err)
   110  }
   111  
   112  func TestRenderJsonpJSON(t *testing.T) {
   113  	w1 := httptest.NewRecorder()
   114  	data := map[string]any{
   115  		"foo": "bar",
   116  	}
   117  
   118  	(JsonpJSON{"x", data}).WriteContentType(w1)
   119  	assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
   120  
   121  	err1 := (JsonpJSON{"x", data}).Render(w1)
   122  
   123  	assert.NoError(t, err1)
   124  	assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String())
   125  	assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
   126  
   127  	w2 := httptest.NewRecorder()
   128  	datas := []map[string]any{{
   129  		"foo": "bar",
   130  	}, {
   131  		"bar": "foo",
   132  	}}
   133  
   134  	err2 := (JsonpJSON{"x", datas}).Render(w2)
   135  	assert.NoError(t, err2)
   136  	assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String())
   137  	assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type"))
   138  }
   139  
   140  type errorWriter struct {
   141  	bufString string
   142  	*httptest.ResponseRecorder
   143  }
   144  
   145  var _ http.ResponseWriter = (*errorWriter)(nil)
   146  
   147  func (w *errorWriter) Write(buf []byte) (int, error) {
   148  	if string(buf) == w.bufString {
   149  		return 0, errors.New(`write "` + w.bufString + `" error`)
   150  	}
   151  	return w.ResponseRecorder.Write(buf)
   152  }
   153  
   154  func TestRenderJsonpJSONError(t *testing.T) {
   155  	ew := &errorWriter{
   156  		ResponseRecorder: httptest.NewRecorder(),
   157  	}
   158  
   159  	jsonpJSON := JsonpJSON{
   160  		Callback: "foo",
   161  		Data: map[string]string{
   162  			"foo": "bar",
   163  		},
   164  	}
   165  
   166  	cb := template.JSEscapeString(jsonpJSON.Callback)
   167  	ew.bufString = cb
   168  	err := jsonpJSON.Render(ew) // error was returned while writing callback
   169  	assert.Equal(t, `write "`+cb+`" error`, err.Error())
   170  
   171  	ew.bufString = `(`
   172  	err = jsonpJSON.Render(ew)
   173  	assert.Equal(t, `write "`+`(`+`" error`, err.Error())
   174  
   175  	data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data
   176  	ew.bufString = string(data)
   177  	err = jsonpJSON.Render(ew)
   178  	assert.Equal(t, `write "`+string(data)+`" error`, err.Error())
   179  
   180  	ew.bufString = `);`
   181  	err = jsonpJSON.Render(ew)
   182  	assert.Equal(t, `write "`+`);`+`" error`, err.Error())
   183  }
   184  
   185  func TestRenderJsonpJSONError2(t *testing.T) {
   186  	w := httptest.NewRecorder()
   187  	data := map[string]any{
   188  		"foo": "bar",
   189  	}
   190  	(JsonpJSON{"", data}).WriteContentType(w)
   191  	assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
   192  
   193  	e := (JsonpJSON{"", data}).Render(w)
   194  	assert.NoError(t, e)
   195  
   196  	assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
   197  	assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
   198  }
   199  
   200  func TestRenderJsonpJSONFail(t *testing.T) {
   201  	w := httptest.NewRecorder()
   202  	data := make(chan int)
   203  
   204  	// json: unsupported type: chan int
   205  	err := (JsonpJSON{"x", data}).Render(w)
   206  	assert.Error(t, err)
   207  }
   208  
   209  func TestRenderAsciiJSON(t *testing.T) {
   210  	w1 := httptest.NewRecorder()
   211  	data1 := map[string]any{
   212  		"lang": "GO语言",
   213  		"tag":  "<br>",
   214  	}
   215  
   216  	err := (AsciiJSON{data1}).Render(w1)
   217  
   218  	assert.NoError(t, err)
   219  	assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String())
   220  	assert.Equal(t, "application/json", w1.Header().Get("Content-Type"))
   221  
   222  	w2 := httptest.NewRecorder()
   223  	data2 := 3.1415926
   224  
   225  	err = (AsciiJSON{data2}).Render(w2)
   226  	assert.NoError(t, err)
   227  	assert.Equal(t, "3.1415926", w2.Body.String())
   228  }
   229  
   230  func TestRenderAsciiJSONFail(t *testing.T) {
   231  	w := httptest.NewRecorder()
   232  	data := make(chan int)
   233  
   234  	// json: unsupported type: chan int
   235  	assert.Error(t, (AsciiJSON{data}).Render(w))
   236  }
   237  
   238  func TestRenderPureJSON(t *testing.T) {
   239  	w := httptest.NewRecorder()
   240  	data := map[string]any{
   241  		"foo":  "bar",
   242  		"html": "<b>",
   243  	}
   244  	err := (PureJSON{data}).Render(w)
   245  	assert.NoError(t, err)
   246  	assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
   247  	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
   248  }
   249  
   250  type xmlmap map[string]any
   251  
   252  // Allows type H to be used with xml.Marshal
   253  func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
   254  	start.Name = xml.Name{
   255  		Space: "",
   256  		Local: "map",
   257  	}
   258  	if err := e.EncodeToken(start); err != nil {
   259  		return err
   260  	}
   261  	for key, value := range h {
   262  		elem := xml.StartElement{
   263  			Name: xml.Name{Space: "", Local: key},
   264  			Attr: []xml.Attr{},
   265  		}
   266  		if err := e.EncodeElement(value, elem); err != nil {
   267  			return err
   268  		}
   269  	}
   270  
   271  	return e.EncodeToken(xml.EndElement{Name: start.Name})
   272  }
   273  
   274  func TestRenderYAML(t *testing.T) {
   275  	w := httptest.NewRecorder()
   276  	data := `
   277  a : Easy!
   278  b:
   279  	c: 2
   280  	d: [3, 4]
   281  	`
   282  	(YAML{data}).WriteContentType(w)
   283  	assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
   284  
   285  	err := (YAML{data}).Render(w)
   286  	assert.NoError(t, err)
   287  	assert.Equal(t, "|4-\n    a : Easy!\n    b:\n    \tc: 2\n    \td: [3, 4]\n    \t\n", w.Body.String())
   288  	assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
   289  }
   290  
   291  type fail struct{}
   292  
   293  // Hook MarshalYAML
   294  func (ft *fail) MarshalYAML() (any, error) {
   295  	return nil, errors.New("fail")
   296  }
   297  
   298  func TestRenderYAMLFail(t *testing.T) {
   299  	w := httptest.NewRecorder()
   300  	err := (YAML{&fail{}}).Render(w)
   301  	assert.Error(t, err)
   302  }
   303  
   304  func TestRenderTOML(t *testing.T) {
   305  	w := httptest.NewRecorder()
   306  	data := map[string]any{
   307  		"foo":  "bar",
   308  		"html": "<b>",
   309  	}
   310  	(TOML{data}).WriteContentType(w)
   311  	assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
   312  
   313  	err := (TOML{data}).Render(w)
   314  	assert.NoError(t, err)
   315  	assert.Equal(t, "foo = 'bar'\nhtml = '<b>'\n", w.Body.String())
   316  	assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
   317  }
   318  
   319  func TestRenderTOMLFail(t *testing.T) {
   320  	w := httptest.NewRecorder()
   321  	err := (TOML{net.IPv4bcast}).Render(w)
   322  	assert.Error(t, err)
   323  }
   324  
   325  // test Protobuf rendering
   326  func TestRenderProtoBuf(t *testing.T) {
   327  	w := httptest.NewRecorder()
   328  	reps := []int64{int64(1), int64(2)}
   329  	label := "test"
   330  	data := &testdata.Test{
   331  		Label: &label,
   332  		Reps:  reps,
   333  	}
   334  
   335  	(ProtoBuf{data}).WriteContentType(w)
   336  	protoData, err := proto.Marshal(data)
   337  	assert.NoError(t, err)
   338  	assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
   339  
   340  	err = (ProtoBuf{data}).Render(w)
   341  
   342  	assert.NoError(t, err)
   343  	assert.Equal(t, string(protoData), w.Body.String())
   344  	assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
   345  }
   346  
   347  func TestRenderProtoBufFail(t *testing.T) {
   348  	w := httptest.NewRecorder()
   349  	data := &testdata.Test{}
   350  	err := (ProtoBuf{data}).Render(w)
   351  	assert.Error(t, err)
   352  }
   353  
   354  func TestRenderXML(t *testing.T) {
   355  	w := httptest.NewRecorder()
   356  	data := xmlmap{
   357  		"foo": "bar",
   358  	}
   359  
   360  	(XML{data}).WriteContentType(w)
   361  	assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
   362  
   363  	err := (XML{data}).Render(w)
   364  
   365  	assert.NoError(t, err)
   366  	assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
   367  	assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
   368  }
   369  
   370  func TestRenderRedirect(t *testing.T) {
   371  	req, err := http.NewRequest("GET", "/test-redirect", nil)
   372  	assert.NoError(t, err)
   373  
   374  	data1 := Redirect{
   375  		Code:     http.StatusMovedPermanently,
   376  		Request:  req,
   377  		Location: "/new/location",
   378  	}
   379  
   380  	w := httptest.NewRecorder()
   381  	err = data1.Render(w)
   382  	assert.NoError(t, err)
   383  
   384  	data2 := Redirect{
   385  		Code:     http.StatusOK,
   386  		Request:  req,
   387  		Location: "/new/location",
   388  	}
   389  
   390  	w = httptest.NewRecorder()
   391  	assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() {
   392  		err := data2.Render(w)
   393  		assert.NoError(t, err)
   394  	})
   395  
   396  	data3 := Redirect{
   397  		Code:     http.StatusCreated,
   398  		Request:  req,
   399  		Location: "/new/location",
   400  	}
   401  
   402  	w = httptest.NewRecorder()
   403  	err = data3.Render(w)
   404  	assert.NoError(t, err)
   405  
   406  	// only improve coverage
   407  	data2.WriteContentType(w)
   408  }
   409  
   410  func TestRenderData(t *testing.T) {
   411  	w := httptest.NewRecorder()
   412  	data := []byte("#!PNG some raw data")
   413  
   414  	err := (Data{
   415  		ContentType: "image/png",
   416  		Data:        data,
   417  	}).Render(w)
   418  
   419  	assert.NoError(t, err)
   420  	assert.Equal(t, "#!PNG some raw data", w.Body.String())
   421  	assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
   422  }
   423  
   424  func TestRenderString(t *testing.T) {
   425  	w := httptest.NewRecorder()
   426  
   427  	(String{
   428  		Format: "hello %s %d",
   429  		Data:   []any{},
   430  	}).WriteContentType(w)
   431  	assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
   432  
   433  	err := (String{
   434  		Format: "hola %s %d",
   435  		Data:   []any{"manu", 2},
   436  	}).Render(w)
   437  
   438  	assert.NoError(t, err)
   439  	assert.Equal(t, "hola manu 2", w.Body.String())
   440  	assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
   441  }
   442  
   443  func TestRenderStringLenZero(t *testing.T) {
   444  	w := httptest.NewRecorder()
   445  
   446  	err := (String{
   447  		Format: "hola %s %d",
   448  		Data:   []any{},
   449  	}).Render(w)
   450  
   451  	assert.NoError(t, err)
   452  	assert.Equal(t, "hola %s %d", w.Body.String())
   453  	assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
   454  }
   455  
   456  func TestRenderHTMLTemplate(t *testing.T) {
   457  	w := httptest.NewRecorder()
   458  	templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
   459  
   460  	htmlRender := HTMLProduction{Template: templ}
   461  	instance := htmlRender.Instance("t", map[string]any{
   462  		"name": "alexandernyquist",
   463  	})
   464  
   465  	err := instance.Render(w)
   466  
   467  	assert.NoError(t, err)
   468  	assert.Equal(t, "Hello alexandernyquist", w.Body.String())
   469  	assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
   470  }
   471  
   472  func TestRenderHTMLTemplateEmptyName(t *testing.T) {
   473  	w := httptest.NewRecorder()
   474  	templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
   475  
   476  	htmlRender := HTMLProduction{Template: templ}
   477  	instance := htmlRender.Instance("", map[string]any{
   478  		"name": "alexandernyquist",
   479  	})
   480  
   481  	err := instance.Render(w)
   482  
   483  	assert.NoError(t, err)
   484  	assert.Equal(t, "Hello alexandernyquist", w.Body.String())
   485  	assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
   486  }
   487  
   488  func TestRenderHTMLDebugFiles(t *testing.T) {
   489  	w := httptest.NewRecorder()
   490  	htmlRender := HTMLDebug{
   491  		Files:   []string{"../testdata/template/hello.tmpl"},
   492  		Glob:    "",
   493  		Delims:  Delims{Left: "{[{", Right: "}]}"},
   494  		FuncMap: nil,
   495  	}
   496  	instance := htmlRender.Instance("hello.tmpl", map[string]any{
   497  		"name": "thinkerou",
   498  	})
   499  
   500  	err := instance.Render(w)
   501  
   502  	assert.NoError(t, err)
   503  	assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
   504  	assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
   505  }
   506  
   507  func TestRenderHTMLDebugGlob(t *testing.T) {
   508  	w := httptest.NewRecorder()
   509  	htmlRender := HTMLDebug{
   510  		Files:   nil,
   511  		Glob:    "../testdata/template/hello*",
   512  		Delims:  Delims{Left: "{[{", Right: "}]}"},
   513  		FuncMap: nil,
   514  	}
   515  	instance := htmlRender.Instance("hello.tmpl", map[string]any{
   516  		"name": "thinkerou",
   517  	})
   518  
   519  	err := instance.Render(w)
   520  
   521  	assert.NoError(t, err)
   522  	assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
   523  	assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
   524  }
   525  
   526  func TestRenderHTMLDebugPanics(t *testing.T) {
   527  	htmlRender := HTMLDebug{
   528  		Files:   nil,
   529  		Glob:    "",
   530  		Delims:  Delims{"{{", "}}"},
   531  		FuncMap: nil,
   532  	}
   533  	assert.Panics(t, func() { htmlRender.Instance("", nil) })
   534  }
   535  
   536  func TestRenderReader(t *testing.T) {
   537  	w := httptest.NewRecorder()
   538  
   539  	body := "#!PNG some raw data"
   540  	headers := make(map[string]string)
   541  	headers["Content-Disposition"] = `attachment; filename="filename.png"`
   542  	headers["x-request-id"] = "requestId"
   543  
   544  	err := (Reader{
   545  		ContentLength: int64(len(body)),
   546  		ContentType:   "image/png",
   547  		Reader:        strings.NewReader(body),
   548  		Headers:       headers,
   549  	}).Render(w)
   550  
   551  	assert.NoError(t, err)
   552  	assert.Equal(t, body, w.Body.String())
   553  	assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
   554  	assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length"))
   555  	assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
   556  	assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
   557  }
   558  
   559  func TestRenderReaderNoContentLength(t *testing.T) {
   560  	w := httptest.NewRecorder()
   561  
   562  	body := "#!PNG some raw data"
   563  	headers := make(map[string]string)
   564  	headers["Content-Disposition"] = `attachment; filename="filename.png"`
   565  	headers["x-request-id"] = "requestId"
   566  
   567  	err := (Reader{
   568  		ContentLength: -1,
   569  		ContentType:   "image/png",
   570  		Reader:        strings.NewReader(body),
   571  		Headers:       headers,
   572  	}).Render(w)
   573  
   574  	assert.NoError(t, err)
   575  	assert.Equal(t, body, w.Body.String())
   576  	assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
   577  	assert.NotContains(t, "Content-Length", w.Header())
   578  	assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
   579  	assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
   580  }
   581  

View as plain text