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]}"), 265 url: "http://a.b.c/", 266 vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, 267 result: false, 268 }, 269} 270 271type methodMatcherTest struct { 272 matcher methodMatcher 273 method string 274 result bool 275} 276 277var methodMatcherTests = []methodMatcherTest{ 278 { 279 matcher: methodMatcher([]string{"GET", "POST", "PUT"}), 280 method: "GET", 281 result: true, 282 }, 283 { 284 matcher: methodMatcher([]string{"GET", "POST", "PUT"}), 285 method: "POST", 286 result: true, 287 }, 288 { 289 matcher: methodMatcher([]string{"GET", "POST", "PUT"}), 290 method: "PUT", 291 result: true, 292 }, 293 { 294 matcher: methodMatcher([]string{"GET", "POST", "PUT"}), 295 method: "DELETE", 296 result: false, 297 }, 298} 299 300type pathMatcherTest struct { 301 matcher *Route 302 url string 303 vars map[string]string 304 result bool 305} 306 307var pathMatcherTests = []pathMatcherTest{ 308 { 309 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]}"), 310 url: "http://localhost:8080/123/456/789", 311 vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, 312 result: true, 313 }, 314 { 315 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]}"), 316 url: "http://localhost:8080/1/2/3", 317 vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, 318 result: false, 319 }, 320} 321 322type schemeMatcherTest struct { 323 matcher schemeMatcher 324 url string 325 result bool 326} 327 328var schemeMatcherTests = []schemeMatcherTest{ 329 { 330 matcher: schemeMatcher([]string{"http", "https"}), 331 url: "http://localhost:8080/", 332 result: true, 333 }, 334 { 335 matcher: schemeMatcher([]string{"http", "https"}), 336 url: "https://localhost:8080/", 337 result: true, 338 }, 339 { 340 matcher: schemeMatcher([]string{"https"}), 341 url: "http://localhost:8080/", 342 result: false, 343 }, 344 { 345 matcher: schemeMatcher([]string{"http"}), 346 url: "https://localhost:8080/", 347 result: false, 348 }, 349} 350 351type urlBuildingTest struct { 352 route *Route 353 vars []string 354 url string 355} 356 357var urlBuildingTests = []urlBuildingTest{ 358 { 359 route: new(Route).Host("foo.domain.com"), 360 vars: []string{}, 361 url: "http://foo.domain.com", 362 }, 363 { 364 route: new(Route).Host("{subdomain}.domain.com"), 365 vars: []string{"subdomain", "bar"}, 366 url: "http://bar.domain.com", 367 }, 368 { 369 route: new(Route).Host("foo.domain.com").Path("/articles"), 370 vars: []string{}, 371 url: "http://foo.domain.com/articles", 372 }, 373 { 374 route: new(Route).Path("/articles"), 375 vars: []string{}, 376 url: "/articles", 377 }, 378 { 379 route: new(Route).Path("/articles/{category}/{id:[0-9]+}"), 380 vars: []string{"category", "technology", "id", "42"}, 381 url: "/articles/technology/42", 382 }, 383 { 384 route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"), 385 vars: []string{"subdomain", "foo", "category", "technology", "id", "42"}, 386 url: "http://foo.domain.com/articles/technology/42", 387 }, 388} 389 390func TestHeaderMatcher(t *testing.T) { 391 for _, v := range headerMatcherTests { 392 request, _ := http.NewRequest("GET", "http://localhost:8080/", nil) 393 for key, value := range v.headers { 394 request.Header.Add(key, value) 395 } 396 var routeMatch RouteMatch 397 result := v.matcher.Match(request, &routeMatch) 398 if result != v.result { 399 if v.result { 400 t.Errorf("%#v: should match %v.", v.matcher, request.Header) 401 } else { 402 t.Errorf("%#v: should not match %v.", v.matcher, request.Header) 403 } 404 } 405 } 406} 407 408func TestHostMatcher(t *testing.T) { 409 for _, v := range hostMatcherTests { 410 request, _ := http.NewRequest("GET", v.url, nil) 411 var routeMatch RouteMatch 412 result := v.matcher.Match(request, &routeMatch) 413 vars := routeMatch.Vars 414 if result != v.result { 415 if v.result { 416 t.Errorf("%#v: should match %v.", v.matcher, v.url) 417 } else { 418 t.Errorf("%#v: should not match %v.", v.matcher, v.url) 419 } 420 } 421 if result { 422 if len(vars) != len(v.vars) { 423 t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) 424 } 425 for name, value := range vars { 426 if v.vars[name] != value { 427 t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) 428 } 429 } 430 } else { 431 if len(vars) != 0 { 432 t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) 433 } 434 } 435 } 436} 437 438func TestMethodMatcher(t *testing.T) { 439 for _, v := range methodMatcherTests { 440 request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil) 441 var routeMatch RouteMatch 442 result := v.matcher.Match(request, &routeMatch) 443 if result != v.result { 444 if v.result { 445 t.Errorf("%#v: should match %v.", v.matcher, v.method) 446 } else { 447 t.Errorf("%#v: should not match %v.", v.matcher, v.method) 448 } 449 } 450 } 451} 452 453func TestPathMatcher(t *testing.T) { 454 for _, v := range pathMatcherTests { 455 request, _ := http.NewRequest("GET", v.url, nil) 456 var routeMatch RouteMatch 457 result := v.matcher.Match(request, &routeMatch) 458 vars := routeMatch.Vars 459 if result != v.result { 460 if v.result { 461 t.Errorf("%#v: should match %v.", v.matcher, v.url) 462 } else { 463 t.Errorf("%#v: should not match %v.", v.matcher, v.url) 464 } 465 } 466 if result { 467 if len(vars) != len(v.vars) { 468 t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) 469 } 470 for name, value := range vars { 471 if v.vars[name] != value { 472 t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) 473 } 474 } 475 } else { 476 if len(vars) != 0 { 477 t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) 478 } 479 } 480 } 481} 482 483func TestSchemeMatcher(t *testing.T) { 484 for _, v := range schemeMatcherTests { 485 request, _ := http.NewRequest("GET", v.url, nil) 486 var routeMatch RouteMatch 487 result := v.matcher.Match(request, &routeMatch) 488 if result != v.result { 489 if v.result { 490 t.Errorf("%#v: should match %v.", v.matcher, v.url) 491 } else { 492 t.Errorf("%#v: should not match %v.", v.matcher, v.url) 493 } 494 } 495 } 496} 497 498func TestUrlBuilding(t *testing.T) { 499 500 for _, v := range urlBuildingTests { 501 u, _ := v.route.URL(v.vars...) 502 url := u.String() 503 if url != v.url { 504 t.Errorf("expected %v, got %v", v.url, url) 505 /* 506 reversePath := "" 507 reverseHost := "" 508 if v.route.pathTemplate != nil { 509 reversePath = v.route.pathTemplate.Reverse 510 } 511 if v.route.hostTemplate != nil { 512 reverseHost = v.route.hostTemplate.Reverse 513 } 514 515 t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost) 516 */ 517 } 518 } 519 520 ArticleHandler := func(w http.ResponseWriter, r *http.Request) { 521 } 522 523 router := NewRouter() 524 router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article") 525 526 url, _ := router.Get("article").URL("category", "technology", "id", "42") 527 expected := "/articles/technology/42" 528 if url.String() != expected { 529 t.Errorf("Expected %v, got %v", expected, url.String()) 530 } 531} 532 533func TestMatchedRouteName(t *testing.T) { 534 routeName := "stock" 535 router := NewRouter() 536 route := router.NewRoute().Path("/products/").Name(routeName) 537 538 url := "http://www.example.com/products/" 539 request, _ := http.NewRequest("GET", url, nil) 540 var rv RouteMatch 541 ok := router.Match(request, &rv) 542 543 if !ok || rv.Route != route { 544 t.Errorf("Expected same route, got %+v.", rv.Route) 545 } 546 547 retName := rv.Route.GetName() 548 if retName != routeName { 549 t.Errorf("Expected %q, got %q.", routeName, retName) 550 } 551} 552 553func TestSubRouting(t *testing.T) { 554 // Example from docs. 555 router := NewRouter() 556 subrouter := router.NewRoute().Host("www.example.com").Subrouter() 557 route := subrouter.NewRoute().Path("/products/").Name("products") 558 559 url := "http://www.example.com/products/" 560 request, _ := http.NewRequest("GET", url, nil) 561 var rv RouteMatch 562 ok := router.Match(request, &rv) 563 564 if !ok || rv.Route != route { 565 t.Errorf("Expected same route, got %+v.", rv.Route) 566 } 567 568 u, _ := router.Get("products").URL() 569 builtURL := u.String() 570 // Yay, subroute aware of the domain when building! 571 if builtURL != url { 572 t.Errorf("Expected %q, got %q.", url, builtURL) 573 } 574} 575 576func TestVariableNames(t *testing.T) { 577 route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}") 578 if route.err == nil { 579 t.Errorf("Expected error for duplicated variable names") 580 } 581} 582 583func TestRedirectSlash(t *testing.T) { 584 var route *Route 585 var routeMatch RouteMatch 586 r := NewRouter() 587 588 r.StrictSlash(false) 589 route = r.NewRoute() 590 if route.strictSlash != false { 591 t.Errorf("Expected false redirectSlash.") 592 } 593 594 r.StrictSlash(true) 595 route = r.NewRoute() 596 if route.strictSlash != true { 597 t.Errorf("Expected true redirectSlash.") 598 } 599 600 route = new(Route) 601 route.strictSlash = true 602 route.Path("/{arg1}/{arg2:[0-9]+}/") 603 request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil) 604 routeMatch = RouteMatch{} 605 _ = route.Match(request, &routeMatch) 606 vars := routeMatch.Vars 607 if vars["arg1"] != "foo" { 608 t.Errorf("Expected foo.") 609 } 610 if vars["arg2"] != "123" { 611 t.Errorf("Expected 123.") 612 } 613 rsp := NewRecorder() 614 routeMatch.Handler.ServeHTTP(rsp, request) 615 if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" { 616 t.Errorf("Expected redirect header.") 617 } 618 619 route = new(Route) 620 route.strictSlash = true 621 route.Path("/{arg1}/{arg2:[0-9]+}") 622 request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil) 623 routeMatch = RouteMatch{} 624 _ = route.Match(request, &routeMatch) 625 vars = routeMatch.Vars 626 if vars["arg1"] != "foo" { 627 t.Errorf("Expected foo.") 628 } 629 if vars["arg2"] != "123" { 630 t.Errorf("Expected 123.") 631 } 632 rsp = NewRecorder() 633 routeMatch.Handler.ServeHTTP(rsp, request) 634 if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" { 635 t.Errorf("Expected redirect header.") 636 } 637} 638 639// Test for the new regexp library, still not available in stable Go. 640func TestNewRegexp(t *testing.T) { 641 var p *routeRegexp 642 var matches []string 643 644 tests := map[string]map[string][]string{ 645 "/{foo:a{2}}": { 646 "/a": nil, 647 "/aa": {"aa"}, 648 "/aaa": nil, 649 "/aaaa": nil, 650 }, 651 "/{foo:a{2,}}": { 652 "/a": nil, 653 "/aa": {"aa"}, 654 "/aaa": {"aaa"}, 655 "/aaaa": {"aaaa"}, 656 }, 657 "/{foo:a{2,3}}": { 658 "/a": nil, 659 "/aa": {"aa"}, 660 "/aaa": {"aaa"}, 661 "/aaaa": nil, 662 }, 663 "/{foo:[a-z]{3}}/{bar:[a-z]{2}}": { 664 "/a": nil, 665 "/ab": nil, 666 "/abc": nil, 667 "/abcd": nil, 668 "/abc/ab": {"abc", "ab"}, 669 "/abc/abc": nil, 670 "/abcd/ab": nil, 671 }, 672 `/{foo:\w{3,}}/{bar:\d{2,}}`: { 673 "/a": nil, 674 "/ab": nil, 675 "/abc": nil, 676 "/abc/1": nil, 677 "/abc/12": {"abc", "12"}, 678 "/abcd/12": {"abcd", "12"}, 679 "/abcd/123": {"abcd", "123"}, 680 }, 681 } 682 683 for pattern, paths := range tests { 684 p, _ = newRouteRegexp(pattern, regexpTypePath, routeRegexpOptions{}) 685 for path, result := range paths { 686 matches = p.regexp.FindStringSubmatch(path) 687 if result == nil { 688 if matches != nil { 689 t.Errorf("%v should not match %v.", pattern, path) 690 } 691 } else { 692 if len(matches) != len(result)+1 { 693 t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches)) 694 } else { 695 for k, v := range result { 696 if matches[k+1] != v { 697 t.Errorf("Expected %v, got %v.", v, matches[k+1]) 698 } 699 } 700 } 701 } 702 } 703 } 704} 705