1
2
3
4
5
6
7 package mux
8
9 import (
10 "bytes"
11 "net/http"
12 "testing"
13 )
14
15
16
17
18
19
20
21
22
23
24 type ResponseRecorder struct {
25 Code int
26 HeaderMap http.Header
27 Body *bytes.Buffer
28 Flushed bool
29 }
30
31
32 func NewRecorder() *ResponseRecorder {
33 return &ResponseRecorder{
34 HeaderMap: make(http.Header),
35 Body: new(bytes.Buffer),
36 }
37 }
38
39
40 func (rw *ResponseRecorder) Header() http.Header {
41 return rw.HeaderMap
42 }
43
44
45 func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
46 if rw.Body != nil {
47 rw.Body.Write(buf)
48 }
49 if rw.Code == 0 {
50 rw.Code = http.StatusOK
51 }
52 return len(buf), nil
53 }
54
55
56 func (rw *ResponseRecorder) WriteHeader(code int) {
57 rw.Code = code
58 }
59
60
61 func (rw *ResponseRecorder) Flush() {
62 rw.Flushed = true
63 }
64
65
66
67 func TestRouteMatchers(t *testing.T) {
68 var scheme, host, path, query, method string
69 var headers map[string]string
70 var resultVars map[bool]map[string]string
71
72 router := NewRouter()
73 router.NewRoute().Host("{var1}.google.com").
74 Path("/{var2:[a-z]+}/{var3:[0-9]+}").
75 Queries("foo", "bar").
76 Methods("GET").
77 Schemes("https").
78 Headers("x-requested-with", "XMLHttpRequest")
79 router.NewRoute().Host("www.{var4}.com").
80 PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
81 Queries("baz", "ding").
82 Methods("POST").
83 Schemes("http").
84 Headers("Content-Type", "application/json")
85
86 reset := func() {
87
88 scheme = "https"
89 host = "www.google.com"
90 path = "/product/42"
91 query = "?foo=bar"
92 method = "GET"
93 headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
94 resultVars = map[bool]map[string]string{
95 true: {"var1": "www", "var2": "product", "var3": "42"},
96 false: {},
97 }
98 }
99
100 reset2 := func() {
101
102 scheme = "http"
103 host = "www.google.com"
104 path = "/foo/product/42/path/that/is/ignored"
105 query = "?baz=ding"
106 method = "POST"
107 headers = map[string]string{"Content-Type": "application/json"}
108 resultVars = map[bool]map[string]string{
109 true: {"var4": "google", "var5": "product", "var6": "42"},
110 false: {},
111 }
112 }
113
114 match := func(shouldMatch bool) {
115 url := scheme + "://" + host + path + query
116 request, _ := http.NewRequest(method, url, nil)
117 for key, value := range headers {
118 request.Header.Add(key, value)
119 }
120
121 var routeMatch RouteMatch
122 matched := router.Match(request, &routeMatch)
123 if matched != shouldMatch {
124 t.Errorf("Expected: %v\nGot: %v\nRequest: %v %v", shouldMatch, matched, request.Method, url)
125 }
126
127 if matched {
128 currentRoute := routeMatch.Route
129 if currentRoute == nil {
130 t.Errorf("Expected a current route.")
131 }
132 vars := routeMatch.Vars
133 expectedVars := resultVars[shouldMatch]
134 if len(vars) != len(expectedVars) {
135 t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
136 }
137 for name, value := range vars {
138 if expectedVars[name] != value {
139 t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
140 }
141 }
142 }
143 }
144
145
146
147
148 reset()
149 match(true)
150
151
152 reset()
153 scheme = "http"
154 match(false)
155
156
157 reset()
158 host = "www.mygoogle.com"
159 match(false)
160
161
162 reset()
163 path = "/product/notdigits"
164 match(false)
165
166
167 reset()
168 query = "?foo=baz"
169 match(false)
170
171
172 reset()
173 method = "POST"
174 match(false)
175
176
177 reset()
178 headers = map[string]string{}
179 match(false)
180
181
182 reset()
183 match(true)
184
185
186
187 reset2()
188 match(true)
189
190
191 reset2()
192 scheme = "https"
193 match(false)
194
195
196 reset2()
197 host = "sub.google.com"
198 match(false)
199
200
201 reset2()
202 path = "/bar/product/42"
203 match(false)
204
205
206 reset2()
207 query = "?foo=baz"
208 match(false)
209
210
211 reset2()
212 method = "GET"
213 match(false)
214
215
216 reset2()
217 headers = map[string]string{}
218 match(false)
219
220
221 reset2()
222 match(true)
223 }
224
225 type headerMatcherTest struct {
226 matcher headerMatcher
227 headers map[string]string
228 result bool
229 }
230
231 var headerMatcherTests = []headerMatcherTest{
232 {
233 matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
234 headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
235 result: true,
236 },
237 {
238 matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
239 headers: map[string]string{"X-Requested-With": "anything"},
240 result: true,
241 },
242 {
243 matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
244 headers: map[string]string{},
245 result: false,
246 },
247 }
248
249 type hostMatcherTest struct {
250 matcher *Route
251 url string
252 vars map[string]string
253 result bool
254 }
255
256 var hostMatcherTests = []hostMatcherTest{
257 {
258 matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
259 url: "http://abc.def.ghi/",
260 vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
261 result: true,
262 },
263 {
264 matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}:{port:.*}"),
265 url: "http://abc.def.ghi:65535/",
266 vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi", "port": "65535"},
267 result: true,
268 },
269 {
270 matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
271 url: "http://abc.def.ghi:65535/",
272 vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
273 result: true,
274 },
275 {
276 matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
277 url: "http://a.b.c/",
278 vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
279 result: false,
280 },
281 }
282
283 type methodMatcherTest struct {
284 matcher methodMatcher
285 method string
286 result bool
287 }
288
289 var methodMatcherTests = []methodMatcherTest{
290 {
291 matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
292 method: "GET",
293 result: true,
294 },
295 {
296 matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
297 method: "POST",
298 result: true,
299 },
300 {
301 matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
302 method: "PUT",
303 result: true,
304 },
305 {
306 matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
307 method: "DELETE",
308 result: false,
309 },
310 }
311
312 type pathMatcherTest struct {
313 matcher *Route
314 url string
315 vars map[string]string
316 result bool
317 }
318
319 var pathMatcherTests = []pathMatcherTest{
320 {
321 matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
322 url: "http://localhost:8080/123/456/789",
323 vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
324 result: true,
325 },
326 {
327 matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
328 url: "http://localhost:8080/1/2/3",
329 vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
330 result: false,
331 },
332 }
333
334 type schemeMatcherTest struct {
335 matcher schemeMatcher
336 url string
337 result bool
338 }
339
340 var schemeMatcherTests = []schemeMatcherTest{
341 {
342 matcher: schemeMatcher([]string{"http", "https"}),
343 url: "http://localhost:8080/",
344 result: true,
345 },
346 {
347 matcher: schemeMatcher([]string{"http", "https"}),
348 url: "https://localhost:8080/",
349 result: true,
350 },
351 {
352 matcher: schemeMatcher([]string{"https"}),
353 url: "http://localhost:8080/",
354 result: false,
355 },
356 {
357 matcher: schemeMatcher([]string{"http"}),
358 url: "https://localhost:8080/",
359 result: false,
360 },
361 }
362
363 type urlBuildingTest struct {
364 route *Route
365 vars []string
366 url string
367 }
368
369 var urlBuildingTests = []urlBuildingTest{
370 {
371 route: new(Route).Host("foo.domain.com"),
372 vars: []string{},
373 url: "http://foo.domain.com",
374 },
375 {
376 route: new(Route).Host("{subdomain}.domain.com"),
377 vars: []string{"subdomain", "bar"},
378 url: "http://bar.domain.com",
379 },
380 {
381 route: new(Route).Host("{subdomain}.domain.com:{port:.*}"),
382 vars: []string{"subdomain", "bar", "port", "65535"},
383 url: "http://bar.domain.com:65535",
384 },
385 {
386 route: new(Route).Host("foo.domain.com").Path("/articles"),
387 vars: []string{},
388 url: "http://foo.domain.com/articles",
389 },
390 {
391 route: new(Route).Path("/articles"),
392 vars: []string{},
393 url: "/articles",
394 },
395 {
396 route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
397 vars: []string{"category", "technology", "id", "42"},
398 url: "/articles/technology/42",
399 },
400 {
401 route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
402 vars: []string{"subdomain", "foo", "category", "technology", "id", "42"},
403 url: "http://foo.domain.com/articles/technology/42",
404 },
405 {
406 route: new(Route).Host("example.com").Schemes("https", "http"),
407 vars: []string{},
408 url: "https://example.com",
409 },
410 }
411
412 func TestHeaderMatcher(t *testing.T) {
413 for _, v := range headerMatcherTests {
414 request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
415 for key, value := range v.headers {
416 request.Header.Add(key, value)
417 }
418 var routeMatch RouteMatch
419 result := v.matcher.Match(request, &routeMatch)
420 if result != v.result {
421 if v.result {
422 t.Errorf("%#v: should match %v.", v.matcher, request.Header)
423 } else {
424 t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
425 }
426 }
427 }
428 }
429
430 func TestHostMatcher(t *testing.T) {
431 for _, v := range hostMatcherTests {
432 request, err := http.NewRequest("GET", v.url, nil)
433 if err != nil {
434 t.Errorf("http.NewRequest failed %#v", err)
435 continue
436 }
437 var routeMatch RouteMatch
438 result := v.matcher.Match(request, &routeMatch)
439 vars := routeMatch.Vars
440 if result != v.result {
441 if v.result {
442 t.Errorf("%#v: should match %v.", v.matcher, v.url)
443 } else {
444 t.Errorf("%#v: should not match %v.", v.matcher, v.url)
445 }
446 }
447 if result {
448 if len(vars) != len(v.vars) {
449 t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
450 }
451 for name, value := range vars {
452 if v.vars[name] != value {
453 t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
454 }
455 }
456 } else {
457 if len(vars) != 0 {
458 t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
459 }
460 }
461 }
462 }
463
464 func TestMethodMatcher(t *testing.T) {
465 for _, v := range methodMatcherTests {
466 request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
467 var routeMatch RouteMatch
468 result := v.matcher.Match(request, &routeMatch)
469 if result != v.result {
470 if v.result {
471 t.Errorf("%#v: should match %v.", v.matcher, v.method)
472 } else {
473 t.Errorf("%#v: should not match %v.", v.matcher, v.method)
474 }
475 }
476 }
477 }
478
479 func TestPathMatcher(t *testing.T) {
480 for _, v := range pathMatcherTests {
481 request, _ := http.NewRequest("GET", v.url, nil)
482 var routeMatch RouteMatch
483 result := v.matcher.Match(request, &routeMatch)
484 vars := routeMatch.Vars
485 if result != v.result {
486 if v.result {
487 t.Errorf("%#v: should match %v.", v.matcher, v.url)
488 } else {
489 t.Errorf("%#v: should not match %v.", v.matcher, v.url)
490 }
491 }
492 if result {
493 if len(vars) != len(v.vars) {
494 t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
495 }
496 for name, value := range vars {
497 if v.vars[name] != value {
498 t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
499 }
500 }
501 } else {
502 if len(vars) != 0 {
503 t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
504 }
505 }
506 }
507 }
508
509 func TestSchemeMatcher(t *testing.T) {
510 for _, v := range schemeMatcherTests {
511 request, _ := http.NewRequest("GET", v.url, nil)
512 var routeMatch RouteMatch
513 result := v.matcher.Match(request, &routeMatch)
514 if result != v.result {
515 if v.result {
516 t.Errorf("%#v: should match %v.", v.matcher, v.url)
517 } else {
518 t.Errorf("%#v: should not match %v.", v.matcher, v.url)
519 }
520 }
521 }
522 }
523
524 func TestUrlBuilding(t *testing.T) {
525
526 for _, v := range urlBuildingTests {
527 u, _ := v.route.URL(v.vars...)
528 url := u.String()
529 if url != v.url {
530 t.Errorf("expected %v, got %v", v.url, url)
531 }
532 }
533
534 ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
535 }
536
537 router := NewRouter()
538 router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
539
540 url, _ := router.Get("article").URL("category", "technology", "id", "42")
541 expected := "/articles/technology/42"
542 if url.String() != expected {
543 t.Errorf("Expected %v, got %v", expected, url.String())
544 }
545 }
546
547 func TestMatchedRouteName(t *testing.T) {
548 routeName := "stock"
549 router := NewRouter()
550 route := router.NewRoute().Path("/products/").Name(routeName)
551
552 url := "http://www.example.com/products/"
553 request, _ := http.NewRequest("GET", url, nil)
554 var rv RouteMatch
555 ok := router.Match(request, &rv)
556
557 if !ok || rv.Route != route {
558 t.Errorf("Expected same route, got %+v.", rv.Route)
559 }
560
561 retName := rv.Route.GetName()
562 if retName != routeName {
563 t.Errorf("Expected %q, got %q.", routeName, retName)
564 }
565 }
566
567 func TestSubRouting(t *testing.T) {
568
569 router := NewRouter()
570 subrouter := router.NewRoute().Host("www.example.com").Subrouter()
571 route := subrouter.NewRoute().Path("/products/").Name("products")
572
573 url := "http://www.example.com/products/"
574 request, _ := http.NewRequest("GET", url, nil)
575 var rv RouteMatch
576 ok := router.Match(request, &rv)
577
578 if !ok || rv.Route != route {
579 t.Errorf("Expected same route, got %+v.", rv.Route)
580 }
581
582 u, _ := router.Get("products").URL()
583 builtURL := u.String()
584
585 if builtURL != url {
586 t.Errorf("Expected %q, got %q.", url, builtURL)
587 }
588 }
589
590 func TestVariableNames(t *testing.T) {
591 route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
592 if route.err == nil {
593 t.Errorf("Expected error for duplicated variable names")
594 }
595 }
596
597 func TestRedirectSlash(t *testing.T) {
598 var route *Route
599 var routeMatch RouteMatch
600 r := NewRouter()
601
602 r.StrictSlash(false)
603 route = r.NewRoute()
604 if route.strictSlash != false {
605 t.Errorf("Expected false redirectSlash.")
606 }
607
608 r.StrictSlash(true)
609 route = r.NewRoute()
610 if route.strictSlash != true {
611 t.Errorf("Expected true redirectSlash.")
612 }
613
614 route = new(Route)
615 route.strictSlash = true
616 route.Path("/{arg1}/{arg2:[0-9]+}/")
617 request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
618 routeMatch = RouteMatch{}
619 _ = route.Match(request, &routeMatch)
620 vars := routeMatch.Vars
621 if vars["arg1"] != "foo" {
622 t.Errorf("Expected foo.")
623 }
624 if vars["arg2"] != "123" {
625 t.Errorf("Expected 123.")
626 }
627 rsp := NewRecorder()
628 routeMatch.Handler.ServeHTTP(rsp, request)
629 if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
630 t.Errorf("Expected redirect header.")
631 }
632
633 route = new(Route)
634 route.strictSlash = true
635 route.Path("/{arg1}/{arg2:[0-9]+}")
636 request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
637 routeMatch = RouteMatch{}
638 _ = route.Match(request, &routeMatch)
639 vars = routeMatch.Vars
640 if vars["arg1"] != "foo" {
641 t.Errorf("Expected foo.")
642 }
643 if vars["arg2"] != "123" {
644 t.Errorf("Expected 123.")
645 }
646 rsp = NewRecorder()
647 routeMatch.Handler.ServeHTTP(rsp, request)
648 if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
649 t.Errorf("Expected redirect header.")
650 }
651 }
652
653
654 func TestNewRegexp(t *testing.T) {
655 var p *routeRegexp
656 var matches []string
657
658 tests := map[string]map[string][]string{
659 "/{foo:a{2}}": {
660 "/a": nil,
661 "/aa": {"aa"},
662 "/aaa": nil,
663 "/aaaa": nil,
664 },
665 "/{foo:a{2,}}": {
666 "/a": nil,
667 "/aa": {"aa"},
668 "/aaa": {"aaa"},
669 "/aaaa": {"aaaa"},
670 },
671 "/{foo:a{2,3}}": {
672 "/a": nil,
673 "/aa": {"aa"},
674 "/aaa": {"aaa"},
675 "/aaaa": nil,
676 },
677 "/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
678 "/a": nil,
679 "/ab": nil,
680 "/abc": nil,
681 "/abcd": nil,
682 "/abc/ab": {"abc", "ab"},
683 "/abc/abc": nil,
684 "/abcd/ab": nil,
685 },
686 `/{foo:\w{3,}}/{bar:\d{2,}}`: {
687 "/a": nil,
688 "/ab": nil,
689 "/abc": nil,
690 "/abc/1": nil,
691 "/abc/12": {"abc", "12"},
692 "/abcd/12": {"abcd", "12"},
693 "/abcd/123": {"abcd", "123"},
694 },
695 }
696
697 for pattern, paths := range tests {
698 p, _ = newRouteRegexp(pattern, regexpTypePath, routeRegexpOptions{})
699 for path, result := range paths {
700 matches = p.regexp.FindStringSubmatch(path)
701 if result == nil {
702 if matches != nil {
703 t.Errorf("%v should not match %v.", pattern, path)
704 }
705 } else {
706 if len(matches) != len(result)+1 {
707 t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
708 } else {
709 for k, v := range result {
710 if matches[k+1] != v {
711 t.Errorf("Expected %v, got %v.", v, matches[k+1])
712 }
713 }
714 }
715 }
716 }
717 }
718 }
719
View as plain text