X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ffoundation%2Fapi%2Frevel%2Ftesting%2Ftestsuite.go;fp=src%2Ffoundation%2Fapi%2Frevel%2Ftesting%2Ftestsuite.go;h=ff02472e0ea5051d881f8bcf145c6565ffac9aa2;hb=1d1ee6961c93781e1187d8c7faa868da6b2f01f4;hp=0000000000000000000000000000000000000000;hpb=56dd5e0f2164b37b40ac1daa188ccc618b4cbd19;p=iec.git diff --git a/src/foundation/api/revel/testing/testsuite.go b/src/foundation/api/revel/testing/testsuite.go new file mode 100644 index 0000000..ff02472 --- /dev/null +++ b/src/foundation/api/revel/testing/testsuite.go @@ -0,0 +1,411 @@ +// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. +// Revel Framework source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package testing + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime" + "mime/multipart" + "net/http" + "net/http/cookiejar" + "net/textproto" + "net/url" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/revel/revel" + + "github.com/revel/revel/session" + "golang.org/x/net/websocket" + "net/http/httptest" +) + +type TestSuite struct { + Client *http.Client + Response *http.Response + ResponseBody []byte + Session session.Session + SessionEngine revel.SessionEngine +} + +type TestRequest struct { + *http.Request + testSuite *TestSuite +} + +// This is populated by the generated code in the run/run/go file +var TestSuites []interface{} // Array of structs that embed TestSuite + +// NewTestSuite returns an initialized TestSuite ready for use. It is invoked +// by the test harness to initialize the embedded field in application tests. +func NewTestSuite() TestSuite { + return NewTestSuiteEngine(revel.NewSessionCookieEngine()) +} + +// Define a new test suite with a custom session engine +func NewTestSuiteEngine(engine revel.SessionEngine) TestSuite { + jar, _ := cookiejar.New(nil) + ts := TestSuite{ + Client: &http.Client{Jar: jar}, + Session: session.NewSession(), + SessionEngine: engine, + } + + return ts +} + +// NewTestRequest returns an initialized *TestRequest. It is used for extending +// testsuite package making it possibe to define own methods. Example: +// type MyTestSuite struct { +// testing.TestSuite +// } +// +// func (t *MyTestSuite) PutFormCustom(...) { +// req := http.NewRequest(...) +// ... +// return t.NewTestRequest(req) +// } +func (t *TestSuite) NewTestRequest(req *http.Request) *TestRequest { + request := &TestRequest{ + Request: req, + testSuite: t, + } + return request +} + +// Host returns the address and port of the server, e.g. "127.0.0.1:8557" +func (t *TestSuite) Host() string { + if revel.ServerEngineInit.Address[0] == ':' { + return "127.0.0.1" + revel.ServerEngineInit.Address + } + return revel.ServerEngineInit.Address +} + +// BaseUrl returns the base http/https URL of the server, e.g. "http://127.0.0.1:8557". +// The scheme is set to https if http.ssl is set to true in the configuration file. +func (t *TestSuite) BaseUrl() string { + if revel.HTTPSsl { + return "https://" + t.Host() + } + return "http://" + t.Host() +} + +// WebSocketUrl returns the base websocket URL of the server, e.g. "ws://127.0.0.1:8557" +func (t *TestSuite) WebSocketUrl() string { + return "ws://" + t.Host() +} + +// Get issues a GET request to the given path and stores the result in Response +// and ResponseBody. +func (t *TestSuite) Get(path string) { + t.GetCustom(t.BaseUrl() + path).Send() +} + +// GetCustom returns a GET request to the given URI in a form of its wrapper. +func (t *TestSuite) GetCustom(uri string) *TestRequest { + req, err := http.NewRequest("GET", uri, nil) + if err != nil { + panic(err) + } + return t.NewTestRequest(req) +} + +// Delete issues a DELETE request to the given path and stores the result in +// Response and ResponseBody. +func (t *TestSuite) Delete(path string) { + t.DeleteCustom(t.BaseUrl() + path).Send() +} + +// DeleteCustom returns a DELETE request to the given URI in a form of its +// wrapper. +func (t *TestSuite) DeleteCustom(uri string) *TestRequest { + req, err := http.NewRequest("DELETE", uri, nil) + if err != nil { + panic(err) + } + return t.NewTestRequest(req) +} + +// Put issues a PUT request to the given path, sending the given Content-Type +// and data, storing the result in Response and ResponseBody. "data" may be nil. +func (t *TestSuite) Put(path string, contentType string, reader io.Reader) { + t.PutCustom(t.BaseUrl()+path, contentType, reader).Send() +} + +// PutCustom returns a PUT request to the given URI with specified Content-Type +// and data in a form of wrapper. "data" may be nil. +func (t *TestSuite) PutCustom(uri string, contentType string, reader io.Reader) *TestRequest { + req, err := http.NewRequest("PUT", uri, reader) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", contentType) + return t.NewTestRequest(req) +} + +// PutForm issues a PUT request to the given path as a form put of the given key +// and values, and stores the result in Response and ResponseBody. +func (t *TestSuite) PutForm(path string, data url.Values) { + t.PutFormCustom(t.BaseUrl()+path, data).Send() +} + +// PutFormCustom returns a PUT request to the given URI as a form put of the +// given key and values. The request is in a form of TestRequest wrapper. +func (t *TestSuite) PutFormCustom(uri string, data url.Values) *TestRequest { + return t.PutCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} + +// Patch issues a PATCH request to the given path, sending the given +// Content-Type and data, and stores the result in Response and ResponseBody. +// "data" may be nil. +func (t *TestSuite) Patch(path string, contentType string, reader io.Reader) { + t.PatchCustom(t.BaseUrl()+path, contentType, reader).Send() +} + +// PatchCustom returns a PATCH request to the given URI with specified +// Content-Type and data in a form of wrapper. "data" may be nil. +func (t *TestSuite) PatchCustom(uri string, contentType string, reader io.Reader) *TestRequest { + req, err := http.NewRequest("PATCH", uri, reader) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", contentType) + return t.NewTestRequest(req) +} + +// Post issues a POST request to the given path, sending the given Content-Type +// and data, storing the result in Response and ResponseBody. "data" may be nil. +func (t *TestSuite) Post(path string, contentType string, reader io.Reader) { + t.PostCustom(t.BaseUrl()+path, contentType, reader).Send() +} + +// PostCustom returns a POST request to the given URI with specified +// Content-Type and data in a form of wrapper. "data" may be nil. +func (t *TestSuite) PostCustom(uri string, contentType string, reader io.Reader) *TestRequest { + req, err := http.NewRequest("POST", uri, reader) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", contentType) + return t.NewTestRequest(req) +} + +// PostForm issues a POST request to the given path as a form post of the given +// key and values, and stores the result in Response and ResponseBody. +func (t *TestSuite) PostForm(path string, data url.Values) { + t.PostFormCustom(t.BaseUrl()+path, data).Send() +} + +// PostFormCustom returns a POST request to the given URI as a form post of the +// given key and values. The request is in a form of TestRequest wrapper. +func (t *TestSuite) PostFormCustom(uri string, data url.Values) *TestRequest { + return t.PostCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} + +// PostFile issues a multipart request to the given path sending given params +// and files, and stores the result in Response and ResponseBody. +func (t *TestSuite) PostFile(path string, params url.Values, filePaths url.Values) { + t.PostFileCustom(t.BaseUrl()+path, params, filePaths).Send() +} + +// PostFileCustom returns a multipart request to the given URI in a form of its +// wrapper with the given params and files. +func (t *TestSuite) PostFileCustom(uri string, params url.Values, filePaths url.Values) *TestRequest { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + for key, values := range filePaths { + for _, value := range values { + createFormFile(writer, key, value) + } + } + + for key, values := range params { + for _, value := range values { + err := writer.WriteField(key, value) + t.AssertEqual(nil, err) + } + } + err := writer.Close() + t.AssertEqual(nil, err) + + return t.PostCustom(uri, writer.FormDataContentType(), body) +} + +// Send issues any request and reads the response. If successful, the caller may +// examine the Response and ResponseBody properties. Session data will be +// added. +func (r *TestRequest) Send() { + writer := httptest.NewRecorder() + context := revel.NewGoContext(nil) + context.Request.SetRequest(r.Request) + context.Response.SetResponse(writer) + controller := revel.NewController(context) + controller.Session = r.testSuite.Session + + r.testSuite.SessionEngine.Encode(controller) + response := http.Response{Header: writer.Header()} + cookies := response.Cookies() + for _, c := range cookies { + r.AddCookie(c) + } + r.MakeRequest() +} + +// MakeRequest issues any request and read the response. If successful, the +// caller may examine the Response and ResponseBody properties. You will need to +// manage session / cookie data manually +func (r *TestRequest) MakeRequest() { + var err error + if r.testSuite.Response, err = r.testSuite.Client.Do(r.Request); err != nil { + panic(err) + } + if r.testSuite.ResponseBody, err = ioutil.ReadAll(r.testSuite.Response.Body); err != nil { + panic(err) + } + + // Create the controller again to receive the response for processing. + context := revel.NewGoContext(nil) + // Set the request with the header from the response.. + newRequest := &http.Request{URL: r.URL, Header: r.testSuite.Response.Header} + for _, cookie := range r.testSuite.Client.Jar.Cookies(r.Request.URL) { + newRequest.AddCookie(cookie) + } + context.Request.SetRequest(newRequest) + context.Response.SetResponse(httptest.NewRecorder()) + controller := revel.NewController(context) + + // Decode the session data from the controller and assign it to the session + r.testSuite.SessionEngine.Decode(controller) + r.testSuite.Session = controller.Session +} + +// WebSocket creates a websocket connection to the given path and returns it +func (t *TestSuite) WebSocket(path string) *websocket.Conn { + origin := t.BaseUrl() + "/" + urlPath := t.WebSocketUrl() + path + ws, err := websocket.Dial(urlPath, "", origin) + if err != nil { + panic(err) + } + return ws +} + +func (t *TestSuite) AssertOk() { + t.AssertStatus(http.StatusOK) +} + +func (t *TestSuite) AssertNotFound() { + t.AssertStatus(http.StatusNotFound) +} + +func (t *TestSuite) AssertStatus(status int) { + if t.Response.StatusCode != status { + panic(fmt.Errorf("Status: (expected) %d != %d (actual)", status, t.Response.StatusCode)) + } +} + +func (t *TestSuite) AssertContentType(contentType string) { + t.AssertHeader("Content-Type", contentType) +} + +func (t *TestSuite) AssertHeader(name, value string) { + actual := t.Response.Header.Get(name) + if actual != value { + panic(fmt.Errorf("Header %s: (expected) %s != %s (actual)", name, value, actual)) + } +} + +func (t *TestSuite) AssertEqual(expected, actual interface{}) { + if !revel.Equal(expected, actual) { + panic(fmt.Errorf("(expected) %v != %v (actual)", expected, actual)) + } +} + +func (t *TestSuite) AssertNotEqual(expected, actual interface{}) { + if revel.Equal(expected, actual) { + panic(fmt.Errorf("(expected) %v == %v (actual)", expected, actual)) + } +} + +func (t *TestSuite) Assert(exp bool) { + t.Assertf(exp, "Assertion failed") +} + +func (t *TestSuite) Assertf(exp bool, formatStr string, args ...interface{}) { + if !exp { + panic(fmt.Errorf(formatStr, args...)) + } +} + +// AssertContains asserts that the response contains the given string. +func (t *TestSuite) AssertContains(s string) { + if !bytes.Contains(t.ResponseBody, []byte(s)) { + panic(fmt.Errorf("Assertion failed. Expected response to contain %s", s)) + } +} + +// AssertNotContains asserts that the response does not contain the given string. +func (t *TestSuite) AssertNotContains(s string) { + if bytes.Contains(t.ResponseBody, []byte(s)) { + panic(fmt.Errorf("Assertion failed. Expected response not to contain %s", s)) + } +} + +// AssertContainsRegex asserts that the response matches the given regular expression. +func (t *TestSuite) AssertContainsRegex(regex string) { + r := regexp.MustCompile(regex) + + if !r.Match(t.ResponseBody) { + panic(fmt.Errorf("Assertion failed. Expected response to match regexp %s", regex)) + } +} + +func createFormFile(writer *multipart.Writer, fieldname, filename string) { + // Try to open the file. + file, err := os.Open(filename) + if err != nil { + panic(err) + } + defer func() { + _ = file.Close() + }() + + // Create a new form-data header with the provided field name and file name. + // Determine Content-Type of the file by its extension. + h := textproto.MIMEHeader{} + h.Set("Content-Disposition", fmt.Sprintf( + `form-data; name="%s"; filename="%s"`, + escapeQuotes(fieldname), + escapeQuotes(filepath.Base(filename)), + )) + h.Set("Content-Type", "application/octet-stream") + if ct := mime.TypeByExtension(filepath.Ext(filename)); ct != "" { + h.Set("Content-Type", ct) + } + part, err := writer.CreatePart(h) + if err != nil { + panic(err) + } + + // Copy the content of the file we have opened not reading the whole + // file into memory. + _, err = io.Copy(part, file) + if err != nil { + panic(err) + } +} + +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +// This function was borrowed from mime/multipart package. +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +}