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  }