github.com/System-Glitch/goyave/v2@v2.10.3-0.20200819142921-51011e75d504/response_test.go (about) 1 package goyave 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "os" 10 "strings" 11 "testing" 12 13 "github.com/System-Glitch/goyave/v2/config" 14 "github.com/System-Glitch/goyave/v2/database" 15 "github.com/jinzhu/gorm" 16 "github.com/stretchr/testify/suite" 17 ) 18 19 type ResponseTestSuite struct { 20 suite.Suite 21 previousEnv string 22 } 23 24 func (suite *ResponseTestSuite) SetupSuite() { 25 suite.previousEnv = os.Getenv("GOYAVE_ENV") 26 os.Setenv("GOYAVE_ENV", "test") 27 if err := config.Load(); err != nil { 28 suite.FailNow(err.Error()) 29 } 30 } 31 32 func (suite *ResponseTestSuite) TestResponseStatus() { 33 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 34 response := newResponse(httptest.NewRecorder(), rawRequest) 35 response.Status(403) 36 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 37 38 suite.Equal(200, resp.StatusCode) // Not written yet 39 suite.True(response.empty) 40 suite.Equal(403, response.GetStatus()) 41 42 rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 43 response = newResponse(httptest.NewRecorder(), rawRequest) 44 response.String(403, "test") 45 resp = response.responseWriter.(*httptest.ResponseRecorder).Result() 46 47 suite.Equal(403, resp.StatusCode) 48 suite.False(response.empty) 49 suite.Equal(403, response.GetStatus()) 50 51 rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 52 response = newResponse(httptest.NewRecorder(), rawRequest) 53 response.Status(403) 54 response.Status(200) // Should have no effect 55 resp = response.responseWriter.(*httptest.ResponseRecorder).Result() 56 57 suite.Equal(200, resp.StatusCode) // Not written yet 58 suite.True(response.empty) 59 suite.Equal(403, response.GetStatus()) 60 } 61 62 func (suite *ResponseTestSuite) TestResponseHeader() { 63 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 64 response := newResponse(httptest.NewRecorder(), rawRequest) 65 response.Header().Set("Content-Type", "application/json") 66 response.Status(200) 67 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 68 69 suite.Equal(200, resp.StatusCode) 70 suite.Equal("application/json", resp.Header.Get("Content-Type")) 71 suite.True(response.empty) 72 suite.Equal(200, response.status) 73 } 74 75 func (suite *ResponseTestSuite) TestResponseError() { 76 config.Set("app.debug", true) 77 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 78 response := newResponse(httptest.NewRecorder(), rawRequest) 79 response.Error(fmt.Errorf("random error")) 80 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 81 82 suite.Equal(500, resp.StatusCode) 83 84 body, err := ioutil.ReadAll(resp.Body) 85 resp.Body.Close() 86 suite.Nil(err) 87 suite.Equal("{\"error\":\"random error\"}\n", string(body)) 88 suite.False(response.empty) 89 suite.Equal(500, response.status) 90 suite.NotNil(response.err) 91 92 rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 93 response = newResponse(httptest.NewRecorder(), rawRequest) 94 response.Error("random error") 95 resp = response.responseWriter.(*httptest.ResponseRecorder).Result() 96 97 suite.Equal(500, resp.StatusCode) 98 suite.NotNil(response.err) 99 100 body, err = ioutil.ReadAll(resp.Body) 101 resp.Body.Close() 102 suite.Nil(err) 103 suite.Equal("{\"error\":\"random error\"}\n", string(body)) 104 suite.False(response.empty) 105 suite.Equal(500, response.status) 106 suite.NotNil(response.err) 107 108 config.Set("app.debug", false) 109 rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 110 response = newResponse(httptest.NewRecorder(), rawRequest) 111 response.Error("random error") 112 resp = response.responseWriter.(*httptest.ResponseRecorder).Result() 113 114 suite.Equal(500, response.GetStatus()) 115 116 body, err = ioutil.ReadAll(resp.Body) 117 resp.Body.Close() 118 suite.Nil(err) 119 suite.Empty("", string(body)) 120 suite.True(response.empty) 121 suite.Equal("random error", response.GetError()) 122 suite.Equal(500, response.status) 123 config.Set("app.debug", true) 124 } 125 126 func (suite *ResponseTestSuite) TestResponseFile() { 127 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 128 response := newResponse(httptest.NewRecorder(), rawRequest) 129 130 response.File("config/config.test.json") 131 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 132 133 suite.Equal(200, resp.StatusCode) 134 suite.Equal("inline", resp.Header.Get("Content-Disposition")) 135 suite.Equal("application/json", resp.Header.Get("Content-Type")) 136 suite.Equal("91", resp.Header.Get("Content-Length")) // TODO get length of the file instead of hardcoding it in the test 137 suite.False(response.empty) 138 suite.Equal(200, response.status) 139 140 // Test no Content-Type override 141 rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 142 response = newResponse(httptest.NewRecorder(), rawRequest) 143 response.Header().Set("Content-Type", "text/plain") 144 response.File("config/config.test.json") 145 resp = response.responseWriter.(*httptest.ResponseRecorder).Result() 146 suite.Equal("text/plain", resp.Header.Get("Content-Type")) 147 148 // File doesn't exist 149 rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 150 response = newResponse(httptest.NewRecorder(), rawRequest) 151 err := response.File("config/doesntexist") 152 suite.Equal("open config/doesntexist: no such file or directory", err.Error()) 153 resp = response.responseWriter.(*httptest.ResponseRecorder).Result() 154 suite.Equal(404, response.status) 155 suite.True(response.empty) 156 suite.False(response.wroteHeader) 157 suite.Empty(resp.Header.Get("Content-Type")) 158 suite.Empty(resp.Header.Get("Content-Disposition")) 159 } 160 161 func (suite *ResponseTestSuite) TestResponseJSON() { 162 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 163 response := newResponse(httptest.NewRecorder(), rawRequest) 164 165 response.JSON(http.StatusOK, map[string]interface{}{ 166 "status": "ok", 167 "code": 200, 168 }) 169 170 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 171 suite.Equal(200, resp.StatusCode) 172 suite.Equal("application/json", resp.Header.Get("Content-Type")) 173 suite.False(response.empty) 174 175 body, err := ioutil.ReadAll(resp.Body) 176 resp.Body.Close() 177 if err != nil { 178 panic(err) 179 } 180 suite.Equal("{\"code\":200,\"status\":\"ok\"}\n", string(body)) 181 } 182 183 func (suite *ResponseTestSuite) TestResponseJSONHiddenFields() { 184 type Model struct { 185 Password string `model:"hide" json:",omitempty"` 186 Username string 187 } 188 189 model := &Model{ 190 Password: "bcrypted password", 191 Username: "Jeff", 192 } 193 194 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 195 response := newResponse(httptest.NewRecorder(), rawRequest) 196 197 response.JSON(http.StatusOK, model) 198 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 199 body, err := ioutil.ReadAll(resp.Body) 200 resp.Body.Close() 201 if err != nil { 202 panic(err) 203 } 204 suite.Equal("{\"Username\":\"Jeff\"}\n", string(body)) 205 } 206 207 func (suite *ResponseTestSuite) TestResponseDownload() { 208 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 209 response := newResponse(httptest.NewRecorder(), rawRequest) 210 211 response.Download("config/config.test.json", "config.json") 212 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 213 214 suite.Equal(200, resp.StatusCode) 215 suite.Equal("attachment; filename=\"config.json\"", resp.Header.Get("Content-Disposition")) 216 suite.Equal("application/json", resp.Header.Get("Content-Type")) 217 suite.Equal("91", resp.Header.Get("Content-Length")) // TODO get length of the file instead of hardcoding it in the test 218 suite.False(response.empty) 219 suite.Equal(200, response.status) 220 221 rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 222 response = newResponse(httptest.NewRecorder(), rawRequest) 223 224 err := response.Download("config/doesntexist", "config.json") 225 suite.Equal("open config/doesntexist: no such file or directory", err.Error()) 226 resp = response.responseWriter.(*httptest.ResponseRecorder).Result() 227 suite.Equal(404, response.status) 228 suite.True(response.empty) 229 suite.False(response.wroteHeader) 230 suite.Empty(resp.Header.Get("Content-Type")) 231 suite.Empty(resp.Header.Get("Content-Disposition")) 232 } 233 234 func (suite *ResponseTestSuite) TestResponseRedirect() { 235 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 236 response := newResponse(httptest.NewRecorder(), rawRequest) 237 238 response.Redirect("https://www.google.com") 239 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 240 241 suite.Equal(308, resp.StatusCode) 242 body, err := ioutil.ReadAll(resp.Body) 243 resp.Body.Close() 244 suite.Nil(err) 245 suite.Equal("<a href=\"https://www.google.com\">Permanent Redirect</a>.\n\n", string(body)) 246 suite.False(response.empty) 247 suite.Equal(308, response.status) 248 } 249 250 func (suite *ResponseTestSuite) TestResponseTemporaryRedirect() { 251 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 252 response := newResponse(httptest.NewRecorder(), rawRequest) 253 254 response.TemporaryRedirect("https://www.google.com") 255 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 256 257 suite.Equal(307, resp.StatusCode) 258 body, err := ioutil.ReadAll(resp.Body) 259 resp.Body.Close() 260 suite.Nil(err) 261 suite.Equal("<a href=\"https://www.google.com\">Temporary Redirect</a>.\n\n", string(body)) 262 suite.False(response.empty) 263 suite.Equal(307, response.status) 264 } 265 266 func (suite *ResponseTestSuite) TestResponseCookie() { 267 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 268 response := newResponse(httptest.NewRecorder(), rawRequest) 269 response.Cookie(&http.Cookie{ 270 Name: "cookie-name", 271 Value: "test", 272 }) 273 274 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 275 cookies := resp.Cookies() 276 suite.Equal(1, len(cookies)) 277 suite.Equal("cookie-name", cookies[0].Name) 278 suite.Equal("test", cookies[0].Value) 279 } 280 281 func (suite *ResponseTestSuite) TestResponseWrite() { 282 rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body")) 283 response := newResponse(httptest.NewRecorder(), rawRequest) 284 response.Write([]byte("byte array")) 285 resp := response.responseWriter.(*httptest.ResponseRecorder).Result() 286 body, err := ioutil.ReadAll(resp.Body) 287 resp.Body.Close() 288 suite.Nil(err) 289 suite.Equal("byte array", string(body)) 290 suite.False(response.empty) 291 } 292 293 func (suite *ResponseTestSuite) TestCreateTestResponse() { 294 recorder := httptest.NewRecorder() 295 response := CreateTestResponse(recorder) 296 suite.NotNil(response) 297 if response != nil { 298 suite.Equal(recorder, response.responseWriter) 299 } 300 } 301 302 func (suite *ResponseTestSuite) TestRender() { 303 // With map data 304 recorder := httptest.NewRecorder() 305 response := CreateTestResponse(recorder) 306 307 mapData := map[string]interface{}{ 308 "Status": http.StatusNotFound, 309 "Message": "Not Found.", 310 } 311 suite.Nil(response.Render(http.StatusNotFound, "error.txt", mapData)) 312 resp := recorder.Result() 313 suite.Equal(404, resp.StatusCode) 314 body, err := ioutil.ReadAll(resp.Body) 315 resp.Body.Close() 316 suite.Nil(err) 317 suite.Equal("Error 404: Not Found.", string(body)) 318 319 // With struct data 320 recorder = httptest.NewRecorder() 321 response = CreateTestResponse(recorder) 322 323 structData := struct { 324 Status int 325 Message string 326 }{ 327 Status: http.StatusNotFound, 328 Message: "Not Found.", 329 } 330 suite.Nil(response.Render(http.StatusNotFound, "error.txt", structData)) 331 resp = recorder.Result() 332 suite.Equal(404, resp.StatusCode) 333 body, err = ioutil.ReadAll(resp.Body) 334 resp.Body.Close() 335 suite.Nil(err) 336 suite.Equal("Error 404: Not Found.", string(body)) 337 resp.Body.Close() 338 339 // Non-existing template and exec error 340 recorder = httptest.NewRecorder() 341 response = CreateTestResponse(recorder) 342 343 suite.NotNil(response.Render(http.StatusNotFound, "non-existing-template", nil)) 344 345 suite.NotNil(response.Render(http.StatusNotFound, "invalid.txt", nil)) 346 resp = recorder.Result() 347 suite.Equal(0, response.status) 348 suite.Equal(200, resp.StatusCode) // Status not written in case of error 349 resp.Body.Close() 350 } 351 352 func (suite *ResponseTestSuite) TestRenderHTML() { 353 // With map data 354 recorder := httptest.NewRecorder() 355 response := CreateTestResponse(recorder) 356 357 mapData := map[string]interface{}{ 358 "Status": http.StatusNotFound, 359 "Message": "Not Found.", 360 } 361 suite.Nil(response.RenderHTML(http.StatusNotFound, "error.html", mapData)) 362 resp := recorder.Result() 363 suite.Equal(404, resp.StatusCode) 364 body, err := ioutil.ReadAll(resp.Body) 365 resp.Body.Close() 366 suite.Nil(err) 367 suite.Equal("<html>\n <head></head>\n <body>\n <p>Error 404: Not Found.</p>\n </body>\n</html>", string(body)) 368 369 // With struct data 370 recorder = httptest.NewRecorder() 371 response = CreateTestResponse(recorder) 372 373 structData := struct { 374 Status int 375 Message string 376 }{ 377 Status: http.StatusNotFound, 378 Message: "Not Found.", 379 } 380 suite.Nil(response.RenderHTML(http.StatusNotFound, "error.html", structData)) 381 resp = recorder.Result() 382 suite.Equal(404, resp.StatusCode) 383 body, err = ioutil.ReadAll(resp.Body) 384 resp.Body.Close() 385 suite.Nil(err) 386 suite.Equal("<html>\n <head></head>\n <body>\n <p>Error 404: Not Found.</p>\n </body>\n</html>", string(body)) 387 388 // Non-existing template and exec error 389 recorder = httptest.NewRecorder() 390 response = CreateTestResponse(recorder) 391 392 suite.NotNil(response.RenderHTML(http.StatusNotFound, "non-existing-template", nil)) 393 394 suite.NotNil(response.RenderHTML(http.StatusNotFound, "invalid.txt", nil)) 395 resp = recorder.Result() 396 suite.Equal(0, response.status) 397 suite.Equal(200, resp.StatusCode) // Status not written in case of error 398 resp.Body.Close() 399 } 400 401 func (suite *ResponseTestSuite) TestHandleDatabaseError() { 402 type TestRecord struct { 403 gorm.Model 404 } 405 config.Set("database.connection", "mysql") 406 defer config.Set("database.connection", "none") 407 db := database.GetConnection() 408 409 response := newResponse(httptest.NewRecorder(), nil) 410 suite.False(response.HandleDatabaseError(db.Find(&TestRecord{}))) 411 412 suite.Equal(http.StatusInternalServerError, response.status) 413 414 db.AutoMigrate(&TestRecord{}) 415 defer db.DropTable(&TestRecord{}) 416 response = newResponse(httptest.NewRecorder(), nil) 417 suite.False(response.HandleDatabaseError(db.Where("id = ?", -1).Find(&TestRecord{}))) 418 419 suite.Equal(http.StatusNotFound, response.status) 420 421 response = newResponse(httptest.NewRecorder(), nil) 422 suite.True(response.HandleDatabaseError(db.Exec("SHOW TABLES;"))) 423 424 suite.Equal(0, response.status) 425 426 response = newResponse(httptest.NewRecorder(), nil) 427 results := []TestRecord{} 428 suite.True(response.HandleDatabaseError(db.Find(&results))) // Get all but empty result should not be an error 429 suite.Equal(0, response.status) 430 } 431 432 // ------------------------ 433 434 type testWriter struct { 435 result *string 436 id string 437 io.Writer 438 closed bool 439 } 440 441 func (w *testWriter) Write(b []byte) (int, error) { 442 *w.result += w.id + string(b) 443 return w.Writer.Write(b) 444 } 445 446 func (w *testWriter) Close() error { 447 w.closed = true 448 return fmt.Errorf("Test close error") 449 } 450 451 func (suite *ResponseTestSuite) TestChainedWriter() { 452 writer := httptest.NewRecorder() 453 response := newResponse(writer, nil) 454 result := "" 455 testWr := &testWriter{&result, "0", response.Writer(), false} 456 response.SetWriter(testWr) 457 458 response.String(http.StatusOK, "hello world") 459 460 suite.Equal("0hello world", result) 461 suite.Equal(200, response.status) 462 suite.True(response.wroteHeader) 463 suite.False(response.empty) 464 465 suite.Equal("Test close error", response.close().Error()) 466 suite.True(testWr.closed) 467 468 resp := writer.Result() 469 body, _ := ioutil.ReadAll(resp.Body) 470 resp.Body.Close() 471 suite.Equal("hello world", string(body)) 472 473 // Test double chained writer 474 writer = httptest.NewRecorder() 475 response = newResponse(writer, nil) 476 result = "" 477 testWr = &testWriter{&result, "0", response.Writer(), false} 478 testWr2 := &testWriter{&result, "1", testWr, false} 479 response.SetWriter(testWr2) 480 481 response.String(http.StatusOK, "hello world") 482 suite.Equal("1hello world0hello world", result) 483 suite.Equal(200, response.status) 484 suite.True(response.wroteHeader) 485 suite.False(response.empty) 486 resp = writer.Result() 487 body, _ = ioutil.ReadAll(resp.Body) 488 resp.Body.Close() 489 suite.Equal("hello world", string(body)) 490 } 491 492 func (suite *ResponseTestSuite) TearDownAllSuite() { 493 os.Setenv("GOYAVE_ENV", suite.previousEnv) 494 } 495 496 func TestResponseTestSuite(t *testing.T) { 497 suite.Run(t, new(ResponseTestSuite)) 498 }