Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / testing / testsuite.go
1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4
5 package testing
6
7 import (
8         "bytes"
9         "fmt"
10         "io"
11         "io/ioutil"
12         "mime"
13         "mime/multipart"
14         "net/http"
15         "net/http/cookiejar"
16         "net/textproto"
17         "net/url"
18         "os"
19         "path/filepath"
20         "regexp"
21         "strings"
22
23         "github.com/revel/revel"
24
25         "github.com/revel/revel/session"
26         "golang.org/x/net/websocket"
27         "net/http/httptest"
28 )
29
30 type TestSuite struct {
31         Client        *http.Client
32         Response      *http.Response
33         ResponseBody  []byte
34         Session       session.Session
35         SessionEngine revel.SessionEngine
36 }
37
38 type TestRequest struct {
39         *http.Request
40         testSuite *TestSuite
41 }
42
43 // This is populated by the generated code in the run/run/go file
44 var TestSuites []interface{} // Array of structs that embed TestSuite
45
46 // NewTestSuite returns an initialized TestSuite ready for use. It is invoked
47 // by the test harness to initialize the embedded field in application tests.
48 func NewTestSuite() TestSuite {
49         return NewTestSuiteEngine(revel.NewSessionCookieEngine())
50 }
51
52 // Define a new test suite with a custom session engine
53 func NewTestSuiteEngine(engine revel.SessionEngine) TestSuite {
54         jar, _ := cookiejar.New(nil)
55         ts := TestSuite{
56                 Client:        &http.Client{Jar: jar},
57                 Session:       session.NewSession(),
58                 SessionEngine: engine,
59         }
60
61         return ts
62 }
63
64 // NewTestRequest returns an initialized *TestRequest. It is used for extending
65 // testsuite package making it possibe to define own methods. Example:
66 //      type MyTestSuite struct {
67 //              testing.TestSuite
68 //      }
69 //
70 //      func (t *MyTestSuite) PutFormCustom(...) {
71 //              req := http.NewRequest(...)
72 //              ...
73 //              return t.NewTestRequest(req)
74 //      }
75 func (t *TestSuite) NewTestRequest(req *http.Request) *TestRequest {
76         request := &TestRequest{
77                 Request:   req,
78                 testSuite: t,
79         }
80         return request
81 }
82
83 // Host returns the address and port of the server, e.g. "127.0.0.1:8557"
84 func (t *TestSuite) Host() string {
85         if revel.ServerEngineInit.Address[0] == ':' {
86                 return "127.0.0.1" + revel.ServerEngineInit.Address
87         }
88         return revel.ServerEngineInit.Address
89 }
90
91 // BaseUrl returns the base http/https URL of the server, e.g. "http://127.0.0.1:8557".
92 // The scheme is set to https if http.ssl is set to true in the configuration file.
93 func (t *TestSuite) BaseUrl() string {
94         if revel.HTTPSsl {
95                 return "https://" + t.Host()
96         }
97         return "http://" + t.Host()
98 }
99
100 // WebSocketUrl returns the base websocket URL of the server, e.g. "ws://127.0.0.1:8557"
101 func (t *TestSuite) WebSocketUrl() string {
102         return "ws://" + t.Host()
103 }
104
105 // Get issues a GET request to the given path and stores the result in Response
106 // and ResponseBody.
107 func (t *TestSuite) Get(path string) {
108         t.GetCustom(t.BaseUrl() + path).Send()
109 }
110
111 // GetCustom returns a GET request to the given URI in a form of its wrapper.
112 func (t *TestSuite) GetCustom(uri string) *TestRequest {
113         req, err := http.NewRequest("GET", uri, nil)
114         if err != nil {
115                 panic(err)
116         }
117         return t.NewTestRequest(req)
118 }
119
120 // Delete issues a DELETE request to the given path and stores the result in
121 // Response and ResponseBody.
122 func (t *TestSuite) Delete(path string) {
123         t.DeleteCustom(t.BaseUrl() + path).Send()
124 }
125
126 // DeleteCustom returns a DELETE request to the given URI in a form of its
127 // wrapper.
128 func (t *TestSuite) DeleteCustom(uri string) *TestRequest {
129         req, err := http.NewRequest("DELETE", uri, nil)
130         if err != nil {
131                 panic(err)
132         }
133         return t.NewTestRequest(req)
134 }
135
136 // Put issues a PUT request to the given path, sending the given Content-Type
137 // and data, storing the result in Response and ResponseBody. "data" may be nil.
138 func (t *TestSuite) Put(path string, contentType string, reader io.Reader) {
139         t.PutCustom(t.BaseUrl()+path, contentType, reader).Send()
140 }
141
142 // PutCustom returns a PUT request to the given URI with specified Content-Type
143 // and data in a form of wrapper. "data" may be nil.
144 func (t *TestSuite) PutCustom(uri string, contentType string, reader io.Reader) *TestRequest {
145         req, err := http.NewRequest("PUT", uri, reader)
146         if err != nil {
147                 panic(err)
148         }
149         req.Header.Set("Content-Type", contentType)
150         return t.NewTestRequest(req)
151 }
152
153 // PutForm issues a PUT request to the given path as a form put of the given key
154 // and values, and stores the result in Response and ResponseBody.
155 func (t *TestSuite) PutForm(path string, data url.Values) {
156         t.PutFormCustom(t.BaseUrl()+path, data).Send()
157 }
158
159 // PutFormCustom returns a PUT request to the given URI as a form put of the
160 // given key and values. The request is in a form of TestRequest wrapper.
161 func (t *TestSuite) PutFormCustom(uri string, data url.Values) *TestRequest {
162         return t.PutCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
163 }
164
165 // Patch issues a PATCH request to the given path, sending the given
166 // Content-Type and data, and stores the result in Response and ResponseBody.
167 // "data" may be nil.
168 func (t *TestSuite) Patch(path string, contentType string, reader io.Reader) {
169         t.PatchCustom(t.BaseUrl()+path, contentType, reader).Send()
170 }
171
172 // PatchCustom returns a PATCH request to the given URI with specified
173 // Content-Type and data in a form of wrapper. "data" may be nil.
174 func (t *TestSuite) PatchCustom(uri string, contentType string, reader io.Reader) *TestRequest {
175         req, err := http.NewRequest("PATCH", uri, reader)
176         if err != nil {
177                 panic(err)
178         }
179         req.Header.Set("Content-Type", contentType)
180         return t.NewTestRequest(req)
181 }
182
183 // Post issues a POST request to the given path, sending the given Content-Type
184 // and data, storing the result in Response and ResponseBody. "data" may be nil.
185 func (t *TestSuite) Post(path string, contentType string, reader io.Reader) {
186         t.PostCustom(t.BaseUrl()+path, contentType, reader).Send()
187 }
188
189 // PostCustom returns a POST request to the given URI with specified
190 // Content-Type and data in a form of wrapper. "data" may be nil.
191 func (t *TestSuite) PostCustom(uri string, contentType string, reader io.Reader) *TestRequest {
192         req, err := http.NewRequest("POST", uri, reader)
193         if err != nil {
194                 panic(err)
195         }
196         req.Header.Set("Content-Type", contentType)
197         return t.NewTestRequest(req)
198 }
199
200 // PostForm issues a POST request to the given path as a form post of the given
201 // key and values, and stores the result in Response and ResponseBody.
202 func (t *TestSuite) PostForm(path string, data url.Values) {
203         t.PostFormCustom(t.BaseUrl()+path, data).Send()
204 }
205
206 // PostFormCustom returns a POST request to the given URI as a form post of the
207 // given key and values. The request is in a form of TestRequest wrapper.
208 func (t *TestSuite) PostFormCustom(uri string, data url.Values) *TestRequest {
209         return t.PostCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
210 }
211
212 // PostFile issues a multipart request to the given path sending given params
213 // and files, and stores the result in Response and ResponseBody.
214 func (t *TestSuite) PostFile(path string, params url.Values, filePaths url.Values) {
215         t.PostFileCustom(t.BaseUrl()+path, params, filePaths).Send()
216 }
217
218 // PostFileCustom returns a multipart request to the given URI in a form of its
219 // wrapper with the given params and files.
220 func (t *TestSuite) PostFileCustom(uri string, params url.Values, filePaths url.Values) *TestRequest {
221         body := &bytes.Buffer{}
222         writer := multipart.NewWriter(body)
223
224         for key, values := range filePaths {
225                 for _, value := range values {
226                         createFormFile(writer, key, value)
227                 }
228         }
229
230         for key, values := range params {
231                 for _, value := range values {
232                         err := writer.WriteField(key, value)
233                         t.AssertEqual(nil, err)
234                 }
235         }
236         err := writer.Close()
237         t.AssertEqual(nil, err)
238
239         return t.PostCustom(uri, writer.FormDataContentType(), body)
240 }
241
242 // Send issues any request and reads the response. If successful, the caller may
243 // examine the Response and ResponseBody properties. Session data will be
244 // added.
245 func (r *TestRequest) Send() {
246         writer := httptest.NewRecorder()
247         context := revel.NewGoContext(nil)
248         context.Request.SetRequest(r.Request)
249         context.Response.SetResponse(writer)
250         controller := revel.NewController(context)
251         controller.Session = r.testSuite.Session
252
253         r.testSuite.SessionEngine.Encode(controller)
254         response := http.Response{Header: writer.Header()}
255         cookies := response.Cookies()
256         for _, c := range cookies {
257                 r.AddCookie(c)
258         }
259         r.MakeRequest()
260 }
261
262 // MakeRequest issues any request and read the response. If successful, the
263 // caller may examine the Response and ResponseBody properties. You will need to
264 // manage session / cookie data manually
265 func (r *TestRequest) MakeRequest() {
266         var err error
267         if r.testSuite.Response, err = r.testSuite.Client.Do(r.Request); err != nil {
268                 panic(err)
269         }
270         if r.testSuite.ResponseBody, err = ioutil.ReadAll(r.testSuite.Response.Body); err != nil {
271                 panic(err)
272         }
273
274         // Create the controller again to receive the response for processing.
275         context := revel.NewGoContext(nil)
276         // Set the request with the header from the response..
277         newRequest := &http.Request{URL: r.URL, Header: r.testSuite.Response.Header}
278         for _, cookie := range r.testSuite.Client.Jar.Cookies(r.Request.URL) {
279                 newRequest.AddCookie(cookie)
280         }
281         context.Request.SetRequest(newRequest)
282         context.Response.SetResponse(httptest.NewRecorder())
283         controller := revel.NewController(context)
284
285         // Decode the session data from the controller and assign it to the session
286         r.testSuite.SessionEngine.Decode(controller)
287         r.testSuite.Session = controller.Session
288 }
289
290 // WebSocket creates a websocket connection to the given path and returns it
291 func (t *TestSuite) WebSocket(path string) *websocket.Conn {
292         origin := t.BaseUrl() + "/"
293         urlPath := t.WebSocketUrl() + path
294         ws, err := websocket.Dial(urlPath, "", origin)
295         if err != nil {
296                 panic(err)
297         }
298         return ws
299 }
300
301 func (t *TestSuite) AssertOk() {
302         t.AssertStatus(http.StatusOK)
303 }
304
305 func (t *TestSuite) AssertNotFound() {
306         t.AssertStatus(http.StatusNotFound)
307 }
308
309 func (t *TestSuite) AssertStatus(status int) {
310         if t.Response.StatusCode != status {
311                 panic(fmt.Errorf("Status: (expected) %d != %d (actual)", status, t.Response.StatusCode))
312         }
313 }
314
315 func (t *TestSuite) AssertContentType(contentType string) {
316         t.AssertHeader("Content-Type", contentType)
317 }
318
319 func (t *TestSuite) AssertHeader(name, value string) {
320         actual := t.Response.Header.Get(name)
321         if actual != value {
322                 panic(fmt.Errorf("Header %s: (expected) %s != %s (actual)", name, value, actual))
323         }
324 }
325
326 func (t *TestSuite) AssertEqual(expected, actual interface{}) {
327         if !revel.Equal(expected, actual) {
328                 panic(fmt.Errorf("(expected) %v != %v (actual)", expected, actual))
329         }
330 }
331
332 func (t *TestSuite) AssertNotEqual(expected, actual interface{}) {
333         if revel.Equal(expected, actual) {
334                 panic(fmt.Errorf("(expected) %v == %v (actual)", expected, actual))
335         }
336 }
337
338 func (t *TestSuite) Assert(exp bool) {
339         t.Assertf(exp, "Assertion failed")
340 }
341
342 func (t *TestSuite) Assertf(exp bool, formatStr string, args ...interface{}) {
343         if !exp {
344                 panic(fmt.Errorf(formatStr, args...))
345         }
346 }
347
348 // AssertContains asserts that the response contains the given string.
349 func (t *TestSuite) AssertContains(s string) {
350         if !bytes.Contains(t.ResponseBody, []byte(s)) {
351                 panic(fmt.Errorf("Assertion failed. Expected response to contain %s", s))
352         }
353 }
354
355 // AssertNotContains asserts that the response does not contain the given string.
356 func (t *TestSuite) AssertNotContains(s string) {
357         if bytes.Contains(t.ResponseBody, []byte(s)) {
358                 panic(fmt.Errorf("Assertion failed. Expected response not to contain %s", s))
359         }
360 }
361
362 // AssertContainsRegex asserts that the response matches the given regular expression.
363 func (t *TestSuite) AssertContainsRegex(regex string) {
364         r := regexp.MustCompile(regex)
365
366         if !r.Match(t.ResponseBody) {
367                 panic(fmt.Errorf("Assertion failed. Expected response to match regexp %s", regex))
368         }
369 }
370
371 func createFormFile(writer *multipart.Writer, fieldname, filename string) {
372         // Try to open the file.
373         file, err := os.Open(filename)
374         if err != nil {
375                 panic(err)
376         }
377         defer func() {
378                 _ = file.Close()
379         }()
380
381         // Create a new form-data header with the provided field name and file name.
382         // Determine Content-Type of the file by its extension.
383         h := textproto.MIMEHeader{}
384         h.Set("Content-Disposition", fmt.Sprintf(
385                 `form-data; name="%s"; filename="%s"`,
386                 escapeQuotes(fieldname),
387                 escapeQuotes(filepath.Base(filename)),
388         ))
389         h.Set("Content-Type", "application/octet-stream")
390         if ct := mime.TypeByExtension(filepath.Ext(filename)); ct != "" {
391                 h.Set("Content-Type", ct)
392         }
393         part, err := writer.CreatePart(h)
394         if err != nil {
395                 panic(err)
396         }
397
398         // Copy the content of the file we have opened not reading the whole
399         // file into memory.
400         _, err = io.Copy(part, file)
401         if err != nil {
402                 panic(err)
403         }
404 }
405
406 var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
407
408 // This function was borrowed from mime/multipart package.
409 func escapeQuotes(s string) string {
410         return quoteEscaper.Replace(s)
411 }