github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/mux/mux_test.go (about)

     1  // Copyright 2012 The Gorilla Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package mux
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"net/url"
    15  	"reflect"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	http "github.com/hxx258456/ccgo/gmhttp"
    21  	"github.com/hxx258456/ccgo/gmhttp/httptest"
    22  )
    23  
    24  func (r *Route) GoString() string {
    25  	matchers := make([]string, len(r.matchers))
    26  	for i, m := range r.matchers {
    27  		matchers[i] = fmt.Sprintf("%#v", m)
    28  	}
    29  	return fmt.Sprintf("&Route{matchers:[]matcher{%s}}", strings.Join(matchers, ", "))
    30  }
    31  
    32  func (r *routeRegexp) GoString() string {
    33  	return fmt.Sprintf("&routeRegexp{template: %q, regexpType: %v, options: %v, regexp: regexp.MustCompile(%q), reverse: %q, varsN: %v, varsR: %v", r.template, r.regexpType, r.options, r.regexp.String(), r.reverse, r.varsN, r.varsR)
    34  }
    35  
    36  type routeTest struct {
    37  	title           string            // title of the test
    38  	route           *Route            // the route being tested
    39  	request         *http.Request     // a request to test the route
    40  	vars            map[string]string // the expected vars of the match
    41  	scheme          string            // the expected scheme of the built URL
    42  	host            string            // the expected host of the built URL
    43  	path            string            // the expected path of the built URL
    44  	query           string            // the expected query string of the built URL
    45  	pathTemplate    string            // the expected path template of the route
    46  	hostTemplate    string            // the expected host template of the route
    47  	queriesTemplate string            // the expected query template of the route
    48  	methods         []string          // the expected route methods
    49  	pathRegexp      string            // the expected path regexp
    50  	queriesRegexp   string            // the expected query regexp
    51  	shouldMatch     bool              // whether the request is expected to match the route at all
    52  	shouldRedirect  bool              // whether the request should result in a redirect
    53  }
    54  
    55  func TestHost(t *testing.T) {
    56  
    57  	tests := []routeTest{
    58  		{
    59  			title:       "Host route match",
    60  			route:       new(Route).Host("aaa.bbb.ccc"),
    61  			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
    62  			vars:        map[string]string{},
    63  			host:        "aaa.bbb.ccc",
    64  			path:        "",
    65  			shouldMatch: true,
    66  		},
    67  		{
    68  			title:       "Host route, wrong host in request URL",
    69  			route:       new(Route).Host("aaa.bbb.ccc"),
    70  			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
    71  			vars:        map[string]string{},
    72  			host:        "aaa.bbb.ccc",
    73  			path:        "",
    74  			shouldMatch: false,
    75  		},
    76  		{
    77  			title:       "Host route with port, match",
    78  			route:       new(Route).Host("aaa.bbb.ccc:1234"),
    79  			request:     newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
    80  			vars:        map[string]string{},
    81  			host:        "aaa.bbb.ccc:1234",
    82  			path:        "",
    83  			shouldMatch: true,
    84  		},
    85  		{
    86  			title:       "Host route with port, wrong port in request URL",
    87  			route:       new(Route).Host("aaa.bbb.ccc:1234"),
    88  			request:     newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
    89  			vars:        map[string]string{},
    90  			host:        "aaa.bbb.ccc:1234",
    91  			path:        "",
    92  			shouldMatch: false,
    93  		},
    94  		{
    95  			title:       "Host route, match with host in request header",
    96  			route:       new(Route).Host("aaa.bbb.ccc"),
    97  			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
    98  			vars:        map[string]string{},
    99  			host:        "aaa.bbb.ccc",
   100  			path:        "",
   101  			shouldMatch: true,
   102  		},
   103  		{
   104  			title:       "Host route, wrong host in request header",
   105  			route:       new(Route).Host("aaa.bbb.ccc"),
   106  			request:     newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
   107  			vars:        map[string]string{},
   108  			host:        "aaa.bbb.ccc",
   109  			path:        "",
   110  			shouldMatch: false,
   111  		},
   112  		{
   113  			title:       "Host route with port, match with request header",
   114  			route:       new(Route).Host("aaa.bbb.ccc:1234"),
   115  			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"),
   116  			vars:        map[string]string{},
   117  			host:        "aaa.bbb.ccc:1234",
   118  			path:        "",
   119  			shouldMatch: true,
   120  		},
   121  		{
   122  			title:       "Host route with port, wrong host in request header",
   123  			route:       new(Route).Host("aaa.bbb.ccc:1234"),
   124  			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
   125  			vars:        map[string]string{},
   126  			host:        "aaa.bbb.ccc:1234",
   127  			path:        "",
   128  			shouldMatch: false,
   129  		},
   130  		{
   131  			title:        "Host route with pattern, match with request header",
   132  			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc:1{v2:(?:23|4)}"),
   133  			request:      newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:123"),
   134  			vars:         map[string]string{"v1": "bbb", "v2": "23"},
   135  			host:         "aaa.bbb.ccc:123",
   136  			path:         "",
   137  			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc:1{v2:(?:23|4)}`,
   138  			shouldMatch:  true,
   139  		},
   140  		{
   141  			title:        "Host route with pattern, match",
   142  			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
   143  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   144  			vars:         map[string]string{"v1": "bbb"},
   145  			host:         "aaa.bbb.ccc",
   146  			path:         "",
   147  			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
   148  			shouldMatch:  true,
   149  		},
   150  		{
   151  			title:        "Host route with pattern, additional capturing group, match",
   152  			route:        new(Route).Host("aaa.{v1:[a-z]{2}(?:b|c)}.ccc"),
   153  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   154  			vars:         map[string]string{"v1": "bbb"},
   155  			host:         "aaa.bbb.ccc",
   156  			path:         "",
   157  			hostTemplate: `aaa.{v1:[a-z]{2}(?:b|c)}.ccc`,
   158  			shouldMatch:  true,
   159  		},
   160  		{
   161  			title:        "Host route with pattern, wrong host in request URL",
   162  			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
   163  			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
   164  			vars:         map[string]string{"v1": "bbb"},
   165  			host:         "aaa.bbb.ccc",
   166  			path:         "",
   167  			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
   168  			shouldMatch:  false,
   169  		},
   170  		{
   171  			title:        "Host route with multiple patterns, match",
   172  			route:        new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
   173  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   174  			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
   175  			host:         "aaa.bbb.ccc",
   176  			path:         "",
   177  			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
   178  			shouldMatch:  true,
   179  		},
   180  		{
   181  			title:        "Host route with multiple patterns, wrong host in request URL",
   182  			route:        new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
   183  			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
   184  			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
   185  			host:         "aaa.bbb.ccc",
   186  			path:         "",
   187  			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
   188  			shouldMatch:  false,
   189  		},
   190  		{
   191  			title:        "Host route with hyphenated name and pattern, match",
   192  			route:        new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"),
   193  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   194  			vars:         map[string]string{"v-1": "bbb"},
   195  			host:         "aaa.bbb.ccc",
   196  			path:         "",
   197  			hostTemplate: `aaa.{v-1:[a-z]{3}}.ccc`,
   198  			shouldMatch:  true,
   199  		},
   200  		{
   201  			title:        "Host route with hyphenated name and pattern, additional capturing group, match",
   202  			route:        new(Route).Host("aaa.{v-1:[a-z]{2}(?:b|c)}.ccc"),
   203  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   204  			vars:         map[string]string{"v-1": "bbb"},
   205  			host:         "aaa.bbb.ccc",
   206  			path:         "",
   207  			hostTemplate: `aaa.{v-1:[a-z]{2}(?:b|c)}.ccc`,
   208  			shouldMatch:  true,
   209  		},
   210  		{
   211  			title:        "Host route with multiple hyphenated names and patterns, match",
   212  			route:        new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"),
   213  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   214  			vars:         map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"},
   215  			host:         "aaa.bbb.ccc",
   216  			path:         "",
   217  			hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,
   218  			shouldMatch:  true,
   219  		},
   220  	}
   221  	for _, test := range tests {
   222  		t.Run(test.title, func(t *testing.T) {
   223  			testRoute(t, test)
   224  			testTemplate(t, test)
   225  		})
   226  	}
   227  }
   228  
   229  func TestPath(t *testing.T) {
   230  	tests := []routeTest{
   231  		{
   232  			title:       "Path route, match",
   233  			route:       new(Route).Path("/111/222/333"),
   234  			request:     newRequest("GET", "http://localhost/111/222/333"),
   235  			vars:        map[string]string{},
   236  			host:        "",
   237  			path:        "/111/222/333",
   238  			shouldMatch: true,
   239  		},
   240  		{
   241  			title:       "Path route, match with trailing slash in request and path",
   242  			route:       new(Route).Path("/111/"),
   243  			request:     newRequest("GET", "http://localhost/111/"),
   244  			vars:        map[string]string{},
   245  			host:        "",
   246  			path:        "/111/",
   247  			shouldMatch: true,
   248  		},
   249  		{
   250  			title:        "Path route, do not match with trailing slash in path",
   251  			route:        new(Route).Path("/111/"),
   252  			request:      newRequest("GET", "http://localhost/111"),
   253  			vars:         map[string]string{},
   254  			host:         "",
   255  			path:         "/111",
   256  			pathTemplate: `/111/`,
   257  			pathRegexp:   `^/111/$`,
   258  			shouldMatch:  false,
   259  		},
   260  		{
   261  			title:        "Path route, do not match with trailing slash in request",
   262  			route:        new(Route).Path("/111"),
   263  			request:      newRequest("GET", "http://localhost/111/"),
   264  			vars:         map[string]string{},
   265  			host:         "",
   266  			path:         "/111/",
   267  			pathTemplate: `/111`,
   268  			shouldMatch:  false,
   269  		},
   270  		{
   271  			title:        "Path route, match root with no host",
   272  			route:        new(Route).Path("/"),
   273  			request:      newRequest("GET", "/"),
   274  			vars:         map[string]string{},
   275  			host:         "",
   276  			path:         "/",
   277  			pathTemplate: `/`,
   278  			pathRegexp:   `^/$`,
   279  			shouldMatch:  true,
   280  		},
   281  		{
   282  			title: "Path route, match root with no host, App Engine format",
   283  			route: new(Route).Path("/"),
   284  			request: func() *http.Request {
   285  				r := newRequest("GET", "http://localhost/")
   286  				r.RequestURI = "/"
   287  				return r
   288  			}(),
   289  			vars:         map[string]string{},
   290  			host:         "",
   291  			path:         "/",
   292  			pathTemplate: `/`,
   293  			shouldMatch:  true,
   294  		},
   295  		{
   296  			title:       "Path route, wrong path in request in request URL",
   297  			route:       new(Route).Path("/111/222/333"),
   298  			request:     newRequest("GET", "http://localhost/1/2/3"),
   299  			vars:        map[string]string{},
   300  			host:        "",
   301  			path:        "/111/222/333",
   302  			shouldMatch: false,
   303  		},
   304  		{
   305  			title:        "Path route with pattern, match",
   306  			route:        new(Route).Path("/111/{v1:[0-9]{3}}/333"),
   307  			request:      newRequest("GET", "http://localhost/111/222/333"),
   308  			vars:         map[string]string{"v1": "222"},
   309  			host:         "",
   310  			path:         "/111/222/333",
   311  			pathTemplate: `/111/{v1:[0-9]{3}}/333`,
   312  			shouldMatch:  true,
   313  		},
   314  		{
   315  			title:        "Path route with pattern, URL in request does not match",
   316  			route:        new(Route).Path("/111/{v1:[0-9]{3}}/333"),
   317  			request:      newRequest("GET", "http://localhost/111/aaa/333"),
   318  			vars:         map[string]string{"v1": "222"},
   319  			host:         "",
   320  			path:         "/111/222/333",
   321  			pathTemplate: `/111/{v1:[0-9]{3}}/333`,
   322  			pathRegexp:   `^/111/(?P<v0>[0-9]{3})/333$`,
   323  			shouldMatch:  false,
   324  		},
   325  		{
   326  			title:        "Path route with multiple patterns, match",
   327  			route:        new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
   328  			request:      newRequest("GET", "http://localhost/111/222/333"),
   329  			vars:         map[string]string{"v1": "111", "v2": "222", "v3": "333"},
   330  			host:         "",
   331  			path:         "/111/222/333",
   332  			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
   333  			pathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
   334  			shouldMatch:  true,
   335  		},
   336  		{
   337  			title:        "Path route with multiple patterns, URL in request does not match",
   338  			route:        new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
   339  			request:      newRequest("GET", "http://localhost/111/aaa/333"),
   340  			vars:         map[string]string{"v1": "111", "v2": "222", "v3": "333"},
   341  			host:         "",
   342  			path:         "/111/222/333",
   343  			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
   344  			pathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
   345  			shouldMatch:  false,
   346  		},
   347  		{
   348  			title:        "Path route with multiple patterns with pipe, match",
   349  			route:        new(Route).Path("/{category:a|(?:b/c)}/{product}/{id:[0-9]+}"),
   350  			request:      newRequest("GET", "http://localhost/a/product_name/1"),
   351  			vars:         map[string]string{"category": "a", "product": "product_name", "id": "1"},
   352  			host:         "",
   353  			path:         "/a/product_name/1",
   354  			pathTemplate: `/{category:a|(?:b/c)}/{product}/{id:[0-9]+}`,
   355  			pathRegexp:   `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,
   356  			shouldMatch:  true,
   357  		},
   358  		{
   359  			title:        "Path route with hyphenated name and pattern, match",
   360  			route:        new(Route).Path("/111/{v-1:[0-9]{3}}/333"),
   361  			request:      newRequest("GET", "http://localhost/111/222/333"),
   362  			vars:         map[string]string{"v-1": "222"},
   363  			host:         "",
   364  			path:         "/111/222/333",
   365  			pathTemplate: `/111/{v-1:[0-9]{3}}/333`,
   366  			pathRegexp:   `^/111/(?P<v0>[0-9]{3})/333$`,
   367  			shouldMatch:  true,
   368  		},
   369  		{
   370  			title:        "Path route with multiple hyphenated names and patterns, match",
   371  			route:        new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"),
   372  			request:      newRequest("GET", "http://localhost/111/222/333"),
   373  			vars:         map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"},
   374  			host:         "",
   375  			path:         "/111/222/333",
   376  			pathTemplate: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`,
   377  			pathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
   378  			shouldMatch:  true,
   379  		},
   380  		{
   381  			title:        "Path route with multiple hyphenated names and patterns with pipe, match",
   382  			route:        new(Route).Path("/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}"),
   383  			request:      newRequest("GET", "http://localhost/a/product_name/1"),
   384  			vars:         map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"},
   385  			host:         "",
   386  			path:         "/a/product_name/1",
   387  			pathTemplate: `/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}`,
   388  			pathRegexp:   `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,
   389  			shouldMatch:  true,
   390  		},
   391  		{
   392  			title:        "Path route with multiple hyphenated names and patterns with pipe and case insensitive, match",
   393  			route:        new(Route).Path("/{type:(?i:daily|mini|variety)}-{date:\\d{4,4}-\\d{2,2}-\\d{2,2}}"),
   394  			request:      newRequest("GET", "http://localhost/daily-2016-01-01"),
   395  			vars:         map[string]string{"type": "daily", "date": "2016-01-01"},
   396  			host:         "",
   397  			path:         "/daily-2016-01-01",
   398  			pathTemplate: `/{type:(?i:daily|mini|variety)}-{date:\d{4,4}-\d{2,2}-\d{2,2}}`,
   399  			pathRegexp:   `^/(?P<v0>(?i:daily|mini|variety))-(?P<v1>\d{4,4}-\d{2,2}-\d{2,2})$`,
   400  			shouldMatch:  true,
   401  		},
   402  		{
   403  			title:        "Path route with empty match right after other match",
   404  			route:        new(Route).Path(`/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`),
   405  			request:      newRequest("GET", "http://localhost/111/222"),
   406  			vars:         map[string]string{"v1": "111", "v2": "", "v3": "222"},
   407  			host:         "",
   408  			path:         "/111/222",
   409  			pathTemplate: `/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`,
   410  			pathRegexp:   `^/(?P<v0>[0-9]*)(?P<v1>[a-z]*)/(?P<v2>[0-9]*)$`,
   411  			shouldMatch:  true,
   412  		},
   413  		{
   414  			title:        "Path route with single pattern with pipe, match",
   415  			route:        new(Route).Path("/{category:a|b/c}"),
   416  			request:      newRequest("GET", "http://localhost/a"),
   417  			vars:         map[string]string{"category": "a"},
   418  			host:         "",
   419  			path:         "/a",
   420  			pathTemplate: `/{category:a|b/c}`,
   421  			shouldMatch:  true,
   422  		},
   423  		{
   424  			title:        "Path route with single pattern with pipe, match",
   425  			route:        new(Route).Path("/{category:a|b/c}"),
   426  			request:      newRequest("GET", "http://localhost/b/c"),
   427  			vars:         map[string]string{"category": "b/c"},
   428  			host:         "",
   429  			path:         "/b/c",
   430  			pathTemplate: `/{category:a|b/c}`,
   431  			shouldMatch:  true,
   432  		},
   433  		{
   434  			title:        "Path route with multiple patterns with pipe, match",
   435  			route:        new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
   436  			request:      newRequest("GET", "http://localhost/a/product_name/1"),
   437  			vars:         map[string]string{"category": "a", "product": "product_name", "id": "1"},
   438  			host:         "",
   439  			path:         "/a/product_name/1",
   440  			pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
   441  			shouldMatch:  true,
   442  		},
   443  		{
   444  			title:        "Path route with multiple patterns with pipe, match",
   445  			route:        new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
   446  			request:      newRequest("GET", "http://localhost/b/c/product_name/1"),
   447  			vars:         map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
   448  			host:         "",
   449  			path:         "/b/c/product_name/1",
   450  			pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
   451  			shouldMatch:  true,
   452  		},
   453  	}
   454  
   455  	for _, test := range tests {
   456  		t.Run(test.title, func(t *testing.T) {
   457  			testRoute(t, test)
   458  			testTemplate(t, test)
   459  			testUseEscapedRoute(t, test)
   460  			testRegexp(t, test)
   461  		})
   462  	}
   463  }
   464  
   465  func TestPathPrefix(t *testing.T) {
   466  	tests := []routeTest{
   467  		{
   468  			title:       "PathPrefix route, match",
   469  			route:       new(Route).PathPrefix("/111"),
   470  			request:     newRequest("GET", "http://localhost/111/222/333"),
   471  			vars:        map[string]string{},
   472  			host:        "",
   473  			path:        "/111",
   474  			shouldMatch: true,
   475  		},
   476  		{
   477  			title:       "PathPrefix route, match substring",
   478  			route:       new(Route).PathPrefix("/1"),
   479  			request:     newRequest("GET", "http://localhost/111/222/333"),
   480  			vars:        map[string]string{},
   481  			host:        "",
   482  			path:        "/1",
   483  			shouldMatch: true,
   484  		},
   485  		{
   486  			title:       "PathPrefix route, URL prefix in request does not match",
   487  			route:       new(Route).PathPrefix("/111"),
   488  			request:     newRequest("GET", "http://localhost/1/2/3"),
   489  			vars:        map[string]string{},
   490  			host:        "",
   491  			path:        "/111",
   492  			shouldMatch: false,
   493  		},
   494  		{
   495  			title:        "PathPrefix route with pattern, match",
   496  			route:        new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
   497  			request:      newRequest("GET", "http://localhost/111/222/333"),
   498  			vars:         map[string]string{"v1": "222"},
   499  			host:         "",
   500  			path:         "/111/222",
   501  			pathTemplate: `/111/{v1:[0-9]{3}}`,
   502  			shouldMatch:  true,
   503  		},
   504  		{
   505  			title:        "PathPrefix route with pattern, URL prefix in request does not match",
   506  			route:        new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
   507  			request:      newRequest("GET", "http://localhost/111/aaa/333"),
   508  			vars:         map[string]string{"v1": "222"},
   509  			host:         "",
   510  			path:         "/111/222",
   511  			pathTemplate: `/111/{v1:[0-9]{3}}`,
   512  			shouldMatch:  false,
   513  		},
   514  		{
   515  			title:        "PathPrefix route with multiple patterns, match",
   516  			route:        new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
   517  			request:      newRequest("GET", "http://localhost/111/222/333"),
   518  			vars:         map[string]string{"v1": "111", "v2": "222"},
   519  			host:         "",
   520  			path:         "/111/222",
   521  			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
   522  			shouldMatch:  true,
   523  		},
   524  		{
   525  			title:        "PathPrefix route with multiple patterns, URL prefix in request does not match",
   526  			route:        new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
   527  			request:      newRequest("GET", "http://localhost/111/aaa/333"),
   528  			vars:         map[string]string{"v1": "111", "v2": "222"},
   529  			host:         "",
   530  			path:         "/111/222",
   531  			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
   532  			shouldMatch:  false,
   533  		},
   534  	}
   535  
   536  	for _, test := range tests {
   537  		t.Run(test.title, func(t *testing.T) {
   538  			testRoute(t, test)
   539  			testTemplate(t, test)
   540  			testUseEscapedRoute(t, test)
   541  		})
   542  	}
   543  }
   544  
   545  func TestSchemeHostPath(t *testing.T) {
   546  	tests := []routeTest{
   547  		{
   548  			title:        "Host and Path route, match",
   549  			route:        new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
   550  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   551  			vars:         map[string]string{},
   552  			scheme:       "http",
   553  			host:         "aaa.bbb.ccc",
   554  			path:         "/111/222/333",
   555  			pathTemplate: `/111/222/333`,
   556  			hostTemplate: `aaa.bbb.ccc`,
   557  			shouldMatch:  true,
   558  		},
   559  		{
   560  			title:        "Scheme, Host, and Path route, match",
   561  			route:        new(Route).Schemes("https").Host("aaa.bbb.ccc").Path("/111/222/333"),
   562  			request:      newRequest("GET", "https://aaa.bbb.ccc/111/222/333"),
   563  			vars:         map[string]string{},
   564  			scheme:       "https",
   565  			host:         "aaa.bbb.ccc",
   566  			path:         "/111/222/333",
   567  			pathTemplate: `/111/222/333`,
   568  			hostTemplate: `aaa.bbb.ccc`,
   569  			shouldMatch:  true,
   570  		},
   571  		{
   572  			title:        "Host and Path route, wrong host in request URL",
   573  			route:        new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
   574  			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
   575  			vars:         map[string]string{},
   576  			scheme:       "http",
   577  			host:         "aaa.bbb.ccc",
   578  			path:         "/111/222/333",
   579  			pathTemplate: `/111/222/333`,
   580  			hostTemplate: `aaa.bbb.ccc`,
   581  			shouldMatch:  false,
   582  		},
   583  		{
   584  			title:        "Host and Path route with pattern, match",
   585  			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
   586  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   587  			vars:         map[string]string{"v1": "bbb", "v2": "222"},
   588  			scheme:       "http",
   589  			host:         "aaa.bbb.ccc",
   590  			path:         "/111/222/333",
   591  			pathTemplate: `/111/{v2:[0-9]{3}}/333`,
   592  			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
   593  			shouldMatch:  true,
   594  		},
   595  		{
   596  			title:        "Scheme, Host, and Path route with host and path patterns, match",
   597  			route:        new(Route).Schemes("ftp", "ssss").Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
   598  			request:      newRequest("GET", "ssss://aaa.bbb.ccc/111/222/333"),
   599  			vars:         map[string]string{"v1": "bbb", "v2": "222"},
   600  			scheme:       "ftp",
   601  			host:         "aaa.bbb.ccc",
   602  			path:         "/111/222/333",
   603  			pathTemplate: `/111/{v2:[0-9]{3}}/333`,
   604  			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
   605  			shouldMatch:  true,
   606  		},
   607  		{
   608  			title:        "Host and Path route with pattern, URL in request does not match",
   609  			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
   610  			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
   611  			vars:         map[string]string{"v1": "bbb", "v2": "222"},
   612  			scheme:       "http",
   613  			host:         "aaa.bbb.ccc",
   614  			path:         "/111/222/333",
   615  			pathTemplate: `/111/{v2:[0-9]{3}}/333`,
   616  			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
   617  			shouldMatch:  false,
   618  		},
   619  		{
   620  			title:        "Host and Path route with multiple patterns, match",
   621  			route:        new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
   622  			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
   623  			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
   624  			scheme:       "http",
   625  			host:         "aaa.bbb.ccc",
   626  			path:         "/111/222/333",
   627  			pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
   628  			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
   629  			shouldMatch:  true,
   630  		},
   631  		{
   632  			title:        "Host and Path route with multiple patterns, URL in request does not match",
   633  			route:        new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
   634  			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
   635  			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
   636  			scheme:       "http",
   637  			host:         "aaa.bbb.ccc",
   638  			path:         "/111/222/333",
   639  			pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
   640  			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
   641  			shouldMatch:  false,
   642  		},
   643  	}
   644  
   645  	for _, test := range tests {
   646  		t.Run(test.title, func(t *testing.T) {
   647  			testRoute(t, test)
   648  			testTemplate(t, test)
   649  			testUseEscapedRoute(t, test)
   650  		})
   651  	}
   652  }
   653  
   654  func TestHeaders(t *testing.T) {
   655  	// newRequestHeaders creates a new request with a method, url, and headers
   656  	newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
   657  		req, err := http.NewRequest(method, url, nil)
   658  		if err != nil {
   659  			panic(err)
   660  		}
   661  		for k, v := range headers {
   662  			req.Header.Add(k, v)
   663  		}
   664  		return req
   665  	}
   666  
   667  	tests := []routeTest{
   668  		{
   669  			title:       "Headers route, match",
   670  			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
   671  			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
   672  			vars:        map[string]string{},
   673  			host:        "",
   674  			path:        "",
   675  			shouldMatch: true,
   676  		},
   677  		{
   678  			title:       "Headers route, bad header values",
   679  			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
   680  			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
   681  			vars:        map[string]string{},
   682  			host:        "",
   683  			path:        "",
   684  			shouldMatch: false,
   685  		},
   686  		{
   687  			title:       "Headers route, regex header values to match",
   688  			route:       new(Route).HeadersRegexp("foo", "ba[zr]"),
   689  			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baw"}),
   690  			vars:        map[string]string{},
   691  			host:        "",
   692  			path:        "",
   693  			shouldMatch: false,
   694  		},
   695  		{
   696  			title:       "Headers route, regex header values to match",
   697  			route:       new(Route).HeadersRegexp("foo", "ba[zr]"),
   698  			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baz"}),
   699  			vars:        map[string]string{},
   700  			host:        "",
   701  			path:        "",
   702  			shouldMatch: true,
   703  		},
   704  	}
   705  
   706  	for _, test := range tests {
   707  		t.Run(test.title, func(t *testing.T) {
   708  			testRoute(t, test)
   709  			testTemplate(t, test)
   710  		})
   711  	}
   712  }
   713  
   714  func TestMethods(t *testing.T) {
   715  	tests := []routeTest{
   716  		{
   717  			title:       "Methods route, match GET",
   718  			route:       new(Route).Methods("GET", "POST"),
   719  			request:     newRequest("GET", "http://localhost"),
   720  			vars:        map[string]string{},
   721  			host:        "",
   722  			path:        "",
   723  			methods:     []string{"GET", "POST"},
   724  			shouldMatch: true,
   725  		},
   726  		{
   727  			title:       "Methods route, match POST",
   728  			route:       new(Route).Methods("GET", "POST"),
   729  			request:     newRequest("POST", "http://localhost"),
   730  			vars:        map[string]string{},
   731  			host:        "",
   732  			path:        "",
   733  			methods:     []string{"GET", "POST"},
   734  			shouldMatch: true,
   735  		},
   736  		{
   737  			title:       "Methods route, bad method",
   738  			route:       new(Route).Methods("GET", "POST"),
   739  			request:     newRequest("PUT", "http://localhost"),
   740  			vars:        map[string]string{},
   741  			host:        "",
   742  			path:        "",
   743  			methods:     []string{"GET", "POST"},
   744  			shouldMatch: false,
   745  		},
   746  		{
   747  			title:       "Route without methods",
   748  			route:       new(Route),
   749  			request:     newRequest("PUT", "http://localhost"),
   750  			vars:        map[string]string{},
   751  			host:        "",
   752  			path:        "",
   753  			methods:     []string{},
   754  			shouldMatch: true,
   755  		},
   756  	}
   757  
   758  	for _, test := range tests {
   759  		t.Run(test.title, func(t *testing.T) {
   760  			testRoute(t, test)
   761  			testTemplate(t, test)
   762  			testMethods(t, test)
   763  		})
   764  	}
   765  }
   766  
   767  func TestQueries(t *testing.T) {
   768  	tests := []routeTest{
   769  		{
   770  			title:           "Queries route, match",
   771  			route:           new(Route).Queries("foo", "bar", "baz", "ding"),
   772  			request:         newRequest("GET", "http://localhost?foo=bar&baz=ding"),
   773  			vars:            map[string]string{},
   774  			host:            "",
   775  			path:            "",
   776  			query:           "foo=bar&baz=ding",
   777  			queriesTemplate: "foo=bar,baz=ding",
   778  			queriesRegexp:   "^foo=bar$,^baz=ding$",
   779  			shouldMatch:     true,
   780  		},
   781  		{
   782  			title:           "Queries route, match with a query string",
   783  			route:           new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
   784  			request:         newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
   785  			vars:            map[string]string{},
   786  			host:            "",
   787  			path:            "",
   788  			query:           "foo=bar&baz=ding",
   789  			pathTemplate:    `/api`,
   790  			hostTemplate:    `www.example.com`,
   791  			queriesTemplate: "foo=bar,baz=ding",
   792  			queriesRegexp:   "^foo=bar$,^baz=ding$",
   793  			shouldMatch:     true,
   794  		},
   795  		{
   796  			title:           "Queries route, match with a query string out of order",
   797  			route:           new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
   798  			request:         newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
   799  			vars:            map[string]string{},
   800  			host:            "",
   801  			path:            "",
   802  			query:           "foo=bar&baz=ding",
   803  			pathTemplate:    `/api`,
   804  			hostTemplate:    `www.example.com`,
   805  			queriesTemplate: "foo=bar,baz=ding",
   806  			queriesRegexp:   "^foo=bar$,^baz=ding$",
   807  			shouldMatch:     true,
   808  		},
   809  		{
   810  			title:           "Queries route, bad query",
   811  			route:           new(Route).Queries("foo", "bar", "baz", "ding"),
   812  			request:         newRequest("GET", "http://localhost?foo=bar&baz=dong"),
   813  			vars:            map[string]string{},
   814  			host:            "",
   815  			path:            "",
   816  			queriesTemplate: "foo=bar,baz=ding",
   817  			queriesRegexp:   "^foo=bar$,^baz=ding$",
   818  			shouldMatch:     false,
   819  		},
   820  		{
   821  			title:           "Queries route with pattern, match",
   822  			route:           new(Route).Queries("foo", "{v1}"),
   823  			request:         newRequest("GET", "http://localhost?foo=bar"),
   824  			vars:            map[string]string{"v1": "bar"},
   825  			host:            "",
   826  			path:            "",
   827  			query:           "foo=bar",
   828  			queriesTemplate: "foo={v1}",
   829  			queriesRegexp:   "^foo=(?P<v0>.*)$",
   830  			shouldMatch:     true,
   831  		},
   832  		{
   833  			title:           "Queries route with multiple patterns, match",
   834  			route:           new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
   835  			request:         newRequest("GET", "http://localhost?foo=bar&baz=ding"),
   836  			vars:            map[string]string{"v1": "bar", "v2": "ding"},
   837  			host:            "",
   838  			path:            "",
   839  			query:           "foo=bar&baz=ding",
   840  			queriesTemplate: "foo={v1},baz={v2}",
   841  			queriesRegexp:   "^foo=(?P<v0>.*)$,^baz=(?P<v0>.*)$",
   842  			shouldMatch:     true,
   843  		},
   844  		{
   845  			title:           "Queries route with regexp pattern, match",
   846  			route:           new(Route).Queries("foo", "{v1:[0-9]+}"),
   847  			request:         newRequest("GET", "http://localhost?foo=10"),
   848  			vars:            map[string]string{"v1": "10"},
   849  			host:            "",
   850  			path:            "",
   851  			query:           "foo=10",
   852  			queriesTemplate: "foo={v1:[0-9]+}",
   853  			queriesRegexp:   "^foo=(?P<v0>[0-9]+)$",
   854  			shouldMatch:     true,
   855  		},
   856  		{
   857  			title:           "Queries route with regexp pattern, regexp does not match",
   858  			route:           new(Route).Queries("foo", "{v1:[0-9]+}"),
   859  			request:         newRequest("GET", "http://localhost?foo=a"),
   860  			vars:            map[string]string{},
   861  			host:            "",
   862  			path:            "",
   863  			queriesTemplate: "foo={v1:[0-9]+}",
   864  			queriesRegexp:   "^foo=(?P<v0>[0-9]+)$",
   865  			shouldMatch:     false,
   866  		},
   867  		{
   868  			title:           "Queries route with regexp pattern with quantifier, match",
   869  			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
   870  			request:         newRequest("GET", "http://localhost?foo=1"),
   871  			vars:            map[string]string{"v1": "1"},
   872  			host:            "",
   873  			path:            "",
   874  			query:           "foo=1",
   875  			queriesTemplate: "foo={v1:[0-9]{1}}",
   876  			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
   877  			shouldMatch:     true,
   878  		},
   879  		{
   880  			title:           "Queries route with regexp pattern with quantifier, additional variable in query string, match",
   881  			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
   882  			request:         newRequest("GET", "http://localhost?bar=2&foo=1"),
   883  			vars:            map[string]string{"v1": "1"},
   884  			host:            "",
   885  			path:            "",
   886  			query:           "foo=1",
   887  			queriesTemplate: "foo={v1:[0-9]{1}}",
   888  			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
   889  			shouldMatch:     true,
   890  		},
   891  		{
   892  			title:           "Queries route with regexp pattern with quantifier, regexp does not match",
   893  			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
   894  			request:         newRequest("GET", "http://localhost?foo=12"),
   895  			vars:            map[string]string{},
   896  			host:            "",
   897  			path:            "",
   898  			queriesTemplate: "foo={v1:[0-9]{1}}",
   899  			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
   900  			shouldMatch:     false,
   901  		},
   902  		{
   903  			title:           "Queries route with regexp pattern with quantifier, additional capturing group",
   904  			route:           new(Route).Queries("foo", "{v1:[0-9]{1}(?:a|b)}"),
   905  			request:         newRequest("GET", "http://localhost?foo=1a"),
   906  			vars:            map[string]string{"v1": "1a"},
   907  			host:            "",
   908  			path:            "",
   909  			query:           "foo=1a",
   910  			queriesTemplate: "foo={v1:[0-9]{1}(?:a|b)}",
   911  			queriesRegexp:   "^foo=(?P<v0>[0-9]{1}(?:a|b))$",
   912  			shouldMatch:     true,
   913  		},
   914  		{
   915  			title:           "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match",
   916  			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
   917  			request:         newRequest("GET", "http://localhost?foo=12"),
   918  			vars:            map[string]string{},
   919  			host:            "",
   920  			path:            "",
   921  			queriesTemplate: "foo={v1:[0-9]{1}}",
   922  			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
   923  			shouldMatch:     false,
   924  		},
   925  		{
   926  			title:           "Queries route with hyphenated name, match",
   927  			route:           new(Route).Queries("foo", "{v-1}"),
   928  			request:         newRequest("GET", "http://localhost?foo=bar"),
   929  			vars:            map[string]string{"v-1": "bar"},
   930  			host:            "",
   931  			path:            "",
   932  			query:           "foo=bar",
   933  			queriesTemplate: "foo={v-1}",
   934  			queriesRegexp:   "^foo=(?P<v0>.*)$",
   935  			shouldMatch:     true,
   936  		},
   937  		{
   938  			title:           "Queries route with multiple hyphenated names, match",
   939  			route:           new(Route).Queries("foo", "{v-1}", "baz", "{v-2}"),
   940  			request:         newRequest("GET", "http://localhost?foo=bar&baz=ding"),
   941  			vars:            map[string]string{"v-1": "bar", "v-2": "ding"},
   942  			host:            "",
   943  			path:            "",
   944  			query:           "foo=bar&baz=ding",
   945  			queriesTemplate: "foo={v-1},baz={v-2}",
   946  			queriesRegexp:   "^foo=(?P<v0>.*)$,^baz=(?P<v0>.*)$",
   947  			shouldMatch:     true,
   948  		},
   949  		{
   950  			title:           "Queries route with hyphenate name and pattern, match",
   951  			route:           new(Route).Queries("foo", "{v-1:[0-9]+}"),
   952  			request:         newRequest("GET", "http://localhost?foo=10"),
   953  			vars:            map[string]string{"v-1": "10"},
   954  			host:            "",
   955  			path:            "",
   956  			query:           "foo=10",
   957  			queriesTemplate: "foo={v-1:[0-9]+}",
   958  			queriesRegexp:   "^foo=(?P<v0>[0-9]+)$",
   959  			shouldMatch:     true,
   960  		},
   961  		{
   962  			title:           "Queries route with hyphenated name and pattern with quantifier, additional capturing group",
   963  			route:           new(Route).Queries("foo", "{v-1:[0-9]{1}(?:a|b)}"),
   964  			request:         newRequest("GET", "http://localhost?foo=1a"),
   965  			vars:            map[string]string{"v-1": "1a"},
   966  			host:            "",
   967  			path:            "",
   968  			query:           "foo=1a",
   969  			queriesTemplate: "foo={v-1:[0-9]{1}(?:a|b)}",
   970  			queriesRegexp:   "^foo=(?P<v0>[0-9]{1}(?:a|b))$",
   971  			shouldMatch:     true,
   972  		},
   973  		{
   974  			title:           "Queries route with empty value, should match",
   975  			route:           new(Route).Queries("foo", ""),
   976  			request:         newRequest("GET", "http://localhost?foo=bar"),
   977  			vars:            map[string]string{},
   978  			host:            "",
   979  			path:            "",
   980  			query:           "foo=",
   981  			queriesTemplate: "foo=",
   982  			queriesRegexp:   "^foo=.*$",
   983  			shouldMatch:     true,
   984  		},
   985  		{
   986  			title:           "Queries route with empty value and no parameter in request, should not match",
   987  			route:           new(Route).Queries("foo", ""),
   988  			request:         newRequest("GET", "http://localhost"),
   989  			vars:            map[string]string{},
   990  			host:            "",
   991  			path:            "",
   992  			queriesTemplate: "foo=",
   993  			queriesRegexp:   "^foo=.*$",
   994  			shouldMatch:     false,
   995  		},
   996  		{
   997  			title:           "Queries route with empty value and empty parameter in request, should match",
   998  			route:           new(Route).Queries("foo", ""),
   999  			request:         newRequest("GET", "http://localhost?foo="),
  1000  			vars:            map[string]string{},
  1001  			host:            "",
  1002  			path:            "",
  1003  			query:           "foo=",
  1004  			queriesTemplate: "foo=",
  1005  			queriesRegexp:   "^foo=.*$",
  1006  			shouldMatch:     true,
  1007  		},
  1008  		{
  1009  			title:           "Queries route with overlapping value, should not match",
  1010  			route:           new(Route).Queries("foo", "bar"),
  1011  			request:         newRequest("GET", "http://localhost?foo=barfoo"),
  1012  			vars:            map[string]string{},
  1013  			host:            "",
  1014  			path:            "",
  1015  			queriesTemplate: "foo=bar",
  1016  			queriesRegexp:   "^foo=bar$",
  1017  			shouldMatch:     false,
  1018  		},
  1019  		{
  1020  			title:           "Queries route with no parameter in request, should not match",
  1021  			route:           new(Route).Queries("foo", "{bar}"),
  1022  			request:         newRequest("GET", "http://localhost"),
  1023  			vars:            map[string]string{},
  1024  			host:            "",
  1025  			path:            "",
  1026  			queriesTemplate: "foo={bar}",
  1027  			queriesRegexp:   "^foo=(?P<v0>.*)$",
  1028  			shouldMatch:     false,
  1029  		},
  1030  		{
  1031  			title:           "Queries route with empty parameter in request, should match",
  1032  			route:           new(Route).Queries("foo", "{bar}"),
  1033  			request:         newRequest("GET", "http://localhost?foo="),
  1034  			vars:            map[string]string{"foo": ""},
  1035  			host:            "",
  1036  			path:            "",
  1037  			query:           "foo=",
  1038  			queriesTemplate: "foo={bar}",
  1039  			queriesRegexp:   "^foo=(?P<v0>.*)$",
  1040  			shouldMatch:     true,
  1041  		},
  1042  		{
  1043  			title:           "Queries route, bad submatch",
  1044  			route:           new(Route).Queries("foo", "bar", "baz", "ding"),
  1045  			request:         newRequest("GET", "http://localhost?fffoo=bar&baz=dingggg"),
  1046  			vars:            map[string]string{},
  1047  			host:            "",
  1048  			path:            "",
  1049  			queriesTemplate: "foo=bar,baz=ding",
  1050  			queriesRegexp:   "^foo=bar$,^baz=ding$",
  1051  			shouldMatch:     false,
  1052  		},
  1053  		{
  1054  			title:           "Queries route with pattern, match, escaped value",
  1055  			route:           new(Route).Queries("foo", "{v1}"),
  1056  			request:         newRequest("GET", "http://localhost?foo=%25bar%26%20%2F%3D%3F"),
  1057  			vars:            map[string]string{"v1": "%bar& /=?"},
  1058  			host:            "",
  1059  			path:            "",
  1060  			query:           "foo=%25bar%26+%2F%3D%3F",
  1061  			queriesTemplate: "foo={v1}",
  1062  			queriesRegexp:   "^foo=(?P<v0>.*)$",
  1063  			shouldMatch:     true,
  1064  		},
  1065  	}
  1066  
  1067  	for _, test := range tests {
  1068  		t.Run(test.title, func(t *testing.T) {
  1069  			testTemplate(t, test)
  1070  			testQueriesTemplates(t, test)
  1071  			testUseEscapedRoute(t, test)
  1072  			testQueriesRegexp(t, test)
  1073  		})
  1074  	}
  1075  }
  1076  
  1077  func TestSchemes(t *testing.T) {
  1078  	tests := []routeTest{
  1079  		// Schemes
  1080  		{
  1081  			title:       "Schemes route, default scheme, match http, build http",
  1082  			route:       new(Route).Host("localhost"),
  1083  			request:     newRequest("GET", "http://localhost"),
  1084  			scheme:      "http",
  1085  			host:        "localhost",
  1086  			shouldMatch: true,
  1087  		},
  1088  		{
  1089  			title:       "Schemes route, match https, build https",
  1090  			route:       new(Route).Schemes("https", "ftp").Host("localhost"),
  1091  			request:     newRequest("GET", "https://localhost"),
  1092  			scheme:      "https",
  1093  			host:        "localhost",
  1094  			shouldMatch: true,
  1095  		},
  1096  		{
  1097  			title:       "Schemes route, match ftp, build https",
  1098  			route:       new(Route).Schemes("https", "ftp").Host("localhost"),
  1099  			request:     newRequest("GET", "ftp://localhost"),
  1100  			scheme:      "https",
  1101  			host:        "localhost",
  1102  			shouldMatch: true,
  1103  		},
  1104  		{
  1105  			title:       "Schemes route, match ftp, build ftp",
  1106  			route:       new(Route).Schemes("ftp", "https").Host("localhost"),
  1107  			request:     newRequest("GET", "ftp://localhost"),
  1108  			scheme:      "ftp",
  1109  			host:        "localhost",
  1110  			shouldMatch: true,
  1111  		},
  1112  		{
  1113  			title:       "Schemes route, bad scheme",
  1114  			route:       new(Route).Schemes("https", "ftp").Host("localhost"),
  1115  			request:     newRequest("GET", "http://localhost"),
  1116  			scheme:      "https",
  1117  			host:        "localhost",
  1118  			shouldMatch: false,
  1119  		},
  1120  	}
  1121  	for _, test := range tests {
  1122  		t.Run(test.title, func(t *testing.T) {
  1123  			testRoute(t, test)
  1124  			testTemplate(t, test)
  1125  		})
  1126  	}
  1127  }
  1128  
  1129  func TestMatcherFunc(t *testing.T) {
  1130  	m := func(r *http.Request, m *RouteMatch) bool {
  1131  		return r.URL.Host == "aaa.bbb.ccc"
  1132  	}
  1133  
  1134  	tests := []routeTest{
  1135  		{
  1136  			title:       "MatchFunc route, match",
  1137  			route:       new(Route).MatcherFunc(m),
  1138  			request:     newRequest("GET", "http://aaa.bbb.ccc"),
  1139  			vars:        map[string]string{},
  1140  			host:        "",
  1141  			path:        "",
  1142  			shouldMatch: true,
  1143  		},
  1144  		{
  1145  			title:       "MatchFunc route, non-match",
  1146  			route:       new(Route).MatcherFunc(m),
  1147  			request:     newRequest("GET", "http://aaa.222.ccc"),
  1148  			vars:        map[string]string{},
  1149  			host:        "",
  1150  			path:        "",
  1151  			shouldMatch: false,
  1152  		},
  1153  	}
  1154  
  1155  	for _, test := range tests {
  1156  		t.Run(test.title, func(t *testing.T) {
  1157  			testRoute(t, test)
  1158  			testTemplate(t, test)
  1159  		})
  1160  	}
  1161  }
  1162  
  1163  func TestBuildVarsFunc(t *testing.T) {
  1164  	tests := []routeTest{
  1165  		{
  1166  			title: "BuildVarsFunc set on route",
  1167  			route: new(Route).Path(`/111/{v1:\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
  1168  				vars["v1"] = "3"
  1169  				vars["v2"] = "a"
  1170  				return vars
  1171  			}),
  1172  			request:      newRequest("GET", "http://localhost/111/2"),
  1173  			path:         "/111/3a",
  1174  			pathTemplate: `/111/{v1:\d}{v2:.*}`,
  1175  			shouldMatch:  true,
  1176  		},
  1177  		{
  1178  			title: "BuildVarsFunc set on route and parent route",
  1179  			route: new(Route).PathPrefix(`/{v1:\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
  1180  				vars["v1"] = "2"
  1181  				return vars
  1182  			}).Subrouter().Path(`/{v2:\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
  1183  				vars["v2"] = "b"
  1184  				return vars
  1185  			}),
  1186  			request:      newRequest("GET", "http://localhost/1/a"),
  1187  			path:         "/2/b",
  1188  			pathTemplate: `/{v1:\d}/{v2:\w}`,
  1189  			shouldMatch:  true,
  1190  		},
  1191  	}
  1192  
  1193  	for _, test := range tests {
  1194  		t.Run(test.title, func(t *testing.T) {
  1195  			testRoute(t, test)
  1196  			testTemplate(t, test)
  1197  		})
  1198  	}
  1199  }
  1200  
  1201  func TestSubRouter(t *testing.T) {
  1202  	subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
  1203  	subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
  1204  	subrouter3 := new(Route).PathPrefix("/foo").Subrouter()
  1205  	subrouter4 := new(Route).PathPrefix("/foo/bar").Subrouter()
  1206  	subrouter5 := new(Route).PathPrefix("/{category}").Subrouter()
  1207  	tests := []routeTest{
  1208  		{
  1209  			route:        subrouter1.Path("/{v2:[a-z]+}"),
  1210  			request:      newRequest("GET", "http://aaa.google.com/bbb"),
  1211  			vars:         map[string]string{"v1": "aaa", "v2": "bbb"},
  1212  			host:         "aaa.google.com",
  1213  			path:         "/bbb",
  1214  			pathTemplate: `/{v2:[a-z]+}`,
  1215  			hostTemplate: `{v1:[a-z]+}.google.com`,
  1216  			shouldMatch:  true,
  1217  		},
  1218  		{
  1219  			route:        subrouter1.Path("/{v2:[a-z]+}"),
  1220  			request:      newRequest("GET", "http://111.google.com/111"),
  1221  			vars:         map[string]string{"v1": "aaa", "v2": "bbb"},
  1222  			host:         "aaa.google.com",
  1223  			path:         "/bbb",
  1224  			pathTemplate: `/{v2:[a-z]+}`,
  1225  			hostTemplate: `{v1:[a-z]+}.google.com`,
  1226  			shouldMatch:  false,
  1227  		},
  1228  		{
  1229  			route:        subrouter2.Path("/baz/{v2}"),
  1230  			request:      newRequest("GET", "http://localhost/foo/bar/baz/ding"),
  1231  			vars:         map[string]string{"v1": "bar", "v2": "ding"},
  1232  			host:         "",
  1233  			path:         "/foo/bar/baz/ding",
  1234  			pathTemplate: `/foo/{v1}/baz/{v2}`,
  1235  			shouldMatch:  true,
  1236  		},
  1237  		{
  1238  			route:        subrouter2.Path("/baz/{v2}"),
  1239  			request:      newRequest("GET", "http://localhost/foo/bar"),
  1240  			vars:         map[string]string{"v1": "bar", "v2": "ding"},
  1241  			host:         "",
  1242  			path:         "/foo/bar/baz/ding",
  1243  			pathTemplate: `/foo/{v1}/baz/{v2}`,
  1244  			shouldMatch:  false,
  1245  		},
  1246  		{
  1247  			route:        subrouter3.Path("/"),
  1248  			request:      newRequest("GET", "http://localhost/foo/"),
  1249  			vars:         map[string]string{},
  1250  			host:         "",
  1251  			path:         "/foo/",
  1252  			pathTemplate: `/foo/`,
  1253  			shouldMatch:  true,
  1254  		},
  1255  		{
  1256  			route:        subrouter3.Path(""),
  1257  			request:      newRequest("GET", "http://localhost/foo"),
  1258  			vars:         map[string]string{},
  1259  			host:         "",
  1260  			path:         "/foo",
  1261  			pathTemplate: `/foo`,
  1262  			shouldMatch:  true,
  1263  		},
  1264  
  1265  		{
  1266  			route:        subrouter4.Path("/"),
  1267  			request:      newRequest("GET", "http://localhost/foo/bar/"),
  1268  			vars:         map[string]string{},
  1269  			host:         "",
  1270  			path:         "/foo/bar/",
  1271  			pathTemplate: `/foo/bar/`,
  1272  			shouldMatch:  true,
  1273  		},
  1274  		{
  1275  			route:        subrouter4.Path(""),
  1276  			request:      newRequest("GET", "http://localhost/foo/bar"),
  1277  			vars:         map[string]string{},
  1278  			host:         "",
  1279  			path:         "/foo/bar",
  1280  			pathTemplate: `/foo/bar`,
  1281  			shouldMatch:  true,
  1282  		},
  1283  		{
  1284  			route:        subrouter5.Path("/"),
  1285  			request:      newRequest("GET", "http://localhost/baz/"),
  1286  			vars:         map[string]string{"category": "baz"},
  1287  			host:         "",
  1288  			path:         "/baz/",
  1289  			pathTemplate: `/{category}/`,
  1290  			shouldMatch:  true,
  1291  		},
  1292  		{
  1293  			route:        subrouter5.Path(""),
  1294  			request:      newRequest("GET", "http://localhost/baz"),
  1295  			vars:         map[string]string{"category": "baz"},
  1296  			host:         "",
  1297  			path:         "/baz",
  1298  			pathTemplate: `/{category}`,
  1299  			shouldMatch:  true,
  1300  		},
  1301  		{
  1302  			title:        "Mismatch method specified on parent route",
  1303  			route:        new(Route).Methods("POST").PathPrefix("/foo").Subrouter().Path("/"),
  1304  			request:      newRequest("GET", "http://localhost/foo/"),
  1305  			vars:         map[string]string{},
  1306  			host:         "",
  1307  			path:         "/foo/",
  1308  			pathTemplate: `/foo/`,
  1309  			shouldMatch:  false,
  1310  		},
  1311  		{
  1312  			title:        "Match method specified on parent route",
  1313  			route:        new(Route).Methods("POST").PathPrefix("/foo").Subrouter().Path("/"),
  1314  			request:      newRequest("POST", "http://localhost/foo/"),
  1315  			vars:         map[string]string{},
  1316  			host:         "",
  1317  			path:         "/foo/",
  1318  			pathTemplate: `/foo/`,
  1319  			shouldMatch:  true,
  1320  		},
  1321  		{
  1322  			title:        "Mismatch scheme specified on parent route",
  1323  			route:        new(Route).Schemes("https").Subrouter().PathPrefix("/"),
  1324  			request:      newRequest("GET", "http://localhost/"),
  1325  			vars:         map[string]string{},
  1326  			host:         "",
  1327  			path:         "/",
  1328  			pathTemplate: `/`,
  1329  			shouldMatch:  false,
  1330  		},
  1331  		{
  1332  			title:        "Match scheme specified on parent route",
  1333  			route:        new(Route).Schemes("http").Subrouter().PathPrefix("/"),
  1334  			request:      newRequest("GET", "http://localhost/"),
  1335  			vars:         map[string]string{},
  1336  			host:         "",
  1337  			path:         "/",
  1338  			pathTemplate: `/`,
  1339  			shouldMatch:  true,
  1340  		},
  1341  		{
  1342  			title:        "No match header specified on parent route",
  1343  			route:        new(Route).Headers("X-Forwarded-Proto", "https").Subrouter().PathPrefix("/"),
  1344  			request:      newRequest("GET", "http://localhost/"),
  1345  			vars:         map[string]string{},
  1346  			host:         "",
  1347  			path:         "/",
  1348  			pathTemplate: `/`,
  1349  			shouldMatch:  false,
  1350  		},
  1351  		{
  1352  			title:        "Header mismatch value specified on parent route",
  1353  			route:        new(Route).Headers("X-Forwarded-Proto", "https").Subrouter().PathPrefix("/"),
  1354  			request:      newRequestWithHeaders("GET", "http://localhost/", "X-Forwarded-Proto", "http"),
  1355  			vars:         map[string]string{},
  1356  			host:         "",
  1357  			path:         "/",
  1358  			pathTemplate: `/`,
  1359  			shouldMatch:  false,
  1360  		},
  1361  		{
  1362  			title:        "Header match value specified on parent route",
  1363  			route:        new(Route).Headers("X-Forwarded-Proto", "https").Subrouter().PathPrefix("/"),
  1364  			request:      newRequestWithHeaders("GET", "http://localhost/", "X-Forwarded-Proto", "https"),
  1365  			vars:         map[string]string{},
  1366  			host:         "",
  1367  			path:         "/",
  1368  			pathTemplate: `/`,
  1369  			shouldMatch:  true,
  1370  		},
  1371  		{
  1372  			title:        "Query specified on parent route not present",
  1373  			route:        new(Route).Headers("key", "foobar").Subrouter().PathPrefix("/"),
  1374  			request:      newRequest("GET", "http://localhost/"),
  1375  			vars:         map[string]string{},
  1376  			host:         "",
  1377  			path:         "/",
  1378  			pathTemplate: `/`,
  1379  			shouldMatch:  false,
  1380  		},
  1381  		{
  1382  			title:        "Query mismatch value specified on parent route",
  1383  			route:        new(Route).Queries("key", "foobar").Subrouter().PathPrefix("/"),
  1384  			request:      newRequest("GET", "http://localhost/?key=notfoobar"),
  1385  			vars:         map[string]string{},
  1386  			host:         "",
  1387  			path:         "/",
  1388  			pathTemplate: `/`,
  1389  			shouldMatch:  false,
  1390  		},
  1391  		{
  1392  			title:        "Query match value specified on subroute",
  1393  			route:        new(Route).Queries("key", "foobar").Subrouter().PathPrefix("/"),
  1394  			request:      newRequest("GET", "http://localhost/?key=foobar"),
  1395  			vars:         map[string]string{},
  1396  			host:         "",
  1397  			path:         "/",
  1398  			pathTemplate: `/`,
  1399  			shouldMatch:  true,
  1400  		},
  1401  		{
  1402  			title:        "Build with scheme on parent router",
  1403  			route:        new(Route).Schemes("ftp").Host("google.com").Subrouter().Path("/"),
  1404  			request:      newRequest("GET", "ftp://google.com/"),
  1405  			scheme:       "ftp",
  1406  			host:         "google.com",
  1407  			path:         "/",
  1408  			pathTemplate: `/`,
  1409  			hostTemplate: `google.com`,
  1410  			shouldMatch:  true,
  1411  		},
  1412  		{
  1413  			title:        "Prefer scheme on child route when building URLs",
  1414  			route:        new(Route).Schemes("https", "ftp").Host("google.com").Subrouter().Schemes("ftp").Path("/"),
  1415  			request:      newRequest("GET", "ftp://google.com/"),
  1416  			scheme:       "ftp",
  1417  			host:         "google.com",
  1418  			path:         "/",
  1419  			pathTemplate: `/`,
  1420  			hostTemplate: `google.com`,
  1421  			shouldMatch:  true,
  1422  		},
  1423  	}
  1424  
  1425  	for _, test := range tests {
  1426  		t.Run(test.title, func(t *testing.T) {
  1427  			testRoute(t, test)
  1428  			testTemplate(t, test)
  1429  			testUseEscapedRoute(t, test)
  1430  		})
  1431  	}
  1432  }
  1433  
  1434  func TestNamedRoutes(t *testing.T) {
  1435  	r1 := NewRouter()
  1436  	r1.NewRoute().Name("a")
  1437  	r1.NewRoute().Name("b")
  1438  	r1.NewRoute().Name("c")
  1439  
  1440  	r2 := r1.NewRoute().Subrouter()
  1441  	r2.NewRoute().Name("d")
  1442  	r2.NewRoute().Name("e")
  1443  	r2.NewRoute().Name("f")
  1444  
  1445  	r3 := r2.NewRoute().Subrouter()
  1446  	r3.NewRoute().Name("g")
  1447  	r3.NewRoute().Name("h")
  1448  	r3.NewRoute().Name("i")
  1449  	r3.Name("j")
  1450  
  1451  	if r1.namedRoutes == nil || len(r1.namedRoutes) != 10 {
  1452  		t.Errorf("Expected 10 named routes, got %v", r1.namedRoutes)
  1453  	} else if r1.Get("j") == nil {
  1454  		t.Errorf("Subroute name not registered")
  1455  	}
  1456  }
  1457  
  1458  func TestNameMultipleCalls(t *testing.T) {
  1459  	r1 := NewRouter()
  1460  	rt := r1.NewRoute().Name("foo").Name("bar")
  1461  	err := rt.GetError()
  1462  	if err == nil {
  1463  		t.Errorf("Expected an error")
  1464  	}
  1465  }
  1466  
  1467  func TestStrictSlash(t *testing.T) {
  1468  	r := NewRouter()
  1469  	r.StrictSlash(true)
  1470  
  1471  	tests := []routeTest{
  1472  		{
  1473  			title:          "Redirect path without slash",
  1474  			route:          r.NewRoute().Path("/111/"),
  1475  			request:        newRequest("GET", "http://localhost/111"),
  1476  			vars:           map[string]string{},
  1477  			host:           "",
  1478  			path:           "/111/",
  1479  			shouldMatch:    true,
  1480  			shouldRedirect: true,
  1481  		},
  1482  		{
  1483  			title:          "Do not redirect path with slash",
  1484  			route:          r.NewRoute().Path("/111/"),
  1485  			request:        newRequest("GET", "http://localhost/111/"),
  1486  			vars:           map[string]string{},
  1487  			host:           "",
  1488  			path:           "/111/",
  1489  			shouldMatch:    true,
  1490  			shouldRedirect: false,
  1491  		},
  1492  		{
  1493  			title:          "Redirect path with slash",
  1494  			route:          r.NewRoute().Path("/111"),
  1495  			request:        newRequest("GET", "http://localhost/111/"),
  1496  			vars:           map[string]string{},
  1497  			host:           "",
  1498  			path:           "/111",
  1499  			shouldMatch:    true,
  1500  			shouldRedirect: true,
  1501  		},
  1502  		{
  1503  			title:          "Do not redirect path without slash",
  1504  			route:          r.NewRoute().Path("/111"),
  1505  			request:        newRequest("GET", "http://localhost/111"),
  1506  			vars:           map[string]string{},
  1507  			host:           "",
  1508  			path:           "/111",
  1509  			shouldMatch:    true,
  1510  			shouldRedirect: false,
  1511  		},
  1512  		{
  1513  			title:          "Propagate StrictSlash to subrouters",
  1514  			route:          r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
  1515  			request:        newRequest("GET", "http://localhost/static/images"),
  1516  			vars:           map[string]string{},
  1517  			host:           "",
  1518  			path:           "/static/images/",
  1519  			shouldMatch:    true,
  1520  			shouldRedirect: true,
  1521  		},
  1522  		{
  1523  			title:          "Ignore StrictSlash for path prefix",
  1524  			route:          r.NewRoute().PathPrefix("/static/"),
  1525  			request:        newRequest("GET", "http://localhost/static/logo.png"),
  1526  			vars:           map[string]string{},
  1527  			host:           "",
  1528  			path:           "/static/",
  1529  			shouldMatch:    true,
  1530  			shouldRedirect: false,
  1531  		},
  1532  	}
  1533  
  1534  	for _, test := range tests {
  1535  		t.Run(test.title, func(t *testing.T) {
  1536  			testRoute(t, test)
  1537  			testTemplate(t, test)
  1538  			testUseEscapedRoute(t, test)
  1539  		})
  1540  	}
  1541  }
  1542  
  1543  func TestUseEncodedPath(t *testing.T) {
  1544  	r := NewRouter()
  1545  	r.UseEncodedPath()
  1546  
  1547  	tests := []routeTest{
  1548  		{
  1549  			title:        "Router with useEncodedPath, URL with encoded slash does match",
  1550  			route:        r.NewRoute().Path("/v1/{v1}/v2"),
  1551  			request:      newRequest("GET", "http://localhost/v1/1%2F2/v2"),
  1552  			vars:         map[string]string{"v1": "1%2F2"},
  1553  			host:         "",
  1554  			path:         "/v1/1%2F2/v2",
  1555  			pathTemplate: `/v1/{v1}/v2`,
  1556  			shouldMatch:  true,
  1557  		},
  1558  		{
  1559  			title:        "Router with useEncodedPath, URL with encoded slash doesn't match",
  1560  			route:        r.NewRoute().Path("/v1/1/2/v2"),
  1561  			request:      newRequest("GET", "http://localhost/v1/1%2F2/v2"),
  1562  			vars:         map[string]string{"v1": "1%2F2"},
  1563  			host:         "",
  1564  			path:         "/v1/1%2F2/v2",
  1565  			pathTemplate: `/v1/1/2/v2`,
  1566  			shouldMatch:  false,
  1567  		},
  1568  	}
  1569  
  1570  	for _, test := range tests {
  1571  		t.Run(test.title, func(t *testing.T) {
  1572  			testRoute(t, test)
  1573  			testTemplate(t, test)
  1574  		})
  1575  	}
  1576  }
  1577  
  1578  func TestWalkSingleDepth(t *testing.T) {
  1579  	r0 := NewRouter()
  1580  	r1 := NewRouter()
  1581  	r2 := NewRouter()
  1582  
  1583  	r0.Path("/g")
  1584  	r0.Path("/o")
  1585  	r0.Path("/d").Handler(r1)
  1586  	r0.Path("/r").Handler(r2)
  1587  	r0.Path("/a")
  1588  
  1589  	r1.Path("/z")
  1590  	r1.Path("/i")
  1591  	r1.Path("/l")
  1592  	r1.Path("/l")
  1593  
  1594  	r2.Path("/i")
  1595  	r2.Path("/l")
  1596  	r2.Path("/l")
  1597  
  1598  	paths := []string{"g", "o", "r", "i", "l", "l", "a"}
  1599  	depths := []int{0, 0, 0, 1, 1, 1, 0}
  1600  	i := 0
  1601  	err := r0.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1602  		matcher := route.matchers[0].(*routeRegexp)
  1603  		if matcher.template == "/d" {
  1604  			return SkipRouter
  1605  		}
  1606  		if len(ancestors) != depths[i] {
  1607  			t.Errorf(`Expected depth of %d at i = %d; got "%d"`, depths[i], i, len(ancestors))
  1608  		}
  1609  		if matcher.template != "/"+paths[i] {
  1610  			t.Errorf(`Expected "/%s" at i = %d; got "%s"`, paths[i], i, matcher.template)
  1611  		}
  1612  		i++
  1613  		return nil
  1614  	})
  1615  	if err != nil {
  1616  		panic(err)
  1617  	}
  1618  	if i != len(paths) {
  1619  		t.Errorf("Expected %d routes, found %d", len(paths), i)
  1620  	}
  1621  }
  1622  
  1623  func TestWalkNested(t *testing.T) {
  1624  	router := NewRouter()
  1625  
  1626  	routeSubrouter := func(r *Route) (*Route, *Router) {
  1627  		return r, r.Subrouter()
  1628  	}
  1629  
  1630  	gRoute, g := routeSubrouter(router.Path("/g"))
  1631  	oRoute, o := routeSubrouter(g.PathPrefix("/o"))
  1632  	rRoute, r := routeSubrouter(o.PathPrefix("/r"))
  1633  	iRoute, i := routeSubrouter(r.PathPrefix("/i"))
  1634  	l1Route, l1 := routeSubrouter(i.PathPrefix("/l"))
  1635  	l2Route, l2 := routeSubrouter(l1.PathPrefix("/l"))
  1636  	l2.Path("/a")
  1637  
  1638  	testCases := []struct {
  1639  		path      string
  1640  		ancestors []*Route
  1641  	}{
  1642  		{"/g", []*Route{}},
  1643  		{"/g/o", []*Route{gRoute}},
  1644  		{"/g/o/r", []*Route{gRoute, oRoute}},
  1645  		{"/g/o/r/i", []*Route{gRoute, oRoute, rRoute}},
  1646  		{"/g/o/r/i/l", []*Route{gRoute, oRoute, rRoute, iRoute}},
  1647  		{"/g/o/r/i/l/l", []*Route{gRoute, oRoute, rRoute, iRoute, l1Route}},
  1648  		{"/g/o/r/i/l/l/a", []*Route{gRoute, oRoute, rRoute, iRoute, l1Route, l2Route}},
  1649  	}
  1650  
  1651  	idx := 0
  1652  	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1653  		path := testCases[idx].path
  1654  		tpl := route.regexp.path.template
  1655  		if tpl != path {
  1656  			t.Errorf(`Expected %s got %s`, path, tpl)
  1657  		}
  1658  		currWantAncestors := testCases[idx].ancestors
  1659  		if !reflect.DeepEqual(currWantAncestors, ancestors) {
  1660  			t.Errorf(`Expected %+v got %+v`, currWantAncestors, ancestors)
  1661  		}
  1662  		idx++
  1663  		return nil
  1664  	})
  1665  	if err != nil {
  1666  		panic(err)
  1667  	}
  1668  	if idx != len(testCases) {
  1669  		t.Errorf("Expected %d routes, found %d", len(testCases), idx)
  1670  	}
  1671  }
  1672  
  1673  func TestWalkSubrouters(t *testing.T) {
  1674  	router := NewRouter()
  1675  
  1676  	g := router.Path("/g").Subrouter()
  1677  	o := g.PathPrefix("/o").Subrouter()
  1678  	o.Methods("GET")
  1679  	o.Methods("PUT")
  1680  
  1681  	// all 4 routes should be matched
  1682  	paths := []string{"/g", "/g/o", "/g/o", "/g/o"}
  1683  	idx := 0
  1684  	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1685  		path := paths[idx]
  1686  		tpl, _ := route.GetPathTemplate()
  1687  		if tpl != path {
  1688  			t.Errorf(`Expected %s got %s`, path, tpl)
  1689  		}
  1690  		idx++
  1691  		return nil
  1692  	})
  1693  	if err != nil {
  1694  		panic(err)
  1695  	}
  1696  	if idx != len(paths) {
  1697  		t.Errorf("Expected %d routes, found %d", len(paths), idx)
  1698  	}
  1699  }
  1700  
  1701  func TestWalkErrorRoute(t *testing.T) {
  1702  	router := NewRouter()
  1703  	router.Path("/g")
  1704  	expectedError := errors.New("error")
  1705  	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1706  		return expectedError
  1707  	})
  1708  	if err != expectedError {
  1709  		t.Errorf("Expected %v routes, found %v", expectedError, err)
  1710  	}
  1711  }
  1712  
  1713  func TestWalkErrorMatcher(t *testing.T) {
  1714  	router := NewRouter()
  1715  	expectedError := router.Path("/g").Subrouter().Path("").GetError()
  1716  	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1717  		return route.GetError()
  1718  	})
  1719  	if err != expectedError {
  1720  		t.Errorf("Expected %v routes, found %v", expectedError, err)
  1721  	}
  1722  }
  1723  
  1724  func TestWalkErrorHandler(t *testing.T) {
  1725  	handler := NewRouter()
  1726  	expectedError := handler.Path("/path").Subrouter().Path("").GetError()
  1727  	router := NewRouter()
  1728  	router.Path("/g").Handler(handler)
  1729  	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1730  		return route.GetError()
  1731  	})
  1732  	if err != expectedError {
  1733  		t.Errorf("Expected %v routes, found %v", expectedError, err)
  1734  	}
  1735  }
  1736  
  1737  func TestSubrouterErrorHandling(t *testing.T) {
  1738  	superRouterCalled := false
  1739  	subRouterCalled := false
  1740  
  1741  	router := NewRouter()
  1742  	router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1743  		superRouterCalled = true
  1744  	})
  1745  	subRouter := router.PathPrefix("/bign8").Subrouter()
  1746  	subRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1747  		subRouterCalled = true
  1748  	})
  1749  
  1750  	req, _ := http.NewRequest("GET", "http://localhost/bign8/was/here", nil)
  1751  	router.ServeHTTP(NewRecorder(), req)
  1752  
  1753  	if superRouterCalled {
  1754  		t.Error("Super router 404 handler called when sub-router 404 handler is available.")
  1755  	}
  1756  	if !subRouterCalled {
  1757  		t.Error("Sub-router 404 handler was not called.")
  1758  	}
  1759  }
  1760  
  1761  // See: https://github.com/gorilla/mux/issues/200
  1762  func TestPanicOnCapturingGroups(t *testing.T) {
  1763  	defer func() {
  1764  		if recover() == nil {
  1765  			t.Errorf("(Test that capturing groups now fail fast) Expected panic, however test completed successfully.\n")
  1766  		}
  1767  	}()
  1768  	NewRouter().NewRoute().Path("/{type:(promo|special)}/{promoId}.json")
  1769  }
  1770  
  1771  // ----------------------------------------------------------------------------
  1772  // Helpers
  1773  // ----------------------------------------------------------------------------
  1774  
  1775  func getRouteTemplate(route *Route) string {
  1776  	host, err := route.GetHostTemplate()
  1777  	if err != nil {
  1778  		host = "none"
  1779  	}
  1780  	path, err := route.GetPathTemplate()
  1781  	if err != nil {
  1782  		path = "none"
  1783  	}
  1784  	return fmt.Sprintf("Host: %v, Path: %v", host, path)
  1785  }
  1786  
  1787  func testRoute(t *testing.T, test routeTest) {
  1788  	request := test.request
  1789  	route := test.route
  1790  	vars := test.vars
  1791  	shouldMatch := test.shouldMatch
  1792  	query := test.query
  1793  	shouldRedirect := test.shouldRedirect
  1794  	uri := url.URL{
  1795  		Scheme: test.scheme,
  1796  		Host:   test.host,
  1797  		Path:   test.path,
  1798  	}
  1799  	if uri.Scheme == "" {
  1800  		uri.Scheme = "http"
  1801  	}
  1802  
  1803  	var match RouteMatch
  1804  	ok := route.Match(request, &match)
  1805  	if ok != shouldMatch {
  1806  		msg := "Should match"
  1807  		if !shouldMatch {
  1808  			msg = "Should not match"
  1809  		}
  1810  		t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
  1811  		return
  1812  	}
  1813  	if shouldMatch {
  1814  		if vars != nil && !stringMapEqual(vars, match.Vars) {
  1815  			t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
  1816  			return
  1817  		}
  1818  		if test.scheme != "" {
  1819  			u, err := route.URL(mapToPairs(match.Vars)...)
  1820  			if err != nil {
  1821  				t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
  1822  			}
  1823  			if uri.Scheme != u.Scheme {
  1824  				t.Errorf("(%v) URLScheme not equal: expected %v, got %v", test.title, uri.Scheme, u.Scheme)
  1825  				return
  1826  			}
  1827  		}
  1828  		if test.host != "" {
  1829  			u, err := test.route.URLHost(mapToPairs(match.Vars)...)
  1830  			if err != nil {
  1831  				t.Fatalf("(%v) URLHost error: %v -- %v", test.title, err, getRouteTemplate(route))
  1832  			}
  1833  			if uri.Scheme != u.Scheme {
  1834  				t.Errorf("(%v) URLHost scheme not equal: expected %v, got %v -- %v", test.title, uri.Scheme, u.Scheme, getRouteTemplate(route))
  1835  				return
  1836  			}
  1837  			if uri.Host != u.Host {
  1838  				t.Errorf("(%v) URLHost host not equal: expected %v, got %v -- %v", test.title, uri.Host, u.Host, getRouteTemplate(route))
  1839  				return
  1840  			}
  1841  		}
  1842  		if test.path != "" {
  1843  			u, err := route.URLPath(mapToPairs(match.Vars)...)
  1844  			if err != nil {
  1845  				t.Fatalf("(%v) URLPath error: %v -- %v", test.title, err, getRouteTemplate(route))
  1846  			}
  1847  			if uri.Path != u.Path {
  1848  				t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, uri.Path, u.Path, getRouteTemplate(route))
  1849  				return
  1850  			}
  1851  		}
  1852  		if test.host != "" && test.path != "" {
  1853  			u, err := route.URL(mapToPairs(match.Vars)...)
  1854  			if err != nil {
  1855  				t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
  1856  			}
  1857  			if expected, got := uri.String(), u.String(); expected != got {
  1858  				t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, expected, got, getRouteTemplate(route))
  1859  				return
  1860  			}
  1861  		}
  1862  		if query != "" {
  1863  			u, err := route.URL(mapToPairs(match.Vars)...)
  1864  			if err != nil {
  1865  				t.Errorf("(%v) erred while creating url: %v", test.title, err)
  1866  				return
  1867  			}
  1868  			if query != u.RawQuery {
  1869  				t.Errorf("(%v) URL query not equal: expected %v, got %v", test.title, query, u.RawQuery)
  1870  				return
  1871  			}
  1872  		}
  1873  		if shouldRedirect && match.Handler == nil {
  1874  			t.Errorf("(%v) Did not redirect", test.title)
  1875  			return
  1876  		}
  1877  		if !shouldRedirect && match.Handler != nil {
  1878  			t.Errorf("(%v) Unexpected redirect", test.title)
  1879  			return
  1880  		}
  1881  	}
  1882  }
  1883  
  1884  func testUseEscapedRoute(t *testing.T, test routeTest) {
  1885  	test.route.useEncodedPath = true
  1886  	testRoute(t, test)
  1887  }
  1888  
  1889  func testTemplate(t *testing.T, test routeTest) {
  1890  	route := test.route
  1891  	pathTemplate := test.pathTemplate
  1892  	if len(pathTemplate) == 0 {
  1893  		pathTemplate = test.path
  1894  	}
  1895  	hostTemplate := test.hostTemplate
  1896  	if len(hostTemplate) == 0 {
  1897  		hostTemplate = test.host
  1898  	}
  1899  
  1900  	routePathTemplate, pathErr := route.GetPathTemplate()
  1901  	if pathErr == nil && routePathTemplate != pathTemplate {
  1902  		t.Errorf("(%v) GetPathTemplate not equal: expected %v, got %v", test.title, pathTemplate, routePathTemplate)
  1903  	}
  1904  
  1905  	routeHostTemplate, hostErr := route.GetHostTemplate()
  1906  	if hostErr == nil && routeHostTemplate != hostTemplate {
  1907  		t.Errorf("(%v) GetHostTemplate not equal: expected %v, got %v", test.title, hostTemplate, routeHostTemplate)
  1908  	}
  1909  }
  1910  
  1911  func testMethods(t *testing.T, test routeTest) {
  1912  	route := test.route
  1913  	methods, _ := route.GetMethods()
  1914  	if strings.Join(methods, ",") != strings.Join(test.methods, ",") {
  1915  		t.Errorf("(%v) GetMethods not equal: expected %v, got %v", test.title, test.methods, methods)
  1916  	}
  1917  }
  1918  
  1919  func testRegexp(t *testing.T, test routeTest) {
  1920  	route := test.route
  1921  	routePathRegexp, regexpErr := route.GetPathRegexp()
  1922  	if test.pathRegexp != "" && regexpErr == nil && routePathRegexp != test.pathRegexp {
  1923  		t.Errorf("(%v) GetPathRegexp not equal: expected %v, got %v", test.title, test.pathRegexp, routePathRegexp)
  1924  	}
  1925  }
  1926  
  1927  func testQueriesRegexp(t *testing.T, test routeTest) {
  1928  	route := test.route
  1929  	queries, queriesErr := route.GetQueriesRegexp()
  1930  	gotQueries := strings.Join(queries, ",")
  1931  	if test.queriesRegexp != "" && queriesErr == nil && gotQueries != test.queriesRegexp {
  1932  		t.Errorf("(%v) GetQueriesRegexp not equal: expected %v, got %v", test.title, test.queriesRegexp, gotQueries)
  1933  	}
  1934  }
  1935  
  1936  func testQueriesTemplates(t *testing.T, test routeTest) {
  1937  	route := test.route
  1938  	queries, queriesErr := route.GetQueriesTemplates()
  1939  	gotQueries := strings.Join(queries, ",")
  1940  	if test.queriesTemplate != "" && queriesErr == nil && gotQueries != test.queriesTemplate {
  1941  		t.Errorf("(%v) GetQueriesTemplates not equal: expected %v, got %v", test.title, test.queriesTemplate, gotQueries)
  1942  	}
  1943  }
  1944  
  1945  type TestA301ResponseWriter struct {
  1946  	hh     http.Header
  1947  	status int
  1948  }
  1949  
  1950  func (ho *TestA301ResponseWriter) Header() http.Header {
  1951  	return ho.hh
  1952  }
  1953  
  1954  func (ho *TestA301ResponseWriter) Write(b []byte) (int, error) {
  1955  	return 0, nil
  1956  }
  1957  
  1958  func (ho *TestA301ResponseWriter) WriteHeader(code int) {
  1959  	ho.status = code
  1960  }
  1961  
  1962  func Test301Redirect(t *testing.T) {
  1963  	m := make(http.Header)
  1964  
  1965  	func1 := func(w http.ResponseWriter, r *http.Request) {}
  1966  	func2 := func(w http.ResponseWriter, r *http.Request) {}
  1967  
  1968  	r := NewRouter()
  1969  	r.HandleFunc("/api/", func2).Name("func2")
  1970  	r.HandleFunc("/", func1).Name("func1")
  1971  
  1972  	req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
  1973  
  1974  	res := TestA301ResponseWriter{
  1975  		hh:     m,
  1976  		status: 0,
  1977  	}
  1978  	r.ServeHTTP(&res, req)
  1979  
  1980  	if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
  1981  		t.Errorf("Should have complete URL with query string")
  1982  	}
  1983  }
  1984  
  1985  func TestSkipClean(t *testing.T) {
  1986  	func1 := func(w http.ResponseWriter, r *http.Request) {}
  1987  	func2 := func(w http.ResponseWriter, r *http.Request) {}
  1988  
  1989  	r := NewRouter()
  1990  	r.SkipClean(true)
  1991  	r.HandleFunc("/api/", func2).Name("func2")
  1992  	r.HandleFunc("/", func1).Name("func1")
  1993  
  1994  	req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
  1995  	res := NewRecorder()
  1996  	r.ServeHTTP(res, req)
  1997  
  1998  	if len(res.HeaderMap["Location"]) != 0 {
  1999  		t.Errorf("Shouldn't redirect since skip clean is disabled")
  2000  	}
  2001  }
  2002  
  2003  // https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
  2004  func TestSubrouterHeader(t *testing.T) {
  2005  	expected := "func1 response"
  2006  	func1 := func(w http.ResponseWriter, r *http.Request) {
  2007  		fmt.Fprint(w, expected)
  2008  	}
  2009  	func2 := func(http.ResponseWriter, *http.Request) {}
  2010  
  2011  	r := NewRouter()
  2012  	s := r.Headers("SomeSpecialHeader", "").Subrouter()
  2013  	s.HandleFunc("/", func1).Name("func1")
  2014  	r.HandleFunc("/", func2).Name("func2")
  2015  
  2016  	req, _ := http.NewRequest("GET", "http://localhost/", nil)
  2017  	req.Header.Add("SomeSpecialHeader", "foo")
  2018  	match := new(RouteMatch)
  2019  	matched := r.Match(req, match)
  2020  	if !matched {
  2021  		t.Errorf("Should match request")
  2022  	}
  2023  	if match.Route.GetName() != "func1" {
  2024  		t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
  2025  	}
  2026  	resp := NewRecorder()
  2027  	match.Handler.ServeHTTP(resp, req)
  2028  	if resp.Body.String() != expected {
  2029  		t.Errorf("Expecting %q", expected)
  2030  	}
  2031  }
  2032  
  2033  func TestNoMatchMethodErrorHandler(t *testing.T) {
  2034  	func1 := func(w http.ResponseWriter, r *http.Request) {}
  2035  
  2036  	r := NewRouter()
  2037  	r.HandleFunc("/", func1).Methods("GET", "POST")
  2038  
  2039  	req, _ := http.NewRequest("PUT", "http://localhost/", nil)
  2040  	match := new(RouteMatch)
  2041  	matched := r.Match(req, match)
  2042  
  2043  	if matched {
  2044  		t.Error("Should not have matched route for methods")
  2045  	}
  2046  
  2047  	if match.MatchErr != ErrMethodMismatch {
  2048  		t.Error("Should get ErrMethodMismatch error")
  2049  	}
  2050  
  2051  	resp := NewRecorder()
  2052  	r.ServeHTTP(resp, req)
  2053  	if resp.Code != http.StatusMethodNotAllowed {
  2054  		t.Errorf("Expecting code %v", 405)
  2055  	}
  2056  
  2057  	// Add matching route
  2058  	r.HandleFunc("/", func1).Methods("PUT")
  2059  
  2060  	match = new(RouteMatch)
  2061  	matched = r.Match(req, match)
  2062  
  2063  	if !matched {
  2064  		t.Error("Should have matched route for methods")
  2065  	}
  2066  
  2067  	if match.MatchErr != nil {
  2068  		t.Error("Should not have any matching error. Found:", match.MatchErr)
  2069  	}
  2070  }
  2071  
  2072  func TestErrMatchNotFound(t *testing.T) {
  2073  	emptyHandler := func(w http.ResponseWriter, r *http.Request) {}
  2074  
  2075  	r := NewRouter()
  2076  	r.HandleFunc("/", emptyHandler)
  2077  	s := r.PathPrefix("/sub/").Subrouter()
  2078  	s.HandleFunc("/", emptyHandler)
  2079  
  2080  	// Regular 404 not found
  2081  	req, _ := http.NewRequest("GET", "/sub/whatever", nil)
  2082  	match := new(RouteMatch)
  2083  	matched := r.Match(req, match)
  2084  
  2085  	if matched {
  2086  		t.Errorf("Subrouter should not have matched that, got %v", match.Route)
  2087  	}
  2088  	// Even without a custom handler, MatchErr is set to ErrNotFound
  2089  	if match.MatchErr != ErrNotFound {
  2090  		t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr)
  2091  	}
  2092  
  2093  	// Now lets add a 404 handler to subrouter
  2094  	s.NotFoundHandler = http.NotFoundHandler()
  2095  	req, _ = http.NewRequest("GET", "/sub/whatever", nil)
  2096  
  2097  	// Test the subrouter first
  2098  	match = new(RouteMatch)
  2099  	matched = s.Match(req, match)
  2100  	// Now we should get a match
  2101  	if !matched {
  2102  		t.Errorf("Subrouter should have matched %s", req.RequestURI)
  2103  	}
  2104  	// But MatchErr should be set to ErrNotFound anyway
  2105  	if match.MatchErr != ErrNotFound {
  2106  		t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr)
  2107  	}
  2108  
  2109  	// Now test the parent (MatchErr should propagate)
  2110  	match = new(RouteMatch)
  2111  	matched = r.Match(req, match)
  2112  
  2113  	// Now we should get a match
  2114  	if !matched {
  2115  		t.Errorf("Router should have matched %s via subrouter", req.RequestURI)
  2116  	}
  2117  	// But MatchErr should be set to ErrNotFound anyway
  2118  	if match.MatchErr != ErrNotFound {
  2119  		t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr)
  2120  	}
  2121  }
  2122  
  2123  // methodsSubrouterTest models the data necessary for testing handler
  2124  // matching for subrouters created after HTTP methods matcher registration.
  2125  type methodsSubrouterTest struct {
  2126  	title    string
  2127  	wantCode int
  2128  	router   *Router
  2129  	// method is the input into the request and expected response
  2130  	method string
  2131  	// input request path
  2132  	path string
  2133  	// redirectTo is the expected location path for strict-slash matches
  2134  	redirectTo string
  2135  }
  2136  
  2137  // methodHandler writes the method string in response.
  2138  func methodHandler(method string) http.HandlerFunc {
  2139  	return func(w http.ResponseWriter, r *http.Request) {
  2140  		w.Write([]byte(method))
  2141  	}
  2142  }
  2143  
  2144  // TestMethodsSubrouterCatchall matches handlers for subrouters where a
  2145  // catchall handler is set for a mis-matching method.
  2146  func TestMethodsSubrouterCatchall(t *testing.T) {
  2147  	t.Parallel()
  2148  
  2149  	router := NewRouter()
  2150  	router.Methods("PATCH").Subrouter().PathPrefix("/").HandlerFunc(methodHandler("PUT"))
  2151  	router.Methods("GET").Subrouter().HandleFunc("/foo", methodHandler("GET"))
  2152  	router.Methods("POST").Subrouter().HandleFunc("/foo", methodHandler("POST"))
  2153  	router.Methods("DELETE").Subrouter().HandleFunc("/foo", methodHandler("DELETE"))
  2154  
  2155  	tests := []methodsSubrouterTest{
  2156  		{
  2157  			title:    "match GET handler",
  2158  			router:   router,
  2159  			path:     "http://localhost/foo",
  2160  			method:   "GET",
  2161  			wantCode: http.StatusOK,
  2162  		},
  2163  		{
  2164  			title:    "match POST handler",
  2165  			router:   router,
  2166  			method:   "POST",
  2167  			path:     "http://localhost/foo",
  2168  			wantCode: http.StatusOK,
  2169  		},
  2170  		{
  2171  			title:    "match DELETE handler",
  2172  			router:   router,
  2173  			method:   "DELETE",
  2174  			path:     "http://localhost/foo",
  2175  			wantCode: http.StatusOK,
  2176  		},
  2177  		{
  2178  			title:    "disallow PUT method",
  2179  			router:   router,
  2180  			method:   "PUT",
  2181  			path:     "http://localhost/foo",
  2182  			wantCode: http.StatusMethodNotAllowed,
  2183  		},
  2184  	}
  2185  
  2186  	for _, test := range tests {
  2187  		t.Run(test.title, func(t *testing.T) {
  2188  			testMethodsSubrouter(t, test)
  2189  		})
  2190  	}
  2191  }
  2192  
  2193  // TestMethodsSubrouterStrictSlash matches handlers on subrouters with
  2194  // strict-slash matchers.
  2195  func TestMethodsSubrouterStrictSlash(t *testing.T) {
  2196  	t.Parallel()
  2197  
  2198  	router := NewRouter()
  2199  	sub := router.PathPrefix("/").Subrouter()
  2200  	sub.StrictSlash(true).Path("/foo").Methods("GET").Subrouter().HandleFunc("", methodHandler("GET"))
  2201  	sub.StrictSlash(true).Path("/foo/").Methods("PUT").Subrouter().HandleFunc("/", methodHandler("PUT"))
  2202  	sub.StrictSlash(true).Path("/foo/").Methods("POST").Subrouter().HandleFunc("/", methodHandler("POST"))
  2203  
  2204  	tests := []methodsSubrouterTest{
  2205  		{
  2206  			title:    "match POST handler",
  2207  			router:   router,
  2208  			method:   "POST",
  2209  			path:     "http://localhost/foo/",
  2210  			wantCode: http.StatusOK,
  2211  		},
  2212  		{
  2213  			title:    "match GET handler",
  2214  			router:   router,
  2215  			method:   "GET",
  2216  			path:     "http://localhost/foo",
  2217  			wantCode: http.StatusOK,
  2218  		},
  2219  		{
  2220  			title:      "match POST handler, redirect strict-slash",
  2221  			router:     router,
  2222  			method:     "POST",
  2223  			path:       "http://localhost/foo",
  2224  			redirectTo: "http://localhost/foo/",
  2225  			wantCode:   http.StatusMovedPermanently,
  2226  		},
  2227  		{
  2228  			title:      "match GET handler, redirect strict-slash",
  2229  			router:     router,
  2230  			method:     "GET",
  2231  			path:       "http://localhost/foo/",
  2232  			redirectTo: "http://localhost/foo",
  2233  			wantCode:   http.StatusMovedPermanently,
  2234  		},
  2235  		{
  2236  			title:    "disallow DELETE method",
  2237  			router:   router,
  2238  			method:   "DELETE",
  2239  			path:     "http://localhost/foo",
  2240  			wantCode: http.StatusMethodNotAllowed,
  2241  		},
  2242  	}
  2243  
  2244  	for _, test := range tests {
  2245  		t.Run(test.title, func(t *testing.T) {
  2246  			testMethodsSubrouter(t, test)
  2247  		})
  2248  	}
  2249  }
  2250  
  2251  // TestMethodsSubrouterPathPrefix matches handlers on subrouters created
  2252  // on a router with a path prefix matcher and method matcher.
  2253  func TestMethodsSubrouterPathPrefix(t *testing.T) {
  2254  	t.Parallel()
  2255  
  2256  	router := NewRouter()
  2257  	router.PathPrefix("/1").Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST"))
  2258  	router.PathPrefix("/1").Methods("DELETE").Subrouter().HandleFunc("/2", methodHandler("DELETE"))
  2259  	router.PathPrefix("/1").Methods("PUT").Subrouter().HandleFunc("/2", methodHandler("PUT"))
  2260  	router.PathPrefix("/1").Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST2"))
  2261  
  2262  	tests := []methodsSubrouterTest{
  2263  		{
  2264  			title:    "match first POST handler",
  2265  			router:   router,
  2266  			method:   "POST",
  2267  			path:     "http://localhost/1/2",
  2268  			wantCode: http.StatusOK,
  2269  		},
  2270  		{
  2271  			title:    "match DELETE handler",
  2272  			router:   router,
  2273  			method:   "DELETE",
  2274  			path:     "http://localhost/1/2",
  2275  			wantCode: http.StatusOK,
  2276  		},
  2277  		{
  2278  			title:    "match PUT handler",
  2279  			router:   router,
  2280  			method:   "PUT",
  2281  			path:     "http://localhost/1/2",
  2282  			wantCode: http.StatusOK,
  2283  		},
  2284  		{
  2285  			title:    "disallow PATCH method",
  2286  			router:   router,
  2287  			method:   "PATCH",
  2288  			path:     "http://localhost/1/2",
  2289  			wantCode: http.StatusMethodNotAllowed,
  2290  		},
  2291  	}
  2292  
  2293  	for _, test := range tests {
  2294  		t.Run(test.title, func(t *testing.T) {
  2295  			testMethodsSubrouter(t, test)
  2296  		})
  2297  	}
  2298  }
  2299  
  2300  // TestMethodsSubrouterSubrouter matches handlers on subrouters produced
  2301  // from method matchers registered on a root subrouter.
  2302  func TestMethodsSubrouterSubrouter(t *testing.T) {
  2303  	t.Parallel()
  2304  
  2305  	router := NewRouter()
  2306  	sub := router.PathPrefix("/1").Subrouter()
  2307  	sub.Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST"))
  2308  	sub.Methods("GET").Subrouter().HandleFunc("/2", methodHandler("GET"))
  2309  	sub.Methods("PATCH").Subrouter().HandleFunc("/2", methodHandler("PATCH"))
  2310  	sub.HandleFunc("/2", methodHandler("PUT")).Subrouter().Methods("PUT")
  2311  	sub.HandleFunc("/2", methodHandler("POST2")).Subrouter().Methods("POST")
  2312  
  2313  	tests := []methodsSubrouterTest{
  2314  		{
  2315  			title:    "match first POST handler",
  2316  			router:   router,
  2317  			method:   "POST",
  2318  			path:     "http://localhost/1/2",
  2319  			wantCode: http.StatusOK,
  2320  		},
  2321  		{
  2322  			title:    "match GET handler",
  2323  			router:   router,
  2324  			method:   "GET",
  2325  			path:     "http://localhost/1/2",
  2326  			wantCode: http.StatusOK,
  2327  		},
  2328  		{
  2329  			title:    "match PATCH handler",
  2330  			router:   router,
  2331  			method:   "PATCH",
  2332  			path:     "http://localhost/1/2",
  2333  			wantCode: http.StatusOK,
  2334  		},
  2335  		{
  2336  			title:    "match PUT handler",
  2337  			router:   router,
  2338  			method:   "PUT",
  2339  			path:     "http://localhost/1/2",
  2340  			wantCode: http.StatusOK,
  2341  		},
  2342  		{
  2343  			title:    "disallow DELETE method",
  2344  			router:   router,
  2345  			method:   "DELETE",
  2346  			path:     "http://localhost/1/2",
  2347  			wantCode: http.StatusMethodNotAllowed,
  2348  		},
  2349  	}
  2350  
  2351  	for _, test := range tests {
  2352  		t.Run(test.title, func(t *testing.T) {
  2353  			testMethodsSubrouter(t, test)
  2354  		})
  2355  	}
  2356  }
  2357  
  2358  // TestMethodsSubrouterPathVariable matches handlers on matching paths
  2359  // with path variables in them.
  2360  func TestMethodsSubrouterPathVariable(t *testing.T) {
  2361  	t.Parallel()
  2362  
  2363  	router := NewRouter()
  2364  	router.Methods("GET").Subrouter().HandleFunc("/foo", methodHandler("GET"))
  2365  	router.Methods("POST").Subrouter().HandleFunc("/{any}", methodHandler("POST"))
  2366  	router.Methods("DELETE").Subrouter().HandleFunc("/1/{any}", methodHandler("DELETE"))
  2367  	router.Methods("PUT").Subrouter().HandleFunc("/1/{any}", methodHandler("PUT"))
  2368  
  2369  	tests := []methodsSubrouterTest{
  2370  		{
  2371  			title:    "match GET handler",
  2372  			router:   router,
  2373  			method:   "GET",
  2374  			path:     "http://localhost/foo",
  2375  			wantCode: http.StatusOK,
  2376  		},
  2377  		{
  2378  			title:    "match POST handler",
  2379  			router:   router,
  2380  			method:   "POST",
  2381  			path:     "http://localhost/foo",
  2382  			wantCode: http.StatusOK,
  2383  		},
  2384  		{
  2385  			title:    "match DELETE handler",
  2386  			router:   router,
  2387  			method:   "DELETE",
  2388  			path:     "http://localhost/1/foo",
  2389  			wantCode: http.StatusOK,
  2390  		},
  2391  		{
  2392  			title:    "match PUT handler",
  2393  			router:   router,
  2394  			method:   "PUT",
  2395  			path:     "http://localhost/1/foo",
  2396  			wantCode: http.StatusOK,
  2397  		},
  2398  		{
  2399  			title:    "disallow PATCH method",
  2400  			router:   router,
  2401  			method:   "PATCH",
  2402  			path:     "http://localhost/1/foo",
  2403  			wantCode: http.StatusMethodNotAllowed,
  2404  		},
  2405  	}
  2406  
  2407  	for _, test := range tests {
  2408  		t.Run(test.title, func(t *testing.T) {
  2409  			testMethodsSubrouter(t, test)
  2410  		})
  2411  	}
  2412  }
  2413  
  2414  func ExampleSetURLVars() {
  2415  	req, _ := http.NewRequest("GET", "/foo", nil)
  2416  	req = SetURLVars(req, map[string]string{"foo": "bar"})
  2417  
  2418  	fmt.Println(Vars(req)["foo"])
  2419  
  2420  	// Output: bar
  2421  }
  2422  
  2423  // testMethodsSubrouter runs an individual methodsSubrouterTest.
  2424  func testMethodsSubrouter(t *testing.T, test methodsSubrouterTest) {
  2425  	// Execute request
  2426  	req, _ := http.NewRequest(test.method, test.path, nil)
  2427  	resp := NewRecorder()
  2428  	test.router.ServeHTTP(resp, req)
  2429  
  2430  	switch test.wantCode {
  2431  	case http.StatusMethodNotAllowed:
  2432  		if resp.Code != http.StatusMethodNotAllowed {
  2433  			t.Errorf(`(%s) Expected "405 Method Not Allowed", but got %d code`, test.title, resp.Code)
  2434  		} else if matchedMethod := resp.Body.String(); matchedMethod != "" {
  2435  			t.Errorf(`(%s) Expected "405 Method Not Allowed", but %q handler was called`, test.title, matchedMethod)
  2436  		}
  2437  
  2438  	case http.StatusMovedPermanently:
  2439  		if gotLocation := resp.HeaderMap.Get("Location"); gotLocation != test.redirectTo {
  2440  			t.Errorf("(%s) Expected %q route-match to redirect to %q, but got %q", test.title, test.method, test.redirectTo, gotLocation)
  2441  		}
  2442  
  2443  	case http.StatusOK:
  2444  		if matchedMethod := resp.Body.String(); matchedMethod != test.method {
  2445  			t.Errorf("(%s) Expected %q handler to be called, but %q handler was called", test.title, test.method, matchedMethod)
  2446  		}
  2447  
  2448  	default:
  2449  		expectedCodes := []int{http.StatusMethodNotAllowed, http.StatusMovedPermanently, http.StatusOK}
  2450  		t.Errorf("(%s) Expected wantCode to be one of: %v, but got %d", test.title, expectedCodes, test.wantCode)
  2451  	}
  2452  }
  2453  
  2454  func TestSubrouterMatching(t *testing.T) {
  2455  	const (
  2456  		none, stdOnly, subOnly uint8 = 0, 1 << 0, 1 << 1
  2457  		both                         = subOnly | stdOnly
  2458  	)
  2459  
  2460  	type request struct {
  2461  		Name    string
  2462  		Request *http.Request
  2463  		Flags   uint8
  2464  	}
  2465  
  2466  	cases := []struct {
  2467  		Name                string
  2468  		Standard, Subrouter func(*Router)
  2469  		Requests            []request
  2470  	}{
  2471  		{
  2472  			"pathPrefix",
  2473  			func(r *Router) {
  2474  				r.PathPrefix("/before").PathPrefix("/after")
  2475  			},
  2476  			func(r *Router) {
  2477  				r.PathPrefix("/before").Subrouter().PathPrefix("/after")
  2478  			},
  2479  			[]request{
  2480  				{"no match final path prefix", newRequest("GET", "/after"), none},
  2481  				{"no match parent path prefix", newRequest("GET", "/before"), none},
  2482  				{"matches append", newRequest("GET", "/before/after"), both},
  2483  				{"matches as prefix", newRequest("GET", "/before/after/1234"), both},
  2484  			},
  2485  		},
  2486  		{
  2487  			"path",
  2488  			func(r *Router) {
  2489  				r.Path("/before").Path("/after")
  2490  			},
  2491  			func(r *Router) {
  2492  				r.Path("/before").Subrouter().Path("/after")
  2493  			},
  2494  			[]request{
  2495  				{"no match subroute path", newRequest("GET", "/after"), none},
  2496  				{"no match parent path", newRequest("GET", "/before"), none},
  2497  				{"no match as prefix", newRequest("GET", "/before/after/1234"), none},
  2498  				{"no match append", newRequest("GET", "/before/after"), none},
  2499  			},
  2500  		},
  2501  		{
  2502  			"host",
  2503  			func(r *Router) {
  2504  				r.Host("before.com").Host("after.com")
  2505  			},
  2506  			func(r *Router) {
  2507  				r.Host("before.com").Subrouter().Host("after.com")
  2508  			},
  2509  			[]request{
  2510  				{"no match before", newRequestHost("GET", "/", "before.com"), none},
  2511  				{"no match other", newRequestHost("GET", "/", "other.com"), none},
  2512  				{"matches after", newRequestHost("GET", "/", "after.com"), none},
  2513  			},
  2514  		},
  2515  		{
  2516  			"queries variant keys",
  2517  			func(r *Router) {
  2518  				r.Queries("foo", "bar").Queries("cricket", "baseball")
  2519  			},
  2520  			func(r *Router) {
  2521  				r.Queries("foo", "bar").Subrouter().Queries("cricket", "baseball")
  2522  			},
  2523  			[]request{
  2524  				{"matches with all", newRequest("GET", "/?foo=bar&cricket=baseball"), both},
  2525  				{"matches with more", newRequest("GET", "/?foo=bar&cricket=baseball&something=else"), both},
  2526  				{"no match with none", newRequest("GET", "/"), none},
  2527  				{"no match with some", newRequest("GET", "/?cricket=baseball"), none},
  2528  			},
  2529  		},
  2530  		{
  2531  			"queries overlapping keys",
  2532  			func(r *Router) {
  2533  				r.Queries("foo", "bar").Queries("foo", "baz")
  2534  			},
  2535  			func(r *Router) {
  2536  				r.Queries("foo", "bar").Subrouter().Queries("foo", "baz")
  2537  			},
  2538  			[]request{
  2539  				{"no match old value", newRequest("GET", "/?foo=bar"), none},
  2540  				{"no match diff value", newRequest("GET", "/?foo=bak"), none},
  2541  				{"no match with none", newRequest("GET", "/"), none},
  2542  				{"matches override", newRequest("GET", "/?foo=baz"), none},
  2543  			},
  2544  		},
  2545  		{
  2546  			"header variant keys",
  2547  			func(r *Router) {
  2548  				r.Headers("foo", "bar").Headers("cricket", "baseball")
  2549  			},
  2550  			func(r *Router) {
  2551  				r.Headers("foo", "bar").Subrouter().Headers("cricket", "baseball")
  2552  			},
  2553  			[]request{
  2554  				{
  2555  					"matches with all",
  2556  					newRequestWithHeaders("GET", "/", "foo", "bar", "cricket", "baseball"),
  2557  					both,
  2558  				},
  2559  				{
  2560  					"matches with more",
  2561  					newRequestWithHeaders("GET", "/", "foo", "bar", "cricket", "baseball", "something", "else"),
  2562  					both,
  2563  				},
  2564  				{"no match with none", newRequest("GET", "/"), none},
  2565  				{"no match with some", newRequestWithHeaders("GET", "/", "cricket", "baseball"), none},
  2566  			},
  2567  		},
  2568  		{
  2569  			"header overlapping keys",
  2570  			func(r *Router) {
  2571  				r.Headers("foo", "bar").Headers("foo", "baz")
  2572  			},
  2573  			func(r *Router) {
  2574  				r.Headers("foo", "bar").Subrouter().Headers("foo", "baz")
  2575  			},
  2576  			[]request{
  2577  				{"no match old value", newRequestWithHeaders("GET", "/", "foo", "bar"), none},
  2578  				{"no match diff value", newRequestWithHeaders("GET", "/", "foo", "bak"), none},
  2579  				{"no match with none", newRequest("GET", "/"), none},
  2580  				{"matches override", newRequestWithHeaders("GET", "/", "foo", "baz"), none},
  2581  			},
  2582  		},
  2583  		{
  2584  			"method",
  2585  			func(r *Router) {
  2586  				r.Methods("POST").Methods("GET")
  2587  			},
  2588  			func(r *Router) {
  2589  				r.Methods("POST").Subrouter().Methods("GET")
  2590  			},
  2591  			[]request{
  2592  				{"matches before", newRequest("POST", "/"), none},
  2593  				{"no match other", newRequest("HEAD", "/"), none},
  2594  				{"matches override", newRequest("GET", "/"), none},
  2595  			},
  2596  		},
  2597  		{
  2598  			"schemes",
  2599  			func(r *Router) {
  2600  				r.Schemes("http").Schemes("https")
  2601  			},
  2602  			func(r *Router) {
  2603  				r.Schemes("http").Subrouter().Schemes("https")
  2604  			},
  2605  			[]request{
  2606  				{"matches overrides", newRequest("GET", "https://www.example.com/"), none},
  2607  				{"matches original", newRequest("GET", "http://www.example.com/"), none},
  2608  				{"no match other", newRequest("GET", "ftp://www.example.com/"), none},
  2609  			},
  2610  		},
  2611  	}
  2612  
  2613  	// case -> request -> router
  2614  	for _, c := range cases {
  2615  		t.Run(c.Name, func(t *testing.T) {
  2616  			for _, req := range c.Requests {
  2617  				t.Run(req.Name, func(t *testing.T) {
  2618  					for _, v := range []struct {
  2619  						Name     string
  2620  						Config   func(*Router)
  2621  						Expected bool
  2622  					}{
  2623  						{"subrouter", c.Subrouter, (req.Flags & subOnly) != 0},
  2624  						{"standard", c.Standard, (req.Flags & stdOnly) != 0},
  2625  					} {
  2626  						r := NewRouter()
  2627  						v.Config(r)
  2628  						if r.Match(req.Request, &RouteMatch{}) != v.Expected {
  2629  							if v.Expected {
  2630  								t.Errorf("expected %v match", v.Name)
  2631  							} else {
  2632  								t.Errorf("expected %v no match", v.Name)
  2633  							}
  2634  						}
  2635  					}
  2636  				})
  2637  			}
  2638  		})
  2639  	}
  2640  }
  2641  
  2642  // verify that copyRouteConf copies fields as expected.
  2643  func Test_copyRouteConf(t *testing.T) {
  2644  	var (
  2645  		m MatcherFunc = func(*http.Request, *RouteMatch) bool {
  2646  			return true
  2647  		}
  2648  		b BuildVarsFunc = func(i map[string]string) map[string]string {
  2649  			return i
  2650  		}
  2651  		r, _ = newRouteRegexp("hi", regexpTypeHost, routeRegexpOptions{})
  2652  	)
  2653  
  2654  	tests := []struct {
  2655  		name string
  2656  		args routeConf
  2657  		want routeConf
  2658  	}{
  2659  		{
  2660  			"empty",
  2661  			routeConf{},
  2662  			routeConf{},
  2663  		},
  2664  		{
  2665  			"full",
  2666  			routeConf{
  2667  				useEncodedPath: true,
  2668  				strictSlash:    true,
  2669  				skipClean:      true,
  2670  				regexp:         routeRegexpGroup{host: r, path: r, queries: []*routeRegexp{r}},
  2671  				matchers:       []matcher{m},
  2672  				buildScheme:    "https",
  2673  				buildVarsFunc:  b,
  2674  			},
  2675  			routeConf{
  2676  				useEncodedPath: true,
  2677  				strictSlash:    true,
  2678  				skipClean:      true,
  2679  				regexp:         routeRegexpGroup{host: r, path: r, queries: []*routeRegexp{r}},
  2680  				matchers:       []matcher{m},
  2681  				buildScheme:    "https",
  2682  				buildVarsFunc:  b,
  2683  			},
  2684  		},
  2685  	}
  2686  
  2687  	for _, tt := range tests {
  2688  		t.Run(tt.name, func(t *testing.T) {
  2689  			// special case some incomparable fields of routeConf before delegating to reflect.DeepEqual
  2690  			got := copyRouteConf(tt.args)
  2691  
  2692  			// funcs not comparable, just compare length of slices
  2693  			if len(got.matchers) != len(tt.want.matchers) {
  2694  				t.Errorf("matchers different lengths: %v %v", len(got.matchers), len(tt.want.matchers))
  2695  			}
  2696  			got.matchers, tt.want.matchers = nil, nil
  2697  
  2698  			// deep equal treats nil slice differently to empty slice so check for zero len first
  2699  			{
  2700  				bothZero := len(got.regexp.queries) == 0 && len(tt.want.regexp.queries) == 0
  2701  				if !bothZero && !reflect.DeepEqual(got.regexp.queries, tt.want.regexp.queries) {
  2702  					t.Errorf("queries unequal: %v %v", got.regexp.queries, tt.want.regexp.queries)
  2703  				}
  2704  				got.regexp.queries, tt.want.regexp.queries = nil, nil
  2705  			}
  2706  
  2707  			// funcs not comparable, just compare nullity
  2708  			if (got.buildVarsFunc == nil) != (tt.want.buildVarsFunc == nil) {
  2709  				t.Errorf("build vars funcs unequal: %v %v", got.buildVarsFunc == nil, tt.want.buildVarsFunc == nil)
  2710  			}
  2711  			got.buildVarsFunc, tt.want.buildVarsFunc = nil, nil
  2712  
  2713  			// finish the deal
  2714  			if !reflect.DeepEqual(got, tt.want) {
  2715  				t.Errorf("route confs unequal: %v %v", got, tt.want)
  2716  			}
  2717  		})
  2718  	}
  2719  }
  2720  
  2721  func TestMethodNotAllowed(t *testing.T) {
  2722  	handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }
  2723  	router := NewRouter()
  2724  	router.HandleFunc("/thing", handler).Methods(http.MethodGet)
  2725  	router.HandleFunc("/something", handler).Methods(http.MethodGet)
  2726  
  2727  	w := NewRecorder()
  2728  	req := newRequest(http.MethodPut, "/thing")
  2729  
  2730  	router.ServeHTTP(w, req)
  2731  
  2732  	if w.Code != http.StatusMethodNotAllowed {
  2733  		t.Fatalf("Expected status code 405 (got %d)", w.Code)
  2734  	}
  2735  }
  2736  
  2737  type customMethodNotAllowedHandler struct {
  2738  	msg string
  2739  }
  2740  
  2741  func (h customMethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  2742  	w.WriteHeader(http.StatusMethodNotAllowed)
  2743  	fmt.Fprint(w, h.msg)
  2744  }
  2745  
  2746  func TestSubrouterCustomMethodNotAllowed(t *testing.T) {
  2747  	handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }
  2748  
  2749  	router := NewRouter()
  2750  	router.HandleFunc("/test", handler).Methods(http.MethodGet)
  2751  	router.MethodNotAllowedHandler = customMethodNotAllowedHandler{msg: "custom router handler"}
  2752  
  2753  	subrouter := router.PathPrefix("/sub").Subrouter()
  2754  	subrouter.HandleFunc("/test", handler).Methods(http.MethodGet)
  2755  	subrouter.MethodNotAllowedHandler = customMethodNotAllowedHandler{msg: "custom sub router handler"}
  2756  
  2757  	testCases := map[string]struct {
  2758  		path   string
  2759  		expMsg string
  2760  	}{
  2761  		"router method not allowed": {
  2762  			path:   "/test",
  2763  			expMsg: "custom router handler",
  2764  		},
  2765  		"subrouter method not allowed": {
  2766  			path:   "/sub/test",
  2767  			expMsg: "custom sub router handler",
  2768  		},
  2769  	}
  2770  
  2771  	for name, tc := range testCases {
  2772  		t.Run(name, func(tt *testing.T) {
  2773  			w := NewRecorder()
  2774  			req := newRequest(http.MethodPut, tc.path)
  2775  
  2776  			router.ServeHTTP(w, req)
  2777  
  2778  			if w.Code != http.StatusMethodNotAllowed {
  2779  				tt.Errorf("Expected status code 405 (got %d)", w.Code)
  2780  			}
  2781  
  2782  			b, err := ioutil.ReadAll(w.Body)
  2783  			if err != nil {
  2784  				tt.Errorf("failed to read body: %v", err)
  2785  			}
  2786  
  2787  			if string(b) != tc.expMsg {
  2788  				tt.Errorf("expected msg %q, got %q", tc.expMsg, string(b))
  2789  			}
  2790  		})
  2791  	}
  2792  }
  2793  
  2794  func TestSubrouterNotFound(t *testing.T) {
  2795  	handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }
  2796  	router := NewRouter()
  2797  	router.Path("/a").Subrouter().HandleFunc("/thing", handler).Methods(http.MethodGet)
  2798  	router.Path("/b").Subrouter().HandleFunc("/something", handler).Methods(http.MethodGet)
  2799  
  2800  	w := NewRecorder()
  2801  	req := newRequest(http.MethodPut, "/not-present")
  2802  
  2803  	router.ServeHTTP(w, req)
  2804  
  2805  	if w.Code != http.StatusNotFound {
  2806  		t.Fatalf("Expected status code 404 (got %d)", w.Code)
  2807  	}
  2808  }
  2809  
  2810  func TestContextMiddleware(t *testing.T) {
  2811  	withTimeout := func(h http.Handler) http.Handler {
  2812  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2813  			ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
  2814  			defer cancel()
  2815  			h.ServeHTTP(w, r.WithContext(ctx))
  2816  		})
  2817  	}
  2818  
  2819  	r := NewRouter()
  2820  	r.Handle("/path/{foo}", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2821  		vars := Vars(r)
  2822  		if vars["foo"] != "bar" {
  2823  			t.Fatal("Expected foo var to be set")
  2824  		}
  2825  	})))
  2826  
  2827  	rec := NewRecorder()
  2828  	req := newRequest("GET", "/path/bar")
  2829  	r.ServeHTTP(rec, req)
  2830  }
  2831  
  2832  // mapToPairs converts a string map to a slice of string pairs
  2833  func mapToPairs(m map[string]string) []string {
  2834  	var i int
  2835  	p := make([]string, len(m)*2)
  2836  	for k, v := range m {
  2837  		p[i] = k
  2838  		p[i+1] = v
  2839  		i += 2
  2840  	}
  2841  	return p
  2842  }
  2843  
  2844  // stringMapEqual checks the equality of two string maps
  2845  func stringMapEqual(m1, m2 map[string]string) bool {
  2846  	nil1 := m1 == nil
  2847  	nil2 := m2 == nil
  2848  	if nil1 != nil2 || len(m1) != len(m2) {
  2849  		return false
  2850  	}
  2851  	for k, v := range m1 {
  2852  		if v != m2[k] {
  2853  			return false
  2854  		}
  2855  	}
  2856  	return true
  2857  }
  2858  
  2859  // stringHandler returns a handler func that writes a message 's' to the
  2860  // http.ResponseWriter.
  2861  func stringHandler(s string) http.HandlerFunc {
  2862  	return func(w http.ResponseWriter, r *http.Request) {
  2863  		w.Write([]byte(s))
  2864  	}
  2865  }
  2866  
  2867  // newRequest is a helper function to create a new request with a method and url.
  2868  // The request returned is a 'server' request as opposed to a 'client' one through
  2869  // simulated write onto the wire and read off of the wire.
  2870  // The differences between requests are detailed in the net/http package.
  2871  func newRequest(method, url string) *http.Request {
  2872  	req, err := http.NewRequest(method, url, nil)
  2873  	if err != nil {
  2874  		panic(err)
  2875  	}
  2876  	// extract the escaped original host+path from url
  2877  	// http://localhost/path/here?v=1#frag -> //localhost/path/here
  2878  	opaque := ""
  2879  	if i := len(req.URL.Scheme); i > 0 {
  2880  		opaque = url[i+1:]
  2881  	}
  2882  
  2883  	if i := strings.LastIndex(opaque, "?"); i > -1 {
  2884  		opaque = opaque[:i]
  2885  	}
  2886  	if i := strings.LastIndex(opaque, "#"); i > -1 {
  2887  		opaque = opaque[:i]
  2888  	}
  2889  
  2890  	// Escaped host+path workaround as detailed in https://golang.org/pkg/net/url/#URL
  2891  	// for < 1.5 client side workaround
  2892  	req.URL.Opaque = opaque
  2893  
  2894  	// Simulate writing to wire
  2895  	var buff bytes.Buffer
  2896  	req.Write(&buff)
  2897  	ioreader := bufio.NewReader(&buff)
  2898  
  2899  	// Parse request off of 'wire'
  2900  	req, err = http.ReadRequest(ioreader)
  2901  	if err != nil {
  2902  		panic(err)
  2903  	}
  2904  	return req
  2905  }
  2906  
  2907  // create a new request with the provided headers
  2908  func newRequestWithHeaders(method, url string, headers ...string) *http.Request {
  2909  	req := newRequest(method, url)
  2910  
  2911  	if len(headers)%2 != 0 {
  2912  		panic(fmt.Sprintf("Expected headers length divisible by 2 but got %v", len(headers)))
  2913  	}
  2914  
  2915  	for i := 0; i < len(headers); i += 2 {
  2916  		req.Header.Set(headers[i], headers[i+1])
  2917  	}
  2918  
  2919  	return req
  2920  }
  2921  
  2922  // newRequestHost a new request with a method, url, and host header
  2923  func newRequestHost(method, url, host string) *http.Request {
  2924  	req := httptest.NewRequest(method, url, nil)
  2925  	req.Host = host
  2926  	return req
  2927  }