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