Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / router_test.go
diff --git a/src/foundation/api/revel/router_test.go b/src/foundation/api/revel/router_test.go
new file mode 100644 (file)
index 0000000..fe66970
--- /dev/null
@@ -0,0 +1,678 @@
+// 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 revel
+
+import (
+       "fmt"
+       "net/http"
+       "net/url"
+       "strings"
+       "testing"
+)
+
+// Data-driven tests that check that a given routes-file line translates into
+// the expected Route object.
+var routeTestCases = map[string]*Route{
+       "get / Application.Index": {
+               Method:      "GET",
+               Path:        "/",
+               Action:      "Application.Index",
+               FixedParams: []string{},
+       },
+
+       "post /app/:id Application.SaveApp": {
+               Method:      "POST",
+               Path:        "/app/:id",
+               Action:      "Application.SaveApp",
+               FixedParams: []string{},
+       },
+
+       "get /app/ Application.List": {
+               Method:      "GET",
+               Path:        "/app/",
+               Action:      "Application.List",
+               FixedParams: []string{},
+       },
+
+       `get /app/:appId/ Application.Show`: {
+               Method:      "GET",
+               Path:        `/app/:appId/`,
+               Action:      "Application.Show",
+               FixedParams: []string{},
+       },
+
+       `get /app-wild/*appId/ Application.WildShow`: {
+               Method:      "GET",
+               Path:        `/app-wild/*appId/`,
+               Action:      "Application.WildShow",
+               FixedParams: []string{},
+       },
+
+       `GET /public/:filepath   Static.Serve("public")`: {
+               Method: "GET",
+               Path:   "/public/:filepath",
+               Action: "Static.Serve",
+               FixedParams: []string{
+                       "public",
+               },
+       },
+
+       `GET /javascript/:filepath Static.Serve("public/js")`: {
+               Method: "GET",
+               Path:   "/javascript/:filepath",
+               Action: "Static.Serve",
+               FixedParams: []string{
+                       "public",
+               },
+       },
+
+       "* /apps/:id/:action Application.:action": {
+               Method:      "*",
+               Path:        "/apps/:id/:action",
+               Action:      "Application.:action",
+               FixedParams: []string{},
+       },
+
+       "* /:controller/:action :controller.:action": {
+               Method:      "*",
+               Path:        "/:controller/:action",
+               Action:      ":controller.:action",
+               FixedParams: []string{},
+       },
+
+       `GET / Application.Index("Test", "Test2")`: {
+               Method: "GET",
+               Path:   "/",
+               Action: "Application.Index",
+               FixedParams: []string{
+                       "Test",
+                       "Test2",
+               },
+       },
+}
+
+// Run the test cases above.
+func TestComputeRoute(t *testing.T) {
+       for routeLine, expected := range routeTestCases {
+               method, path, action, fixedArgs, found := parseRouteLine(routeLine)
+               if !found {
+                       t.Error("Failed to parse route line:", routeLine)
+                       continue
+               }
+               actual := NewRoute(appModule, method, path, action, fixedArgs, "", 0)
+               eq(t, "Method", actual.Method, expected.Method)
+               eq(t, "Path", actual.Path, expected.Path)
+               eq(t, "Action", actual.Action, expected.Action)
+               if t.Failed() {
+                       t.Fatal("Failed on route:", routeLine)
+               }
+       }
+}
+
+// Router Tests
+
+const TestRoutes = `
+# This is a comment
+GET            /                   Application.Index
+GET            /test/              Application.Index("Test", "Test2")
+GET            /app/:id/           Application.Show
+GET            /app-wild/*id/          Application.WildShow
+POST           /app/:id            Application.Save
+PATCH          /app/:id/           Application.Update
+PROPFIND       /app/:id                        Application.WebDevMethodPropFind
+MKCOL          /app/:id                        Application.WebDevMethodMkCol
+COPY           /app/:id                        Application.WebDevMethodCopy
+MOVE           /app/:id                        Application.WebDevMethodMove
+PROPPATCH      /app/:id                        Application.WebDevMethodPropPatch
+LOCK           /app/:id                        Application.WebDevMethodLock
+UNLOCK         /app/:id                        Application.WebDevMethodUnLock
+TRACE          /app/:id                        Application.WebDevMethodTrace
+PURGE          /app/:id                        Application.CacheMethodPurge
+GET   /javascript/:filepath      App\Static.Serve("public/js")
+GET   /public/*filepath          Static.Serve("public")
+*     /:controller/:action       :controller.:action
+
+GET   /favicon.ico               404
+`
+
+var routeMatchTestCases = map[*http.Request]*RouteMatch{
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Index",
+               FixedParams:    []string{},
+               Params:         map[string][]string{},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/test/"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Index",
+               FixedParams:    []string{"Test", "Test2"},
+               Params:         map[string][]string{},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Show",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PATCH",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Update",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "POST",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Save",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/app/123/"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Show",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/public/css/style.css"},
+       }: {
+               ControllerName: "static",
+               MethodName:     "Serve",
+               FixedParams:    []string{"public"},
+               Params:         map[string][]string{"filepath": {"css/style.css"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/javascript/sessvars.js"},
+       }: {
+               ControllerName: "static",
+               MethodName:     "Serve",
+               FixedParams:    []string{"public/js"},
+               Params:         map[string][]string{"filepath": {"sessvars.js"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/Implicit/Route"},
+       }: {
+               ControllerName: "implicit",
+               MethodName:     "Route",
+               FixedParams:    []string{},
+               Params: map[string][]string{
+                       "METHOD":     {"GET"},
+                       "controller": {"Implicit"},
+                       "action":     {"Route"},
+               },
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/favicon.ico"},
+       }: {
+               ControllerName: "",
+               MethodName:     "",
+               Action:         "404",
+               FixedParams:    []string{},
+               Params:         map[string][]string{},
+       },
+
+       {
+               Method: "POST",
+               URL:    &url.URL{Path: "/app/123"},
+               Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Update",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/app/123"},
+               Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Show",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PATCH",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Update",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PROPFIND",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodPropFind",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "MKCOL",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodMkCol",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "COPY",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodCopy",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "MOVE",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodMove",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PROPPATCH",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodPropPatch",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+       {
+               Method: "LOCK",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodLock",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "UNLOCK",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodUnLock",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "TRACE",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodTrace",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PURGE",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "CacheMethodPurge",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+}
+
+func TestRouteMatches(t *testing.T) {
+       initControllers()
+       BasePath = "/BasePath"
+       router := NewRouter("")
+       router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false)
+       if err := router.updateTree(); err != nil {
+               t.Errorf("updateTree failed: %s", err)
+       }
+       for req, expected := range routeMatchTestCases {
+               t.Log("Routing:", req.Method, req.URL)
+
+               context := NewGoContext(nil)
+               context.Request.SetRequest(req)
+               c := NewTestController(nil, req)
+
+               actual := router.Route(c.Request)
+               if !eq(t, "Found route", actual != nil, expected != nil) {
+                       continue
+               }
+               if expected.ControllerName != "" {
+                       eq(t, "ControllerName", actual.ControllerName, appModule.Namespace()+expected.ControllerName)
+               } else {
+                       eq(t, "ControllerName", actual.ControllerName, expected.ControllerName)
+               }
+
+               eq(t, "MethodName", actual.MethodName, strings.ToLower(expected.MethodName))
+               eq(t, "len(Params)", len(actual.Params), len(expected.Params))
+               for key, actualValue := range actual.Params {
+                       eq(t, "Params "+key, actualValue[0], expected.Params[key][0])
+               }
+               eq(t, "len(FixedParams)", len(actual.FixedParams), len(expected.FixedParams))
+               for i, actualValue := range actual.FixedParams {
+                       eq(t, "FixedParams", actualValue, expected.FixedParams[i])
+               }
+       }
+}
+
+// Reverse Routing
+
+type ReverseRouteArgs struct {
+       action string
+       args   map[string]string
+}
+
+var reverseRoutingTestCases = map[*ReverseRouteArgs]*ActionDefinition{
+       {
+               action: "Application.Index",
+               args:   map[string]string{},
+       }: {
+               URL:    "/",
+               Method: "GET",
+               Star:   false,
+               Action: "Application.Index",
+       },
+
+       {
+               action: "Application.Show",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123/",
+               Method: "GET",
+               Star:   false,
+               Action: "Application.Show",
+       },
+
+       {
+               action: "Implicit.Route",
+               args:   map[string]string{},
+       }: {
+               URL:    "/implicit/route",
+               Method: "GET",
+               Star:   true,
+               Action: "Implicit.Route",
+       },
+
+       {
+               action: "Application.Save",
+               args:   map[string]string{"id": "123", "c": "http://continue"},
+       }: {
+               URL:    "/app/123?c=http%3A%2F%2Fcontinue",
+               Method: "POST",
+               Star:   false,
+               Action: "Application.Save",
+       },
+
+       {
+               action: "Application.WildShow",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app-wild/123/",
+               Method: "GET",
+               Star:   false,
+               Action: "Application.WildShow",
+       },
+
+       {
+               action: "Application.WebDevMethodPropFind",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "PROPFIND",
+               Star:   false,
+               Action: "Application.WebDevMethodPropFind",
+       },
+       {
+               action: "Application.WebDevMethodMkCol",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "MKCOL",
+               Star:   false,
+               Action: "Application.WebDevMethodMkCol",
+       },
+       {
+               action: "Application.WebDevMethodCopy",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "COPY",
+               Star:   false,
+               Action: "Application.WebDevMethodCopy",
+       },
+       {
+               action: "Application.WebDevMethodMove",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "MOVE",
+               Star:   false,
+               Action: "Application.WebDevMethodMove",
+       },
+       {
+               action: "Application.WebDevMethodPropPatch",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "PROPPATCH",
+               Star:   false,
+               Action: "Application.WebDevMethodPropPatch",
+       },
+       {
+               action: "Application.WebDevMethodLock",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "LOCK",
+               Star:   false,
+               Action: "Application.WebDevMethodLock",
+       },
+       {
+               action: "Application.WebDevMethodUnLock",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "UNLOCK",
+               Star:   false,
+               Action: "Application.WebDevMethodUnLock",
+       },
+       {
+               action: "Application.WebDevMethodTrace",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "TRACE",
+               Star:   false,
+               Action: "Application.WebDevMethodTrace",
+       },
+       {
+               action: "Application.CacheMethodPurge",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "PURGE",
+               Star:   false,
+               Action: "Application.CacheMethodPurge",
+       },
+}
+
+type testController struct {
+       *Controller
+}
+
+func initControllers() {
+       registerControllers()
+}
+func TestReverseRouting(t *testing.T) {
+       initControllers()
+       router := NewRouter("")
+       router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false)
+       for routeArgs, expected := range reverseRoutingTestCases {
+               actual := router.Reverse(routeArgs.action, routeArgs.args)
+               if !eq(t, fmt.Sprintf("Found route %s %s", routeArgs.action, actual), actual != nil, expected != nil) {
+                       continue
+               }
+               eq(t, "Url", actual.URL, expected.URL)
+               eq(t, "Method", actual.Method, expected.Method)
+               eq(t, "Star", actual.Star, expected.Star)
+               eq(t, "Action", actual.Action, expected.Action)
+       }
+}
+
+func BenchmarkRouter(b *testing.B) {
+       router := NewRouter("")
+       router.Routes, _ = parseRoutes(nil, "", "", TestRoutes, false)
+       if err := router.updateTree(); err != nil {
+               b.Errorf("updateTree failed: %s", err)
+       }
+       b.ResetTimer()
+       for i := 0; i < b.N/len(routeMatchTestCases); i++ {
+               for req := range routeMatchTestCases {
+                       c := NewTestController(nil, req)
+                       r := router.Route(c.Request)
+                       if r == nil {
+                               b.Errorf("Request not found: %s", req.URL.Path)
+                       }
+               }
+       }
+}
+
+// The benchmark from github.com/ant0ine/go-urlrouter
+func BenchmarkLargeRouter(b *testing.B) {
+       router := NewRouter("")
+
+       routePaths := []string{
+               "/",
+               "/signin",
+               "/signout",
+               "/profile",
+               "/settings",
+               "/upload/*file",
+       }
+       for i := 0; i < 10; i++ {
+               for j := 0; j < 5; j++ {
+                       routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id/property%d", i, j))
+               }
+               routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id", i))
+               routePaths = append(routePaths, fmt.Sprintf("/resource%d", i))
+       }
+       routePaths = append(routePaths, "/:any")
+
+       for _, p := range routePaths {
+               router.Routes = append(router.Routes,
+                       NewRoute(appModule, "GET", p, "Controller.Action", "", "", 0))
+       }
+       if err := router.updateTree(); err != nil {
+               b.Errorf("updateTree failed: %s", err)
+       }
+
+       requestUrls := []string{
+               "http://example.org/",
+               "http://example.org/resource9/123",
+               "http://example.org/resource9/123/property1",
+               "http://example.org/doesnotexist",
+       }
+       var reqs []*http.Request
+       for _, url := range requestUrls {
+               req, _ := http.NewRequest("GET", url, nil)
+               reqs = append(reqs, req)
+       }
+
+       b.ResetTimer()
+
+       for i := 0; i < b.N/len(reqs); i++ {
+               for _, req := range reqs {
+                       c := NewTestController(nil, req)
+                       route := router.Route(c.Request)
+                       if route == nil {
+                               b.Errorf("Failed to route: %s", req.URL.Path)
+                       }
+               }
+       }
+}
+
+func BenchmarkRouterFilter(b *testing.B) {
+       startFakeBookingApp()
+       controllers := []*Controller{
+               NewTestController(nil, showRequest),
+               NewTestController(nil, staticRequest),
+       }
+       for _, c := range controllers {
+               c.Params = &Params{}
+               ParseParams(c.Params, c.Request)
+       }
+
+       b.ResetTimer()
+       for i := 0; i < b.N/len(controllers); i++ {
+               for _, c := range controllers {
+                       RouterFilter(c, NilChain)
+               }
+       }
+}
+
+func TestOverrideMethodFilter(t *testing.T) {
+       req, _ := http.NewRequest("POST", "/hotels/3", strings.NewReader("_method=put"))
+       req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
+       c := NewTestController(nil, req)
+
+       if HTTPMethodOverride(c, NilChain); c.Request.Method != "PUT" {
+               t.Errorf("Expected to override current method '%s' in route, found '%s' instead", "", c.Request.Method)
+       }
+}
+
+// Helpers
+
+func eq(t *testing.T, name string, a, b interface{}) bool {
+       if a != b {
+               t.Error(name, ": (actual)", a, " != ", b, "(expected)")
+               return false
+       }
+       return true
+}