1// Old tests ported to Go1. This is a mess. Want to drop it one day. 2 3// Copyright 2011 Gorilla Authors. All rights reserved. 4// Use of this source code is governed by a BSD-style 5// license that can be found in the LICENSE file. 6 7package mux 8 9import ( 10 "bytes" 11 "net/http" 12 "testing" 13) 14 15// ---------------------------------------------------------------------------- 16// ResponseRecorder 17// ---------------------------------------------------------------------------- 18// Copyright 2009 The Go Authors. All rights reserved. 19// Use of this source code is governed by a BSD-style 20// license that can be found in the LICENSE file. 21 22// ResponseRecorder is an implementation of http.ResponseWriter that 23// records its mutations for later inspection in tests. 24type ResponseRecorder struct { 25 Code int // the HTTP response code from WriteHeader 26 HeaderMap http.Header // the HTTP response headers 27 Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to 28 Flushed bool 29} 30 31// NewRecorder returns an initialized ResponseRecorder. 32func NewRecorder() *ResponseRecorder { 33 return &ResponseRecorder{ 34 HeaderMap: make(http.Header), 35 Body: new(bytes.Buffer), 36 } 37} 38 39// Header returns the response headers. 40func (rw *ResponseRecorder) Header() http.Header { 41 return rw.HeaderMap 42} 43 44// Write always succeeds and writes to rw.Body, if not nil. 45func (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// WriteHeader sets rw.Code. 56func (rw *ResponseRecorder) WriteHeader(code int) { 57 rw.Code = code 58} 59 60// Flush sets rw.Flushed to true. 61func (rw *ResponseRecorder) Flush() { 62 rw.Flushed = true 63} 64 65// ---------------------------------------------------------------------------- 66 67func 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 // Everything match. 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 // Everything match. 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 // 1st route -------------------------------------------------------------- 146 147 // Everything match. 148 reset() 149 match(true) 150 151 // Scheme doesn't match. 152 reset() 153 scheme = "http" 154 match(false) 155 156 // Host doesn't match. 157 reset() 158 host = "www.mygoogle.com" 159 match(false) 160 161 // Path doesn't match. 162 reset() 163 path = "/product/notdigits" 164 match(false) 165 166 // Query doesn't match. 167 reset() 168 query = "?foo=baz" 169 match(false) 170 171 // Method doesn't match. 172 reset() 173 method = "POST" 174 match(false) 175 176 // Header doesn't match. 177 reset() 178 headers = map[string]string{} 179 match(false) 180 181 // Everything match, again. 182 reset() 183 match(true) 184 185 // 2nd route -------------------------------------------------------------- 186 // Everything match. 187 reset2() 188 match(true) 189 190 // Scheme doesn't match. 191 reset2() 192 scheme = "https" 193 match(false) 194 195 // Host doesn't match. 196 reset2() 197 host = "sub.google.com" 198 match(false) 199 200 // Path doesn't match. 201 reset2() 202 path = "/bar/product/42" 203 match(false) 204 205 // Query doesn't match. 206 reset2() 207 query = "?foo=baz" 208 match(false) 209 210 // Method doesn't match. 211 reset2() 212 method = "GET" 213 match(false) 214 215 // Header doesn't match. 216 reset2() 217 headers = map[string]string{} 218 match(false) 219 220 // Everything match, again. 221 reset2() 222 match(true) 223} 224 225type headerMatcherTest struct { 226 matcher headerMatcher 227 headers map[string]string 228 result bool 229} 230 231var 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 249type hostMatcherTest struct { 250 matcher *Route 251 url string 252 vars map[string]string 253 result bool 254} 255 256var 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 283type methodMatcherTest struct { 284 matcher methodMatcher 285 method string 286 result bool 287} 288 289var 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 312type pathMatcherTest struct { 313 matcher *Route 314 url string 315 vars map[string]string 316 result bool 317} 318 319var 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 334type schemeMatcherTest struct { 335 matcher schemeMatcher 336 url string 337 result bool 338} 339 340var 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 363type urlBuildingTest struct { 364 route *Route 365 vars []string 366 url string 367} 368 369var 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 412func 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 430func 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 464func 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 479func 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 509func 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 524func 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 547func 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 567func TestSubRouting(t *testing.T) { 568 // Example from docs. 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 // Yay, subroute aware of the domain when building! 585 if builtURL != url { 586 t.Errorf("Expected %q, got %q.", url, builtURL) 587 } 588} 589 590func 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 597func 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// Test for the new regexp library, still not available in stable Go. 654func 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