gitlab.com/ignitionrobotics/web/ign-go@v1.0.0-rc4/testhelpers/test_helpers.go (about) 1 package igntest 2 3 // Important note: functions in this module should NOT include 4 // references to parent package 'ign', to avoid circular dependencies. 5 // These functions should be independent. 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "github.com/stretchr/testify/assert" 12 "io" 13 "io/ioutil" 14 "log" 15 "mime/multipart" 16 "net/http" 17 "net/http/httptest" 18 "os" 19 "path/filepath" 20 "testing" 21 ) 22 23 var router http.Handler 24 25 // FileDesc describes a file to be created. It is used by 26 // func CreateTmpFolderWithContents and sendMultipartPOST. 27 // Fields: 28 // path: is the file path to be sent in the multipart form. 29 // contents: is the string contents to write in the file. Note: if contents 30 // value is ":dir" then a Directory will be created instead of a File. This is only 31 // valid when used with CreateTmpFolderWithContents func. 32 type FileDesc struct { 33 Path string 34 Contents string 35 } 36 37 // SetupTest - Setup helper function 38 func SetupTest(_router http.Handler) { 39 router = _router 40 } 41 42 // SendMultipartPOST executes a multipart POST request with the given form 43 // fields and multipart files, and returns the received http status code, 44 // the response body, and a success flag. 45 func SendMultipartPOST(testName string, t *testing.T, uri string, jwt *string, 46 params map[string]string, files []FileDesc) (respCode int, 47 bslice *[]byte, ok bool) { 48 49 return SendMultipartMethod(testName, t, "POST", uri, jwt, params, files) 50 } 51 52 // SendMultipartMethod executes a multipart POST/PUT/PATCH request with the given form 53 // fields and multipart files, and returns the received http status code, 54 // the response body, and a success flag. 55 func SendMultipartMethod(testName string, t *testing.T, method, uri string, jwt *string, 56 params map[string]string, files []FileDesc) (respCode int, 57 bslice *[]byte, ok bool) { 58 59 body := &bytes.Buffer{} 60 writer := multipart.NewWriter(body) 61 for _, fd := range files { 62 // Remove base path 63 part, err := writer.CreateFormFile("file", fd.Path) 64 assert.NoError(t, err, "Could not create FormFile. TestName:[%s]. fd.Path:[%s]", testName, fd.Path) 65 _, err = io.WriteString(part, fd.Contents) 66 } 67 68 for key, val := range params { 69 _ = writer.WriteField(key, val) 70 } 71 assert.NoError(t, writer.Close(), "Could not close multipart form writer. TestName:[%s]", testName) 72 73 req, err := http.NewRequest(method, uri, body) 74 assert.NoError(t, err, "Could not create POST request. TestName:[%s]", testName) 75 // Adds the "Content-Type: multipart/form-data" header. 76 req.Header.Add("Content-Type", writer.FormDataContentType()) 77 if jwt != nil { 78 // Add the authorization token 79 req.Header.Set("Authorization", "Bearer "+*jwt) 80 } 81 82 // Process the request 83 respRec := httptest.NewRecorder() 84 router.ServeHTTP(respRec, req) 85 // Process results 86 respCode = respRec.Code 87 88 var b []byte 89 var er error 90 b, er = ioutil.ReadAll(respRec.Body) 91 assert.NoError(t, er, "Failed to read the server response. TestName:[%s]", testName) 92 93 bslice = &b 94 ok = true 95 return 96 } 97 98 // CreateTmpFolderWithContents creates a tmp folder with the given files and 99 // returns the path to the created folder. See type fileDesc above. 100 func CreateTmpFolderWithContents(folderName string, files []FileDesc) (string, error) { 101 baseDir, err := ioutil.TempDir("", folderName) 102 if err != nil { 103 return "", err 104 } 105 106 for _, fd := range files { 107 fullpath := filepath.Join(baseDir, fd.Path) 108 dir := filepath.Dir(fullpath) 109 if dir != baseDir { 110 if err := os.MkdirAll(dir, os.ModePerm); err != nil { 111 return "", err 112 } 113 } 114 115 if fd.Contents == ":dir" { 116 // folder 117 if err := os.MkdirAll(fullpath, os.ModePerm); err != nil { 118 return "", err 119 } 120 } else { 121 // normal file with given contents 122 f, err := os.Create(fullpath) 123 defer f.Close() 124 if err != nil { 125 log.Println("Unable to create [" + fullpath + "]") 126 return "", err 127 } 128 if _, err := f.WriteString(fd.Contents); err != nil { 129 log.Println("Unable to write contents to [" + fullpath + "]") 130 return "", err 131 } 132 f.Sync() 133 } 134 } 135 return baseDir, nil 136 } 137 138 // AssertRoute is a helper function that checks for a valid route 139 // \param[in] method One of "GET", "PATCH", "PUT", "POST", "DELETE", "OPTIONS" 140 // \param[in] route The URL string 141 // \param[in] code The expected result HTTP code 142 // \param[in] t Testing pointer 143 // \return[out] *[]byte A pointer to a bytes slice containing the response body. 144 // \return[out] bool A flag indicating if the operation was ok. 145 func AssertRoute(method, route string, code int, t *testing.T) (*[]byte, bool) { 146 return AssertRouteWithBody(method, route, nil, code, t) 147 } 148 149 // AssertRouteWithBody is a helper function that checks for a valid route 150 // \return[out] *[]byte A pointer to a bytes slice containing the response body. 151 // \return[out] bool A flag indicating if the operation was ok. 152 func AssertRouteWithBody(method, route string, body *bytes.Buffer, code int, t *testing.T) (*[]byte, bool) { 153 jwt := os.Getenv("IGN_TEST_JWT") 154 return AssertRouteMultipleArgs(method, route, body, code, &jwt, 155 "application/json", t) 156 } 157 158 // AssertRouteMultipleArgs is a helper function that checks for a valid route. 159 // \param[in] method One of "GET", "PATCH", "PUT", "POST", "DELETE" 160 // \param[in] route The URL string 161 // \param[in] body The body to send in the request, or nil 162 // \param[in] code The expected response HTTP code 163 // \param[in] signedToken JWT token as base64 string, or nil. 164 // \param[in] contentType The expected response content type 165 // \param[in] t Test pointer 166 // \return[out] *[]byte A pointer to a bytes slice containing the response body. 167 // \return[out] bool A flag indicating if the operation was ok. 168 func AssertRouteMultipleArgs(method string, route string, body *bytes.Buffer, code int, signedToken *string, contentType string, t *testing.T) (*[]byte, bool) { 169 args := RequestArgs{ 170 Method: method, 171 Route: route, 172 Body: body, 173 SignedToken: signedToken, 174 } 175 ar := AssertRouteMultipleArgsStruct(args, code, contentType, t) 176 return ar.BodyAsBytes, ar.Ok 177 } 178 179 // RequestArgs - input arguments struct used by AssertRouteMultipleArgsStruct func. 180 type RequestArgs struct { 181 Method string 182 Route string 183 Body *bytes.Buffer 184 SignedToken *string 185 Headers map[string]string 186 } 187 188 // AssertResponse - response of AssertRouteMultipleArgsStruct func. 189 type AssertResponse struct { 190 Ok bool 191 RespRecorder *httptest.ResponseRecorder 192 BodyAsBytes *[]byte 193 } 194 195 // AssertRouteMultipleArgsStruct is a convenient helper function that accepts input 196 // arguments as a struct, allowing us to keep extending the number of args without changing 197 // the func signature and the corresponding invocation code everywhere. 198 // \param[in] args RequestArgs struct 199 // \param[in] expCode The expected response HTTP code 200 // \param[in] contentType The expected response content type 201 // \param[in] t Test pointer 202 // \return[out] *AssertResponse A pointer to a AssertResponse struct with the response. 203 func AssertRouteMultipleArgsStruct(args RequestArgs, expCode int, contentType string, t *testing.T) *AssertResponse { 204 var b []byte 205 var ar AssertResponse 206 207 var buff bytes.Buffer 208 if args.Body != nil { 209 buff = *args.Body 210 } 211 // Create a new http request 212 req, err := http.NewRequest(args.Method, args.Route, &buff) 213 assert.NoError(t, err, "Request failed!") 214 215 // Add the authorization token 216 if args.SignedToken != nil { 217 req.Header.Set("Authorization", "Bearer "+*args.SignedToken) 218 } 219 for key, val := range args.Headers { 220 req.Header.Set(key, val) 221 } 222 223 // Process the request 224 respRec := httptest.NewRecorder() 225 router.ServeHTTP(respRec, req) 226 ar.RespRecorder = respRec 227 228 // Read the result 229 var er error 230 b, er = ioutil.ReadAll(respRec.Body) 231 assert.NoError(t, er, "Failed to read the server response") 232 ar.BodyAsBytes = &b 233 234 // Make sure the error code is correct 235 assert.Equal(t, expCode, respRec.Code, "Server error: returned %d instead of %d. Route:%s", 236 respRec.Code, expCode, args.Route) 237 238 gotCT := respRec.Header().Get("Content-Type") 239 assert.Equal(t, contentType, gotCT, "Expected Content-Type[%s] != [%s]. Route:%s", 240 contentType, gotCT, args.Route) 241 ar.Ok = true 242 return &ar 243 } 244 245 // GetIndentedTraceToTest returns a formatted and indented string containing the 246 // file and line number of each stack frame leading from the current test to 247 // the assert call that failed. 248 func GetIndentedTraceToTest() string { 249 return fmt.Sprintf("\n\r\tTrace:\n\r%s", indentStrings("\t\t", assert.CallerInfo())) 250 } 251 252 func indentStrings(ind string, lines []string) string { 253 res := "" 254 for _, s := range lines { 255 res += ind + s + "\n\r" 256 } 257 return res 258 } 259 260 // AssertBackendErrorCode is a function that tries to unmarshal a backend's 261 // ErrMsg and compares to given error code 262 func AssertBackendErrorCode(testName string, bslice *[]byte, errCode int, t *testing.T) { 263 var errMsg interface{} 264 assert.NoError(t, json.Unmarshal(*bslice, &errMsg), 265 "Unable to unmarshal bytes slice. Testname:[%s]. Body:[%s]", testName, 266 string(*bslice)) 267 268 em := errMsg.(map[string]interface{}) 269 gotCode := em["errcode"].(float64) 270 assert.Equal(t, errCode, int(gotCode), 271 "errcode [%d] is different than expected code [%d]", int(gotCode), errCode) 272 assert.NotEmpty(t, em["errid"], "ErrMsg 'errid' is empty but it should not") 273 }