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