Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / router_test.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 revel
6
7 import (
8         "fmt"
9         "net/http"
10         "net/url"
11         "strings"
12         "testing"
13 )
14
15 // Data-driven tests that check that a given routes-file line translates into
16 // the expected Route object.
17 var routeTestCases = map[string]*Route{
18         "get / Application.Index": {
19                 Method:      "GET",
20                 Path:        "/",
21                 Action:      "Application.Index",
22                 FixedParams: []string{},
23         },
24
25         "post /app/:id Application.SaveApp": {
26                 Method:      "POST",
27                 Path:        "/app/:id",
28                 Action:      "Application.SaveApp",
29                 FixedParams: []string{},
30         },
31
32         "get /app/ Application.List": {
33                 Method:      "GET",
34                 Path:        "/app/",
35                 Action:      "Application.List",
36                 FixedParams: []string{},
37         },
38
39         `get /app/:appId/ Application.Show`: {
40                 Method:      "GET",
41                 Path:        `/app/:appId/`,
42                 Action:      "Application.Show",
43                 FixedParams: []string{},
44         },
45
46         `get /app-wild/*appId/ Application.WildShow`: {
47                 Method:      "GET",
48                 Path:        `/app-wild/*appId/`,
49                 Action:      "Application.WildShow",
50                 FixedParams: []string{},
51         },
52
53         `GET /public/:filepath   Static.Serve("public")`: {
54                 Method: "GET",
55                 Path:   "/public/:filepath",
56                 Action: "Static.Serve",
57                 FixedParams: []string{
58                         "public",
59                 },
60         },
61
62         `GET /javascript/:filepath Static.Serve("public/js")`: {
63                 Method: "GET",
64                 Path:   "/javascript/:filepath",
65                 Action: "Static.Serve",
66                 FixedParams: []string{
67                         "public",
68                 },
69         },
70
71         "* /apps/:id/:action Application.:action": {
72                 Method:      "*",
73                 Path:        "/apps/:id/:action",
74                 Action:      "Application.:action",
75                 FixedParams: []string{},
76         },
77
78         "* /:controller/:action :controller.:action": {
79                 Method:      "*",
80                 Path:        "/:controller/:action",
81                 Action:      ":controller.:action",
82                 FixedParams: []string{},
83         },
84
85         `GET / Application.Index("Test", "Test2")`: {
86                 Method: "GET",
87                 Path:   "/",
88                 Action: "Application.Index",
89                 FixedParams: []string{
90                         "Test",
91                         "Test2",
92                 },
93         },
94 }
95
96 // Run the test cases above.
97 func TestComputeRoute(t *testing.T) {
98         for routeLine, expected := range routeTestCases {
99                 method, path, action, fixedArgs, found := parseRouteLine(routeLine)
100                 if !found {
101                         t.Error("Failed to parse route line:", routeLine)
102                         continue
103                 }
104                 actual := NewRoute(appModule, method, path, action, fixedArgs, "", 0)
105                 eq(t, "Method", actual.Method, expected.Method)
106                 eq(t, "Path", actual.Path, expected.Path)
107                 eq(t, "Action", actual.Action, expected.Action)
108                 if t.Failed() {
109                         t.Fatal("Failed on route:", routeLine)
110                 }
111         }
112 }
113
114 // Router Tests
115
116 const TestRoutes = `
117 # This is a comment
118 GET             /                   Application.Index
119 GET             /test/              Application.Index("Test", "Test2")
120 GET             /app/:id/           Application.Show
121 GET             /app-wild/*id/          Application.WildShow
122 POST            /app/:id            Application.Save
123 PATCH           /app/:id/           Application.Update
124 PROPFIND        /app/:id                        Application.WebDevMethodPropFind
125 MKCOL           /app/:id                        Application.WebDevMethodMkCol
126 COPY            /app/:id                        Application.WebDevMethodCopy
127 MOVE            /app/:id                        Application.WebDevMethodMove
128 PROPPATCH       /app/:id                        Application.WebDevMethodPropPatch
129 LOCK            /app/:id                        Application.WebDevMethodLock
130 UNLOCK          /app/:id                        Application.WebDevMethodUnLock
131 TRACE           /app/:id                        Application.WebDevMethodTrace
132 PURGE           /app/:id                        Application.CacheMethodPurge
133 GET   /javascript/:filepath      App\Static.Serve("public/js")
134 GET   /public/*filepath          Static.Serve("public")
135 *     /:controller/:action       :controller.:action
136
137 GET   /favicon.ico               404
138 `
139
140 var routeMatchTestCases = map[*http.Request]*RouteMatch{
141         {
142                 Method: "GET",
143                 URL:    &url.URL{Path: "/"},
144         }: {
145                 ControllerName: "application",
146                 MethodName:     "Index",
147                 FixedParams:    []string{},
148                 Params:         map[string][]string{},
149         },
150
151         {
152                 Method: "GET",
153                 URL:    &url.URL{Path: "/test/"},
154         }: {
155                 ControllerName: "application",
156                 MethodName:     "Index",
157                 FixedParams:    []string{"Test", "Test2"},
158                 Params:         map[string][]string{},
159         },
160
161         {
162                 Method: "GET",
163                 URL:    &url.URL{Path: "/app/123"},
164         }: {
165                 ControllerName: "application",
166                 MethodName:     "Show",
167                 FixedParams:    []string{},
168                 Params:         map[string][]string{"id": {"123"}},
169         },
170
171         {
172                 Method: "PATCH",
173                 URL:    &url.URL{Path: "/app/123"},
174         }: {
175                 ControllerName: "application",
176                 MethodName:     "Update",
177                 FixedParams:    []string{},
178                 Params:         map[string][]string{"id": {"123"}},
179         },
180
181         {
182                 Method: "POST",
183                 URL:    &url.URL{Path: "/app/123"},
184         }: {
185                 ControllerName: "application",
186                 MethodName:     "Save",
187                 FixedParams:    []string{},
188                 Params:         map[string][]string{"id": {"123"}},
189         },
190
191         {
192                 Method: "GET",
193                 URL:    &url.URL{Path: "/app/123/"},
194         }: {
195                 ControllerName: "application",
196                 MethodName:     "Show",
197                 FixedParams:    []string{},
198                 Params:         map[string][]string{"id": {"123"}},
199         },
200
201         {
202                 Method: "GET",
203                 URL:    &url.URL{Path: "/public/css/style.css"},
204         }: {
205                 ControllerName: "static",
206                 MethodName:     "Serve",
207                 FixedParams:    []string{"public"},
208                 Params:         map[string][]string{"filepath": {"css/style.css"}},
209         },
210
211         {
212                 Method: "GET",
213                 URL:    &url.URL{Path: "/javascript/sessvars.js"},
214         }: {
215                 ControllerName: "static",
216                 MethodName:     "Serve",
217                 FixedParams:    []string{"public/js"},
218                 Params:         map[string][]string{"filepath": {"sessvars.js"}},
219         },
220
221         {
222                 Method: "GET",
223                 URL:    &url.URL{Path: "/Implicit/Route"},
224         }: {
225                 ControllerName: "implicit",
226                 MethodName:     "Route",
227                 FixedParams:    []string{},
228                 Params: map[string][]string{
229                         "METHOD":     {"GET"},
230                         "controller": {"Implicit"},
231                         "action":     {"Route"},
232                 },
233         },
234
235         {
236                 Method: "GET",
237                 URL:    &url.URL{Path: "/favicon.ico"},
238         }: {
239                 ControllerName: "",
240                 MethodName:     "",
241                 Action:         "404",
242                 FixedParams:    []string{},
243                 Params:         map[string][]string{},
244         },
245
246         {
247                 Method: "POST",
248                 URL:    &url.URL{Path: "/app/123"},
249                 Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}},
250         }: {
251                 ControllerName: "application",
252                 MethodName:     "Update",
253                 FixedParams:    []string{},
254                 Params:         map[string][]string{"id": {"123"}},
255         },
256
257         {
258                 Method: "GET",
259                 URL:    &url.URL{Path: "/app/123"},
260                 Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}},
261         }: {
262                 ControllerName: "application",
263                 MethodName:     "Show",
264                 FixedParams:    []string{},
265                 Params:         map[string][]string{"id": {"123"}},
266         },
267
268         {
269                 Method: "PATCH",
270                 URL:    &url.URL{Path: "/app/123"},
271         }: {
272                 ControllerName: "application",
273                 MethodName:     "Update",
274                 FixedParams:    []string{},
275                 Params:         map[string][]string{"id": {"123"}},
276         },
277
278         {
279                 Method: "PROPFIND",
280                 URL:    &url.URL{Path: "/app/123"},
281         }: {
282                 ControllerName: "application",
283                 MethodName:     "WebDevMethodPropFind",
284                 FixedParams:    []string{},
285                 Params:         map[string][]string{"id": {"123"}},
286         },
287
288         {
289                 Method: "MKCOL",
290                 URL:    &url.URL{Path: "/app/123"},
291         }: {
292                 ControllerName: "application",
293                 MethodName:     "WebDevMethodMkCol",
294                 FixedParams:    []string{},
295                 Params:         map[string][]string{"id": {"123"}},
296         },
297
298         {
299                 Method: "COPY",
300                 URL:    &url.URL{Path: "/app/123"},
301         }: {
302                 ControllerName: "application",
303                 MethodName:     "WebDevMethodCopy",
304                 FixedParams:    []string{},
305                 Params:         map[string][]string{"id": {"123"}},
306         },
307
308         {
309                 Method: "MOVE",
310                 URL:    &url.URL{Path: "/app/123"},
311         }: {
312                 ControllerName: "application",
313                 MethodName:     "WebDevMethodMove",
314                 FixedParams:    []string{},
315                 Params:         map[string][]string{"id": {"123"}},
316         },
317
318         {
319                 Method: "PROPPATCH",
320                 URL:    &url.URL{Path: "/app/123"},
321         }: {
322                 ControllerName: "application",
323                 MethodName:     "WebDevMethodPropPatch",
324                 FixedParams:    []string{},
325                 Params:         map[string][]string{"id": {"123"}},
326         },
327         {
328                 Method: "LOCK",
329                 URL:    &url.URL{Path: "/app/123"},
330         }: {
331                 ControllerName: "application",
332                 MethodName:     "WebDevMethodLock",
333                 FixedParams:    []string{},
334                 Params:         map[string][]string{"id": {"123"}},
335         },
336
337         {
338                 Method: "UNLOCK",
339                 URL:    &url.URL{Path: "/app/123"},
340         }: {
341                 ControllerName: "application",
342                 MethodName:     "WebDevMethodUnLock",
343                 FixedParams:    []string{},
344                 Params:         map[string][]string{"id": {"123"}},
345         },
346
347         {
348                 Method: "TRACE",
349                 URL:    &url.URL{Path: "/app/123"},
350         }: {
351                 ControllerName: "application",
352                 MethodName:     "WebDevMethodTrace",
353                 FixedParams:    []string{},
354                 Params:         map[string][]string{"id": {"123"}},
355         },
356
357         {
358                 Method: "PURGE",
359                 URL:    &url.URL{Path: "/app/123"},
360         }: {
361                 ControllerName: "application",
362                 MethodName:     "CacheMethodPurge",
363                 FixedParams:    []string{},
364                 Params:         map[string][]string{"id": {"123"}},
365         },
366 }
367
368 func TestRouteMatches(t *testing.T) {
369         initControllers()
370         BasePath = "/BasePath"
371         router := NewRouter("")
372         router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false)
373         if err := router.updateTree(); err != nil {
374                 t.Errorf("updateTree failed: %s", err)
375         }
376         for req, expected := range routeMatchTestCases {
377                 t.Log("Routing:", req.Method, req.URL)
378
379                 context := NewGoContext(nil)
380                 context.Request.SetRequest(req)
381                 c := NewTestController(nil, req)
382
383                 actual := router.Route(c.Request)
384                 if !eq(t, "Found route", actual != nil, expected != nil) {
385                         continue
386                 }
387                 if expected.ControllerName != "" {
388                         eq(t, "ControllerName", actual.ControllerName, appModule.Namespace()+expected.ControllerName)
389                 } else {
390                         eq(t, "ControllerName", actual.ControllerName, expected.ControllerName)
391                 }
392
393                 eq(t, "MethodName", actual.MethodName, strings.ToLower(expected.MethodName))
394                 eq(t, "len(Params)", len(actual.Params), len(expected.Params))
395                 for key, actualValue := range actual.Params {
396                         eq(t, "Params "+key, actualValue[0], expected.Params[key][0])
397                 }
398                 eq(t, "len(FixedParams)", len(actual.FixedParams), len(expected.FixedParams))
399                 for i, actualValue := range actual.FixedParams {
400                         eq(t, "FixedParams", actualValue, expected.FixedParams[i])
401                 }
402         }
403 }
404
405 // Reverse Routing
406
407 type ReverseRouteArgs struct {
408         action string
409         args   map[string]string
410 }
411
412 var reverseRoutingTestCases = map[*ReverseRouteArgs]*ActionDefinition{
413         {
414                 action: "Application.Index",
415                 args:   map[string]string{},
416         }: {
417                 URL:    "/",
418                 Method: "GET",
419                 Star:   false,
420                 Action: "Application.Index",
421         },
422
423         {
424                 action: "Application.Show",
425                 args:   map[string]string{"id": "123"},
426         }: {
427                 URL:    "/app/123/",
428                 Method: "GET",
429                 Star:   false,
430                 Action: "Application.Show",
431         },
432
433         {
434                 action: "Implicit.Route",
435                 args:   map[string]string{},
436         }: {
437                 URL:    "/implicit/route",
438                 Method: "GET",
439                 Star:   true,
440                 Action: "Implicit.Route",
441         },
442
443         {
444                 action: "Application.Save",
445                 args:   map[string]string{"id": "123", "c": "http://continue"},
446         }: {
447                 URL:    "/app/123?c=http%3A%2F%2Fcontinue",
448                 Method: "POST",
449                 Star:   false,
450                 Action: "Application.Save",
451         },
452
453         {
454                 action: "Application.WildShow",
455                 args:   map[string]string{"id": "123"},
456         }: {
457                 URL:    "/app-wild/123/",
458                 Method: "GET",
459                 Star:   false,
460                 Action: "Application.WildShow",
461         },
462
463         {
464                 action: "Application.WebDevMethodPropFind",
465                 args:   map[string]string{"id": "123"},
466         }: {
467                 URL:    "/app/123",
468                 Method: "PROPFIND",
469                 Star:   false,
470                 Action: "Application.WebDevMethodPropFind",
471         },
472         {
473                 action: "Application.WebDevMethodMkCol",
474                 args:   map[string]string{"id": "123"},
475         }: {
476                 URL:    "/app/123",
477                 Method: "MKCOL",
478                 Star:   false,
479                 Action: "Application.WebDevMethodMkCol",
480         },
481         {
482                 action: "Application.WebDevMethodCopy",
483                 args:   map[string]string{"id": "123"},
484         }: {
485                 URL:    "/app/123",
486                 Method: "COPY",
487                 Star:   false,
488                 Action: "Application.WebDevMethodCopy",
489         },
490         {
491                 action: "Application.WebDevMethodMove",
492                 args:   map[string]string{"id": "123"},
493         }: {
494                 URL:    "/app/123",
495                 Method: "MOVE",
496                 Star:   false,
497                 Action: "Application.WebDevMethodMove",
498         },
499         {
500                 action: "Application.WebDevMethodPropPatch",
501                 args:   map[string]string{"id": "123"},
502         }: {
503                 URL:    "/app/123",
504                 Method: "PROPPATCH",
505                 Star:   false,
506                 Action: "Application.WebDevMethodPropPatch",
507         },
508         {
509                 action: "Application.WebDevMethodLock",
510                 args:   map[string]string{"id": "123"},
511         }: {
512                 URL:    "/app/123",
513                 Method: "LOCK",
514                 Star:   false,
515                 Action: "Application.WebDevMethodLock",
516         },
517         {
518                 action: "Application.WebDevMethodUnLock",
519                 args:   map[string]string{"id": "123"},
520         }: {
521                 URL:    "/app/123",
522                 Method: "UNLOCK",
523                 Star:   false,
524                 Action: "Application.WebDevMethodUnLock",
525         },
526         {
527                 action: "Application.WebDevMethodTrace",
528                 args:   map[string]string{"id": "123"},
529         }: {
530                 URL:    "/app/123",
531                 Method: "TRACE",
532                 Star:   false,
533                 Action: "Application.WebDevMethodTrace",
534         },
535         {
536                 action: "Application.CacheMethodPurge",
537                 args:   map[string]string{"id": "123"},
538         }: {
539                 URL:    "/app/123",
540                 Method: "PURGE",
541                 Star:   false,
542                 Action: "Application.CacheMethodPurge",
543         },
544 }
545
546 type testController struct {
547         *Controller
548 }
549
550 func initControllers() {
551         registerControllers()
552 }
553 func TestReverseRouting(t *testing.T) {
554         initControllers()
555         router := NewRouter("")
556         router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false)
557         for routeArgs, expected := range reverseRoutingTestCases {
558                 actual := router.Reverse(routeArgs.action, routeArgs.args)
559                 if !eq(t, fmt.Sprintf("Found route %s %s", routeArgs.action, actual), actual != nil, expected != nil) {
560                         continue
561                 }
562                 eq(t, "Url", actual.URL, expected.URL)
563                 eq(t, "Method", actual.Method, expected.Method)
564                 eq(t, "Star", actual.Star, expected.Star)
565                 eq(t, "Action", actual.Action, expected.Action)
566         }
567 }
568
569 func BenchmarkRouter(b *testing.B) {
570         router := NewRouter("")
571         router.Routes, _ = parseRoutes(nil, "", "", TestRoutes, false)
572         if err := router.updateTree(); err != nil {
573                 b.Errorf("updateTree failed: %s", err)
574         }
575         b.ResetTimer()
576         for i := 0; i < b.N/len(routeMatchTestCases); i++ {
577                 for req := range routeMatchTestCases {
578                         c := NewTestController(nil, req)
579                         r := router.Route(c.Request)
580                         if r == nil {
581                                 b.Errorf("Request not found: %s", req.URL.Path)
582                         }
583                 }
584         }
585 }
586
587 // The benchmark from github.com/ant0ine/go-urlrouter
588 func BenchmarkLargeRouter(b *testing.B) {
589         router := NewRouter("")
590
591         routePaths := []string{
592                 "/",
593                 "/signin",
594                 "/signout",
595                 "/profile",
596                 "/settings",
597                 "/upload/*file",
598         }
599         for i := 0; i < 10; i++ {
600                 for j := 0; j < 5; j++ {
601                         routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id/property%d", i, j))
602                 }
603                 routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id", i))
604                 routePaths = append(routePaths, fmt.Sprintf("/resource%d", i))
605         }
606         routePaths = append(routePaths, "/:any")
607
608         for _, p := range routePaths {
609                 router.Routes = append(router.Routes,
610                         NewRoute(appModule, "GET", p, "Controller.Action", "", "", 0))
611         }
612         if err := router.updateTree(); err != nil {
613                 b.Errorf("updateTree failed: %s", err)
614         }
615
616         requestUrls := []string{
617                 "http://example.org/",
618                 "http://example.org/resource9/123",
619                 "http://example.org/resource9/123/property1",
620                 "http://example.org/doesnotexist",
621         }
622         var reqs []*http.Request
623         for _, url := range requestUrls {
624                 req, _ := http.NewRequest("GET", url, nil)
625                 reqs = append(reqs, req)
626         }
627
628         b.ResetTimer()
629
630         for i := 0; i < b.N/len(reqs); i++ {
631                 for _, req := range reqs {
632                         c := NewTestController(nil, req)
633                         route := router.Route(c.Request)
634                         if route == nil {
635                                 b.Errorf("Failed to route: %s", req.URL.Path)
636                         }
637                 }
638         }
639 }
640
641 func BenchmarkRouterFilter(b *testing.B) {
642         startFakeBookingApp()
643         controllers := []*Controller{
644                 NewTestController(nil, showRequest),
645                 NewTestController(nil, staticRequest),
646         }
647         for _, c := range controllers {
648                 c.Params = &Params{}
649                 ParseParams(c.Params, c.Request)
650         }
651
652         b.ResetTimer()
653         for i := 0; i < b.N/len(controllers); i++ {
654                 for _, c := range controllers {
655                         RouterFilter(c, NilChain)
656                 }
657         }
658 }
659
660 func TestOverrideMethodFilter(t *testing.T) {
661         req, _ := http.NewRequest("POST", "/hotels/3", strings.NewReader("_method=put"))
662         req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
663         c := NewTestController(nil, req)
664
665         if HTTPMethodOverride(c, NilChain); c.Request.Method != "PUT" {
666                 t.Errorf("Expected to override current method '%s' in route, found '%s' instead", "", c.Request.Method)
667         }
668 }
669
670 // Helpers
671
672 func eq(t *testing.T, name string, a, b interface{}) bool {
673         if a != b {
674                 t.Error(name, ": (actual)", a, " != ", b, "(expected)")
675                 return false
676         }
677         return true
678 }