goyave.dev/goyave/v4@v4.4.11/response_test.go (about)

     1  package goyave
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  
    16  	"gorm.io/gorm"
    17  	"goyave.dev/goyave/v4/config"
    18  	"goyave.dev/goyave/v4/database"
    19  )
    20  
    21  type ResponseTestSuite struct {
    22  	TestSuite
    23  }
    24  
    25  func (suite *ResponseTestSuite) getFileSize(path string) string {
    26  	file, err := os.Open(path)
    27  	if err != nil {
    28  		suite.FailNow(err.Error())
    29  	}
    30  	stats, err := file.Stat()
    31  	if err != nil {
    32  		suite.FailNow(err.Error())
    33  	}
    34  	return strconv.FormatInt(stats.Size(), 10)
    35  }
    36  
    37  func (suite *ResponseTestSuite) TestResponseStatus() {
    38  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
    39  	response := newResponse(httptest.NewRecorder(), rawRequest)
    40  	response.Status(403)
    41  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
    42  
    43  	suite.Equal(200, resp.StatusCode) // Not written yet
    44  	suite.True(response.empty)
    45  	suite.Equal(403, response.GetStatus())
    46  	resp.Body.Close()
    47  
    48  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
    49  	response = newResponse(httptest.NewRecorder(), rawRequest)
    50  	response.String(403, "test")
    51  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
    52  
    53  	suite.Equal(403, resp.StatusCode)
    54  	suite.False(response.empty)
    55  	suite.Equal(403, response.GetStatus())
    56  	resp.Body.Close()
    57  
    58  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
    59  	response = newResponse(httptest.NewRecorder(), rawRequest)
    60  	response.Status(403)
    61  	response.Status(200) // Should have no effect
    62  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
    63  
    64  	suite.Equal(200, resp.StatusCode) // Not written yet
    65  	suite.True(response.empty)
    66  	suite.Equal(403, response.GetStatus())
    67  	resp.Body.Close()
    68  }
    69  
    70  func (suite *ResponseTestSuite) TestResponseHeader() {
    71  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
    72  	response := newResponse(httptest.NewRecorder(), rawRequest)
    73  	response.Header().Set("Content-Type", "application/json")
    74  	response.Status(200)
    75  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
    76  
    77  	suite.Equal(200, resp.StatusCode)
    78  	suite.Equal("application/json", resp.Header.Get("Content-Type"))
    79  	suite.True(response.empty)
    80  	suite.Equal(200, response.status)
    81  	resp.Body.Close()
    82  }
    83  
    84  func (suite *ResponseTestSuite) TestResponseError() {
    85  	config.Set("app.debug", true)
    86  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
    87  	response := newResponse(httptest.NewRecorder(), rawRequest)
    88  	response.Error(fmt.Errorf("random error"))
    89  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
    90  
    91  	suite.Equal(500, resp.StatusCode)
    92  
    93  	body, err := io.ReadAll(resp.Body)
    94  	resp.Body.Close()
    95  	suite.Nil(err)
    96  	suite.Equal("{\"error\":\"random error\"}\n", string(body))
    97  	suite.False(response.empty)
    98  	suite.Equal(500, response.status)
    99  	suite.NotNil(response.err)
   100  
   101  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   102  	response = newResponse(httptest.NewRecorder(), rawRequest)
   103  	response.Error("random error")
   104  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
   105  
   106  	suite.Equal(500, resp.StatusCode)
   107  	suite.NotNil(response.err)
   108  
   109  	body, err = io.ReadAll(resp.Body)
   110  	resp.Body.Close()
   111  	suite.Nil(err)
   112  	suite.Equal("{\"error\":\"random error\"}\n", string(body))
   113  	suite.False(response.empty)
   114  	suite.Equal(500, response.status)
   115  	suite.NotNil(response.err)
   116  
   117  	config.Set("app.debug", false)
   118  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   119  	response = newResponse(httptest.NewRecorder(), rawRequest)
   120  	response.Error("random error")
   121  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
   122  
   123  	suite.Equal(500, response.GetStatus())
   124  
   125  	body, err = io.ReadAll(resp.Body)
   126  	resp.Body.Close()
   127  	suite.Nil(err)
   128  	suite.Empty(string(body))
   129  	suite.True(response.empty)
   130  	suite.Equal("random error", response.GetError())
   131  	suite.Equal(500, response.status)
   132  	config.Set("app.debug", true)
   133  
   134  	// Take user-defined status code in debug mode
   135  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   136  	response = newResponse(httptest.NewRecorder(), rawRequest)
   137  	response.Status(503)
   138  	response.Error("random error")
   139  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
   140  
   141  	suite.Equal(503, response.GetStatus())
   142  
   143  	body, err = io.ReadAll(resp.Body)
   144  	resp.Body.Close()
   145  	suite.Nil(err)
   146  	suite.Equal("{\"error\":\"random error\"}\n", string(body))
   147  	suite.False(response.empty)
   148  	suite.Equal("random error", response.GetError())
   149  	suite.Equal(503, response.status)
   150  	suite.NotNil(response.err)
   151  }
   152  
   153  func (suite *ResponseTestSuite) TestIsEmpty() {
   154  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   155  	response := newResponse(httptest.NewRecorder(), rawRequest)
   156  
   157  	suite.True(response.IsEmpty())
   158  
   159  	response.String(http.StatusOK, "test")
   160  	suite.False(response.IsEmpty())
   161  }
   162  
   163  func (suite *ResponseTestSuite) TestIsHeaderWritten() {
   164  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   165  	response := newResponse(httptest.NewRecorder(), rawRequest)
   166  
   167  	suite.False(response.IsHeaderWritten())
   168  
   169  	response.Status(http.StatusOK)
   170  	suite.False(response.IsHeaderWritten())
   171  
   172  	response.String(http.StatusOK, "test")
   173  	suite.True(response.IsHeaderWritten())
   174  }
   175  
   176  func (suite *ResponseTestSuite) TestGetStacktrace() {
   177  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   178  	response := newResponse(httptest.NewRecorder(), rawRequest)
   179  	suite.Empty(response.GetStacktrace())
   180  
   181  	response.stacktrace = "fake stacktrace"
   182  	suite.Equal("fake stacktrace", response.GetStacktrace())
   183  }
   184  
   185  func (suite *ResponseTestSuite) TestResponseFile() {
   186  	size := suite.getFileSize("config/config.test.json")
   187  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   188  	response := newResponse(httptest.NewRecorder(), rawRequest)
   189  
   190  	response.File("config/config.test.json")
   191  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
   192  
   193  	suite.Equal(200, resp.StatusCode)
   194  	suite.Equal("inline", resp.Header.Get("Content-Disposition"))
   195  	suite.Equal("application/json", resp.Header.Get("Content-Type"))
   196  	suite.Equal(size, resp.Header.Get("Content-Length"))
   197  	suite.False(response.empty)
   198  	suite.Equal(200, response.status)
   199  	resp.Body.Close()
   200  
   201  	// Test no Content-Type override
   202  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   203  	response = newResponse(httptest.NewRecorder(), rawRequest)
   204  	response.Header().Set("Content-Type", "text/plain")
   205  	response.File("config/config.test.json")
   206  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
   207  	suite.Equal("text/plain", resp.Header.Get("Content-Type"))
   208  	resp.Body.Close()
   209  
   210  	// File doesn't exist
   211  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   212  	response = newResponse(httptest.NewRecorder(), rawRequest)
   213  	err := response.File("config/doesntexist")
   214  	suite.Equal("open config/doesntexist: no such file or directory", err.Error())
   215  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
   216  	suite.Equal(404, response.status)
   217  	suite.True(response.empty)
   218  	suite.False(response.wroteHeader)
   219  	suite.Empty(resp.Header.Get("Content-Type"))
   220  	suite.Empty(resp.Header.Get("Content-Disposition"))
   221  	resp.Body.Close()
   222  }
   223  
   224  func (suite *ResponseTestSuite) TestResponseJSON() {
   225  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   226  	response := newResponse(httptest.NewRecorder(), rawRequest)
   227  
   228  	response.JSON(http.StatusOK, map[string]interface{}{
   229  		"status": "ok",
   230  		"code":   200,
   231  	})
   232  
   233  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
   234  	suite.Equal(200, resp.StatusCode)
   235  	suite.Equal("application/json; charset=utf-8", resp.Header.Get("Content-Type"))
   236  	suite.False(response.empty)
   237  
   238  	body, err := io.ReadAll(resp.Body)
   239  	resp.Body.Close()
   240  	if err != nil {
   241  		panic(err)
   242  	}
   243  	suite.Equal("{\"code\":200,\"status\":\"ok\"}\n", string(body))
   244  }
   245  
   246  func (suite *ResponseTestSuite) TestResponseDownload() {
   247  	size := suite.getFileSize("config/config.test.json")
   248  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   249  	response := newResponse(httptest.NewRecorder(), rawRequest)
   250  
   251  	response.Download("config/config.test.json", "config.json")
   252  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
   253  
   254  	suite.Equal(200, resp.StatusCode)
   255  	suite.Equal("attachment; filename=\"config.json\"", resp.Header.Get("Content-Disposition"))
   256  	suite.Equal("application/json", resp.Header.Get("Content-Type"))
   257  	suite.Equal(size, resp.Header.Get("Content-Length"))
   258  	suite.False(response.empty)
   259  	suite.Equal(200, response.status)
   260  	resp.Body.Close()
   261  
   262  	rawRequest = httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   263  	response = newResponse(httptest.NewRecorder(), rawRequest)
   264  
   265  	err := response.Download("config/doesntexist", "config.json")
   266  	suite.Equal("open config/doesntexist: no such file or directory", err.Error())
   267  	resp = response.responseWriter.(*httptest.ResponseRecorder).Result()
   268  	suite.Equal(404, response.status)
   269  	suite.True(response.empty)
   270  	suite.False(response.wroteHeader)
   271  	suite.Empty(resp.Header.Get("Content-Type"))
   272  	suite.Empty(resp.Header.Get("Content-Disposition"))
   273  	resp.Body.Close()
   274  }
   275  
   276  func (suite *ResponseTestSuite) TestResponseRedirect() {
   277  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   278  	response := newResponse(httptest.NewRecorder(), rawRequest)
   279  
   280  	response.Redirect("https://www.google.com")
   281  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
   282  
   283  	suite.Equal(308, resp.StatusCode)
   284  	body, err := io.ReadAll(resp.Body)
   285  	resp.Body.Close()
   286  	suite.Nil(err)
   287  	suite.Equal("<a href=\"https://www.google.com\">Permanent Redirect</a>.\n\n", string(body))
   288  	suite.False(response.empty)
   289  	suite.Equal(308, response.status)
   290  }
   291  
   292  func (suite *ResponseTestSuite) TestResponseTemporaryRedirect() {
   293  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   294  	response := newResponse(httptest.NewRecorder(), rawRequest)
   295  
   296  	response.TemporaryRedirect("https://www.google.com")
   297  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
   298  
   299  	suite.Equal(307, resp.StatusCode)
   300  	body, err := io.ReadAll(resp.Body)
   301  	resp.Body.Close()
   302  	suite.Nil(err)
   303  	suite.Equal("<a href=\"https://www.google.com\">Temporary Redirect</a>.\n\n", string(body))
   304  	suite.False(response.empty)
   305  	suite.Equal(307, response.status)
   306  }
   307  
   308  func (suite *ResponseTestSuite) TestResponseCookie() {
   309  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   310  	response := newResponse(httptest.NewRecorder(), rawRequest)
   311  	response.Cookie(&http.Cookie{
   312  		Name:  "cookie-name",
   313  		Value: "test",
   314  	})
   315  
   316  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
   317  	cookies := resp.Cookies()
   318  	suite.Equal(1, len(cookies))
   319  	suite.Equal("cookie-name", cookies[0].Name)
   320  	suite.Equal("test", cookies[0].Value)
   321  	resp.Body.Close()
   322  }
   323  
   324  func (suite *ResponseTestSuite) TestResponseWrite() {
   325  	rawRequest := httptest.NewRequest("GET", "/test-route", strings.NewReader("body"))
   326  	response := newResponse(httptest.NewRecorder(), rawRequest)
   327  	response.Write([]byte("byte array"))
   328  	resp := response.responseWriter.(*httptest.ResponseRecorder).Result()
   329  	body, err := io.ReadAll(resp.Body)
   330  	resp.Body.Close()
   331  	suite.Nil(err)
   332  	suite.Equal("byte array", string(body))
   333  	suite.False(response.empty)
   334  }
   335  
   336  func (suite *ResponseTestSuite) TestCreateTestResponse() {
   337  	recorder := httptest.NewRecorder()
   338  	response := suite.CreateTestResponse(recorder)
   339  	suite.NotNil(response)
   340  	if response != nil {
   341  		suite.Equal(recorder, response.responseWriter)
   342  	}
   343  }
   344  
   345  func (suite *ResponseTestSuite) TestRender() {
   346  	// With map data
   347  	recorder := httptest.NewRecorder()
   348  	response := suite.CreateTestResponse(recorder)
   349  
   350  	mapData := map[string]interface{}{
   351  		"Status":  http.StatusNotFound,
   352  		"Message": "Not Found.",
   353  	}
   354  	suite.Nil(response.Render(http.StatusNotFound, "error.txt", mapData))
   355  	resp := recorder.Result()
   356  	suite.Equal(404, resp.StatusCode)
   357  	body, err := io.ReadAll(resp.Body)
   358  	resp.Body.Close()
   359  	suite.Nil(err)
   360  	suite.Equal("Error 404: Not Found.", string(body))
   361  
   362  	// With struct data
   363  	recorder = httptest.NewRecorder()
   364  	response = suite.CreateTestResponse(recorder)
   365  
   366  	structData := struct {
   367  		Message string
   368  		Status  int
   369  	}{
   370  		Status:  http.StatusNotFound,
   371  		Message: "Not Found.",
   372  	}
   373  	suite.Nil(response.Render(http.StatusNotFound, "error.txt", structData))
   374  	resp = recorder.Result()
   375  	suite.Equal(404, resp.StatusCode)
   376  	body, err = io.ReadAll(resp.Body)
   377  	resp.Body.Close()
   378  	suite.Nil(err)
   379  	suite.Equal("Error 404: Not Found.", string(body))
   380  	resp.Body.Close()
   381  
   382  	// Non-existing template and exec error
   383  	recorder = httptest.NewRecorder()
   384  	response = suite.CreateTestResponse(recorder)
   385  
   386  	suite.NotNil(response.Render(http.StatusNotFound, "non-existing-template", nil))
   387  
   388  	suite.NotNil(response.Render(http.StatusNotFound, "invalid.txt", nil))
   389  	resp = recorder.Result()
   390  	suite.Equal(0, response.status)
   391  	suite.Equal(200, resp.StatusCode) // Status not written in case of error
   392  	resp.Body.Close()
   393  }
   394  
   395  func (suite *ResponseTestSuite) TestRenderHTML() {
   396  	// With map data
   397  	recorder := httptest.NewRecorder()
   398  	response := suite.CreateTestResponse(recorder)
   399  
   400  	mapData := map[string]interface{}{
   401  		"Status":  http.StatusNotFound,
   402  		"Message": "Not Found.",
   403  	}
   404  	suite.Nil(response.RenderHTML(http.StatusNotFound, "error.html", mapData))
   405  	resp := recorder.Result()
   406  	suite.Equal(404, resp.StatusCode)
   407  	body, err := io.ReadAll(resp.Body)
   408  	resp.Body.Close()
   409  	suite.Nil(err)
   410  	suite.Equal("<html>\n    <head></head>\n    <body>\n        <p>Error 404: Not Found.</p>\n    </body>\n</html>", string(body))
   411  
   412  	// With struct data
   413  	recorder = httptest.NewRecorder()
   414  	response = suite.CreateTestResponse(recorder)
   415  
   416  	structData := struct {
   417  		Message string
   418  		Status  int
   419  	}{
   420  		Status:  http.StatusNotFound,
   421  		Message: "Not Found.",
   422  	}
   423  	suite.Nil(response.RenderHTML(http.StatusNotFound, "error.html", structData))
   424  	resp = recorder.Result()
   425  	suite.Equal(404, resp.StatusCode)
   426  	body, err = io.ReadAll(resp.Body)
   427  	resp.Body.Close()
   428  	suite.Nil(err)
   429  	suite.Equal("<html>\n    <head></head>\n    <body>\n        <p>Error 404: Not Found.</p>\n    </body>\n</html>", string(body))
   430  
   431  	// Non-existing template and exec error
   432  	recorder = httptest.NewRecorder()
   433  	response = suite.CreateTestResponse(recorder)
   434  
   435  	suite.NotNil(response.RenderHTML(http.StatusNotFound, "non-existing-template", nil))
   436  
   437  	suite.NotNil(response.RenderHTML(http.StatusNotFound, "invalid.txt", nil))
   438  	resp = recorder.Result()
   439  	suite.Equal(0, response.status)
   440  	suite.Equal(200, resp.StatusCode) // Status not written in case of error
   441  	resp.Body.Close()
   442  }
   443  
   444  func (suite *ResponseTestSuite) TestHandleDatabaseError() {
   445  	type TestRecord struct {
   446  		gorm.Model
   447  	}
   448  	config.Set("database.connection", "mysql")
   449  	defer config.Set("database.connection", "none")
   450  	db := database.GetConnection()
   451  
   452  	response := newResponse(httptest.NewRecorder(), nil)
   453  	suite.False(response.HandleDatabaseError(db.Find(&TestRecord{})))
   454  
   455  	suite.Equal(http.StatusInternalServerError, response.status)
   456  
   457  	db.AutoMigrate(&TestRecord{})
   458  	defer db.Migrator().DropTable(&TestRecord{})
   459  	response = newResponse(httptest.NewRecorder(), nil)
   460  
   461  	suite.False(response.HandleDatabaseError(db.First(&TestRecord{}, -1)))
   462  
   463  	suite.Equal(http.StatusNotFound, response.status)
   464  
   465  	response = newResponse(httptest.NewRecorder(), nil)
   466  	suite.True(response.HandleDatabaseError(db.Exec("SHOW TABLES;")))
   467  
   468  	suite.Equal(0, response.status)
   469  
   470  	response = newResponse(httptest.NewRecorder(), nil)
   471  	results := []TestRecord{}
   472  	suite.True(response.HandleDatabaseError(db.Find(&results))) // Get all but empty result should not be an error
   473  	suite.Equal(0, response.status)
   474  }
   475  
   476  func (suite *ResponseTestSuite) TestHead() {
   477  	suite.RunServer(func(router *Router) {
   478  		router.Route("GET|HEAD", "/test", func(response *Response, r *Request) {
   479  			response.String(http.StatusOK, "hello world")
   480  		})
   481  	}, func() {
   482  		resp, err := suite.Request("HEAD", "/test", nil, nil)
   483  		if err != nil {
   484  			suite.Fail(err.Error())
   485  		}
   486  		defer resp.Body.Close()
   487  
   488  		body := suite.GetBody(resp)
   489  		suite.Equal(http.StatusOK, resp.StatusCode)
   490  		suite.Equal("text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
   491  		suite.Empty(body)
   492  	})
   493  
   494  	suite.RunServer(func(router *Router) {
   495  		router.Route("GET", "/test", func(response *Response, r *Request) {
   496  			response.String(http.StatusOK, "hello world")
   497  		})
   498  	}, func() {
   499  		resp, err := suite.Request("HEAD", "/test", nil, nil)
   500  		if err != nil {
   501  			suite.Fail(err.Error())
   502  		}
   503  		defer resp.Body.Close()
   504  
   505  		body := suite.GetBody(resp)
   506  		suite.Equal(http.StatusOK, resp.StatusCode)
   507  		suite.Equal("text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
   508  		suite.Empty(body)
   509  	})
   510  
   511  	suite.RunServer(func(router *Router) {
   512  		router.Route("POST", "/test", func(response *Response, r *Request) {
   513  			response.String(http.StatusOK, "hello world")
   514  		})
   515  	}, func() {
   516  		resp, err := suite.Request("HEAD", "/test", nil, nil)
   517  		if err != nil {
   518  			suite.Fail(err.Error())
   519  		}
   520  		defer resp.Body.Close()
   521  
   522  		body := suite.GetBody(resp)
   523  		suite.Equal(http.StatusMethodNotAllowed, resp.StatusCode)
   524  		suite.Equal("application/json; charset=utf-8", resp.Header.Get("Content-Type"))
   525  		suite.Empty(body)
   526  	})
   527  
   528  	suite.RunServer(func(router *Router) {
   529  		router.Route("HEAD", "/test", func(response *Response, r *Request) {
   530  			response.Header().Set("Content-Type", "text/plain; charset=utf-8")
   531  			response.Status(http.StatusOK)
   532  		})
   533  		router.Route("GET", "/test", func(response *Response, r *Request) {
   534  			response.String(http.StatusOK, "hello world")
   535  		})
   536  	}, func() {
   537  		resp, err := suite.Request("HEAD", "/test", nil, nil)
   538  		if err != nil {
   539  			suite.Fail(err.Error())
   540  		}
   541  		defer resp.Body.Close()
   542  
   543  		body := suite.GetBody(resp)
   544  		suite.Equal(http.StatusOK, resp.StatusCode)
   545  		suite.Equal("text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
   546  		suite.Empty(body)
   547  	})
   548  }
   549  
   550  type hijackableRecorder struct {
   551  	*httptest.ResponseRecorder
   552  }
   553  
   554  func (r *hijackableRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   555  	conn := &net.TCPConn{}
   556  	return conn, bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), nil
   557  }
   558  
   559  func (suite *ResponseTestSuite) TestHijack() {
   560  	recorder := &hijackableRecorder{httptest.NewRecorder()}
   561  	req := httptest.NewRequest(http.MethodGet, "/hijack", nil)
   562  	resp := newResponse(recorder, req)
   563  
   564  	suite.False(resp.hijacked)
   565  	suite.False(resp.Hijacked())
   566  
   567  	c, b, err := resp.Hijack()
   568  	if err != nil {
   569  		suite.Fail(err.Error())
   570  	}
   571  	defer c.Close()
   572  
   573  	suite.Nil(err)
   574  	suite.NotNil(c)
   575  	suite.NotNil(b)
   576  	suite.True(resp.hijacked)
   577  	suite.True(resp.Hijacked())
   578  
   579  }
   580  
   581  func (suite *ResponseTestSuite) TestErrorOnHijacked() {
   582  	recorder := &hijackableRecorder{httptest.NewRecorder()}
   583  	req := httptest.NewRequest(http.MethodGet, "/hijack", nil)
   584  	resp := newResponse(recorder, req)
   585  
   586  	c, _, _ := resp.Hijack()
   587  	defer c.Close()
   588  
   589  	suite.Nil(resp.error("test error"))
   590  	res := recorder.Result()
   591  	defer res.Body.Close()
   592  	suite.Equal(http.StatusInternalServerError, resp.status)
   593  
   594  	body, err := io.ReadAll(res.Body)
   595  	suite.Nil(err)
   596  	suite.Empty(body)
   597  }
   598  
   599  func (suite *ResponseTestSuite) TestHijackNotHijackable() {
   600  	recorder := httptest.NewRecorder()
   601  
   602  	req := httptest.NewRequest(http.MethodGet, "/hijack", nil)
   603  	resp := newResponse(recorder, req)
   604  
   605  	c, b, err := resp.Hijack()
   606  	suite.Nil(c)
   607  	suite.Nil(b)
   608  	suite.NotNil(err)
   609  	suite.True(errors.Is(err, ErrNotHijackable))
   610  }
   611  
   612  // ------------------------
   613  
   614  type testWriter struct {
   615  	io.Writer
   616  	result *string
   617  	id     string
   618  	closed bool
   619  }
   620  
   621  func (w *testWriter) Write(b []byte) (int, error) {
   622  	*w.result += w.id + string(b)
   623  	return w.Writer.Write(b)
   624  }
   625  
   626  func (w *testWriter) Close() error {
   627  	w.closed = true
   628  	return fmt.Errorf("Test close error")
   629  }
   630  
   631  func (suite *ResponseTestSuite) TestChainedWriter() {
   632  	writer := httptest.NewRecorder()
   633  	response := newResponse(writer, nil)
   634  	result := ""
   635  	testWr := &testWriter{response.Writer(), &result, "0", false}
   636  	response.SetWriter(testWr)
   637  
   638  	response.String(http.StatusOK, "hello world")
   639  
   640  	suite.Equal("0hello world", result)
   641  	suite.Equal(200, response.status)
   642  	suite.True(response.wroteHeader)
   643  	suite.False(response.empty)
   644  
   645  	suite.Equal("Test close error", response.close().Error())
   646  	suite.True(testWr.closed)
   647  
   648  	resp := writer.Result()
   649  	body, _ := io.ReadAll(resp.Body)
   650  	resp.Body.Close()
   651  	suite.Equal("hello world", string(body))
   652  
   653  	// Test double chained writer
   654  	writer = httptest.NewRecorder()
   655  	response = newResponse(writer, nil)
   656  	result = ""
   657  	testWr = &testWriter{response.Writer(), &result, "0", false}
   658  	testWr2 := &testWriter{testWr, &result, "1", false}
   659  	response.SetWriter(testWr2)
   660  
   661  	response.String(http.StatusOK, "hello world")
   662  	suite.Equal("1hello world0hello world", result)
   663  	suite.Equal(200, response.status)
   664  	suite.True(response.wroteHeader)
   665  	suite.False(response.empty)
   666  	resp = writer.Result()
   667  	body, _ = io.ReadAll(resp.Body)
   668  	resp.Body.Close()
   669  	suite.Equal("hello world", string(body))
   670  }
   671  
   672  func TestResponseTestSuite(t *testing.T) {
   673  	RunTest(t, new(ResponseTestSuite))
   674  }