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  }