github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/api_definition_test.go (about)

     1  package gateway
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"net"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/garyburd/redigo/redis"
    14  
    15  	"github.com/TykTechnologies/tyk/apidef"
    16  	"github.com/TykTechnologies/tyk/config"
    17  	"github.com/TykTechnologies/tyk/test"
    18  	"github.com/TykTechnologies/tyk/user"
    19  )
    20  
    21  func TestURLRewrites(t *testing.T) {
    22  	ts := StartTest()
    23  	defer ts.Close()
    24  
    25  	t.Run("Extended Paths with url_rewrites", func(t *testing.T) {
    26  		BuildAndLoadAPI(func(spec *APISpec) {
    27  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
    28  				json.Unmarshal([]byte(`[
    29  						{
    30                              "path": "/rewrite1",
    31                              "method": "GET",
    32                              "match_pattern": "/rewrite1",
    33                              "rewrite_to": "",
    34                              "triggers": [
    35                                  {
    36                                      "on": "all",
    37                                      "options": {
    38                                          "header_matches": {},
    39                                          "query_val_matches": {
    40                                              "show_env": {
    41                                                  "match_rx": "1"
    42                                              }
    43                                          },
    44                                          "path_part_matches": {},
    45                                          "session_meta_matches": {},
    46                                          "payload_matches": {
    47                                              "match_rx": ""
    48                                          }
    49                                      },
    50                                      "rewrite_to": "/get?show_env=2"
    51                                  }
    52                              ],
    53                              "MatchRegexp": null
    54                          },
    55                          {
    56                              "path": "/rewrite",
    57                              "method": "GET",
    58                              "match_pattern": "/rewrite",
    59                              "rewrite_to": "/get?just_rewrite",
    60                              "triggers": [],
    61                              "MatchRegexp": null
    62  						}
    63  				]`), &v.ExtendedPaths.URLRewrite)
    64  			})
    65  			spec.Proxy.ListenPath = "/"
    66  		})
    67  
    68  		ts.Run(t, []test.TestCase{
    69  			{Path: "/rewrite1?show_env=1", Code: http.StatusOK, BodyMatch: `"URI":"/get?show_env=2"`},
    70  			{Path: "/rewrite", Code: http.StatusOK, BodyMatch: `"URI":"/get?just_rewrite"`},
    71  		}...)
    72  	})
    73  }
    74  
    75  func TestWhitelist(t *testing.T) {
    76  	ts := StartTest()
    77  	defer ts.Close()
    78  
    79  	t.Run("Extended Paths", func(t *testing.T) {
    80  		BuildAndLoadAPI(func(spec *APISpec) {
    81  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
    82  				json.Unmarshal([]byte(`[
    83  					{
    84  						"path": "/reply/{id}",
    85  						"method_actions": {
    86  							"GET": {"action": "reply", "code": 200, "data": "flump"}
    87  						}
    88  					},
    89  					{
    90  						"path": "/get",
    91  						"method_actions": {"GET": {"action": "no_action"}}
    92  					}
    93  				]`), &v.ExtendedPaths.WhiteList)
    94  			})
    95  
    96  			spec.Proxy.ListenPath = "/"
    97  		})
    98  
    99  		ts.Run(t, []test.TestCase{
   100  			// Should mock path
   101  			{Path: "/reply/", Code: http.StatusOK, BodyMatch: "flump"},
   102  			{Path: "/reply/123", Code: http.StatusOK, BodyMatch: "flump"},
   103  			// Should get original upstream response
   104  			{Path: "/get", Code: http.StatusOK, BodyMatch: `"Url":"/get"`},
   105  			// Reject not whitelisted (but know by upstream) path
   106  			{Method: "POST", Path: "/post", Code: http.StatusForbidden},
   107  		}...)
   108  	})
   109  
   110  	t.Run("Simple Paths", func(t *testing.T) {
   111  		BuildAndLoadAPI(func(spec *APISpec) {
   112  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   113  				v.Paths.WhiteList = []string{"/simple", "/regex/{id}/test"}
   114  				v.UseExtendedPaths = false
   115  			})
   116  
   117  			spec.Proxy.ListenPath = "/"
   118  		})
   119  
   120  		ts.Run(t, []test.TestCase{
   121  			// Should mock path
   122  			{Path: "/simple", Code: http.StatusOK},
   123  			{Path: "/regex/123/test", Code: http.StatusOK},
   124  			{Path: "/regex/123/differ", Code: http.StatusForbidden},
   125  			{Path: "/", Code: http.StatusForbidden},
   126  		}...)
   127  	})
   128  
   129  	t.Run("Test #1944", func(t *testing.T) {
   130  		BuildAndLoadAPI(func(spec *APISpec) {
   131  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   132  				v.Paths.WhiteList = []string{"/foo/{fooId}$", "/foo/{fooId}/bar/{barId}$", "/baz/{bazId}"}
   133  				v.UseExtendedPaths = false
   134  			})
   135  
   136  			spec.Proxy.ListenPath = "/"
   137  		})
   138  
   139  		ts.Run(t, []test.TestCase{
   140  			{Path: "/foo", Code: http.StatusForbidden},
   141  			{Path: "/foo/", Code: http.StatusOK},
   142  			{Path: "/foo/1", Code: http.StatusOK},
   143  			{Path: "/foo/1/bar", Code: http.StatusForbidden},
   144  			{Path: "/foo/1/bar/", Code: http.StatusOK},
   145  			{Path: "/foo/1/bar/1", Code: http.StatusOK},
   146  			{Path: "/", Code: http.StatusForbidden},
   147  			{Path: "/baz", Code: http.StatusForbidden},
   148  			{Path: "/baz/", Code: http.StatusOK},
   149  			{Path: "/baz/1", Code: http.StatusOK},
   150  			{Path: "/baz/1/", Code: http.StatusOK},
   151  			{Path: "/baz/1/bazz", Code: http.StatusOK},
   152  		}...)
   153  	})
   154  
   155  	t.Run("Case Sensitivity", func(t *testing.T) {
   156  		BuildAndLoadAPI(func(spec *APISpec) {
   157  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   158  				v.Paths.WhiteList = []string{"/Foo", "/bar"}
   159  				v.UseExtendedPaths = false
   160  			})
   161  
   162  			spec.Proxy.ListenPath = "/"
   163  		})
   164  
   165  		ts.Run(t, []test.TestCase{
   166  			{Path: "/foo", Code: http.StatusForbidden},
   167  			{Path: "/Foo", Code: http.StatusOK},
   168  			{Path: "/bar", Code: http.StatusOK},
   169  			{Path: "/Bar", Code: http.StatusForbidden},
   170  		}...)
   171  	})
   172  }
   173  
   174  func TestBlacklist(t *testing.T) {
   175  	ts := StartTest()
   176  	defer ts.Close()
   177  
   178  	t.Run("Extended Paths", func(t *testing.T) {
   179  		BuildAndLoadAPI(func(spec *APISpec) {
   180  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   181  				json.Unmarshal([]byte(`[
   182  					{
   183  						"path": "/blacklist/literal",
   184  						"method_actions": {"GET": {"action": "no_action"}}
   185  					},
   186  					{
   187  						"path": "/blacklist/{id}/test",
   188  						"method_actions": {"GET": {"action": "no_action"}}
   189  					}
   190  				]`), &v.ExtendedPaths.BlackList)
   191  			})
   192  
   193  			spec.Proxy.ListenPath = "/"
   194  		})
   195  
   196  		ts.Run(t, []test.TestCase{
   197  			{Path: "/blacklist/literal", Code: http.StatusForbidden},
   198  			{Path: "/blacklist/123/test", Code: http.StatusForbidden},
   199  
   200  			{Path: "/blacklist/123/different", Code: http.StatusOK},
   201  			// POST method not blacklisted
   202  			{Method: "POST", Path: "/blacklist/literal", Code: http.StatusOK},
   203  		}...)
   204  	})
   205  
   206  	t.Run("Simple Paths", func(t *testing.T) {
   207  		BuildAndLoadAPI(func(spec *APISpec) {
   208  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   209  				v.Paths.BlackList = []string{"/blacklist/literal", "/blacklist/{id}/test"}
   210  				v.UseExtendedPaths = false
   211  			})
   212  
   213  			spec.Proxy.ListenPath = "/"
   214  		})
   215  
   216  		ts.Run(t, []test.TestCase{
   217  			{Path: "/blacklist/literal", Code: http.StatusForbidden},
   218  			{Path: "/blacklist/123/test", Code: http.StatusForbidden},
   219  
   220  			{Path: "/blacklist/123/different", Code: http.StatusOK},
   221  			// POST method also blacklisted
   222  			{Method: "POST", Path: "/blacklist/literal", Code: http.StatusForbidden},
   223  		}...)
   224  	})
   225  
   226  	t.Run("Case Sensitivity", func(t *testing.T) {
   227  		BuildAndLoadAPI(func(spec *APISpec) {
   228  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   229  				v.Paths.BlackList = []string{"/Foo", "/bar"}
   230  				v.UseExtendedPaths = false
   231  			})
   232  
   233  			spec.Proxy.ListenPath = "/"
   234  		})
   235  
   236  		ts.Run(t, []test.TestCase{
   237  			{Path: "/foo", Code: http.StatusOK},
   238  			{Path: "/Foo", Code: http.StatusForbidden},
   239  			{Path: "/bar", Code: http.StatusForbidden},
   240  			{Path: "/Bar", Code: http.StatusOK},
   241  		}...)
   242  	})
   243  }
   244  
   245  func TestConflictingPaths(t *testing.T) {
   246  	ts := StartTest()
   247  	defer ts.Close()
   248  
   249  	BuildAndLoadAPI(func(spec *APISpec) {
   250  		UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   251  			json.Unmarshal([]byte(`[
   252  				{
   253  					"path": "/metadata/{id}",
   254  					"method_actions": {"GET": {"action": "no_action"}}
   255  				},
   256  				{
   257  					"path": "/metadata/purge",
   258  					"method_actions": {"POST": {"action": "no_action"}}
   259  				}
   260  			]`), &v.ExtendedPaths.WhiteList)
   261  		})
   262  
   263  		spec.Proxy.ListenPath = "/"
   264  	})
   265  
   266  	ts.Run(t, []test.TestCase{
   267  		// Should ignore auth check
   268  		{Method: "POST", Path: "/customer-servicing/documents/metadata/purge", Code: http.StatusOK},
   269  		{Method: "GET", Path: "/customer-servicing/documents/metadata/{id}", Code: http.StatusOK},
   270  	}...)
   271  }
   272  
   273  func TestIgnored(t *testing.T) {
   274  	ts := StartTest()
   275  	defer ts.Close()
   276  
   277  	t.Run("Extended Paths", func(t *testing.T) {
   278  		BuildAndLoadAPI(func(spec *APISpec) {
   279  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   280  				json.Unmarshal([]byte(`[
   281  					{
   282  						"path": "/ignored/literal",
   283  						"method_actions": {"GET": {"action": "no_action"}}
   284  					},
   285  					{
   286  						"path": "/ignored/{id}/test",
   287  						"method_actions": {"GET": {"action": "no_action"}}
   288  					}
   289  				]`), &v.ExtendedPaths.Ignored)
   290  			})
   291  
   292  			spec.UseKeylessAccess = false
   293  			spec.Proxy.ListenPath = "/"
   294  		})
   295  
   296  		ts.Run(t, []test.TestCase{
   297  			// Should ignore auth check
   298  			{Path: "/ignored/literal", Code: http.StatusOK},
   299  			{Path: "/ignored/123/test", Code: http.StatusOK},
   300  			// Only GET is ignored
   301  			{Method: "POST", Path: "/ext/ignored/literal", Code: 401},
   302  
   303  			{Path: "/", Code: 401},
   304  		}...)
   305  	})
   306  
   307  	t.Run("Simple Paths", func(t *testing.T) {
   308  		BuildAndLoadAPI(func(spec *APISpec) {
   309  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   310  				v.Paths.Ignored = []string{"/ignored/literal", "/ignored/{id}/test"}
   311  				v.UseExtendedPaths = false
   312  			})
   313  
   314  			spec.UseKeylessAccess = false
   315  			spec.Proxy.ListenPath = "/"
   316  		})
   317  
   318  		ts.Run(t, []test.TestCase{
   319  			// Should ignore auth check
   320  			{Path: "/ignored/literal", Code: http.StatusOK},
   321  			{Path: "/ignored/123/test", Code: http.StatusOK},
   322  			// All methods ignored
   323  			{Method: "POST", Path: "/ext/ignored/literal", Code: http.StatusOK},
   324  
   325  			{Path: "/", Code: 401},
   326  		}...)
   327  	})
   328  }
   329  
   330  func TestWhitelistMethodWithAdditionalMiddleware(t *testing.T) {
   331  	ts := StartTest()
   332  	defer ts.Close()
   333  
   334  	t.Run("Extended Paths", func(t *testing.T) {
   335  		BuildAndLoadAPI(func(spec *APISpec) {
   336  			spec.UseKeylessAccess = true
   337  			spec.Proxy.ListenPath = "/"
   338  
   339  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   340  				v.UseExtendedPaths = true
   341  
   342  				json.Unmarshal([]byte(`[
   343  					{
   344  						"path": "/get",
   345  						"method_actions": {"GET": {"action": "no_action"}}
   346  					}
   347  				]`), &v.ExtendedPaths.WhiteList)
   348  				json.Unmarshal([]byte(`[
   349  					{
   350  						"add_headers": {"foo": "bar"},
   351  						"path": "/get",
   352  						"method": "GET",
   353  						"act_on": false
   354  					}
   355  				]`), &v.ExtendedPaths.TransformResponseHeader)
   356  			})
   357  			spec.ResponseProcessors = []apidef.ResponseProcessor{{Name: "header_injector"}}
   358  
   359  		})
   360  
   361  		//headers := map[string]string{"foo": "bar"}
   362  		ts.Run(t, []test.TestCase{
   363  			//Should get original upstream response
   364  			//{Method: "GET", Path: "/get", Code: http.StatusOK, HeadersMatch: headers},
   365  			//Reject not whitelisted (but know by upstream) path
   366  			{Method: "POST", Path: "/get", Code: http.StatusForbidden},
   367  		}...)
   368  	})
   369  }
   370  
   371  func TestSyncAPISpecsDashboardSuccess(t *testing.T) {
   372  	// Test Dashboard
   373  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   374  		if r.URL.Path == "/system/apis" {
   375  			w.Write([]byte(`{"Status": "OK", "Nonce": "1", "Message": [{"api_definition": {}}]}`))
   376  		} else {
   377  			t.Fatal("Unknown dashboard API request", r)
   378  		}
   379  	}))
   380  	defer ts.Close()
   381  
   382  	apisMu.Lock()
   383  	apisByID = make(map[string]*APISpec)
   384  	apisMu.Unlock()
   385  
   386  	globalConf := config.Global()
   387  	globalConf.UseDBAppConfigs = true
   388  	globalConf.AllowInsecureConfigs = true
   389  	globalConf.DBAppConfOptions.ConnectionString = ts.URL
   390  	config.SetGlobal(globalConf)
   391  
   392  	defer ResetTestConfig()
   393  
   394  	var wg sync.WaitGroup
   395  	wg.Add(1)
   396  	msg := redis.Message{Data: []byte(`{"Command": "ApiUpdated"}`)}
   397  	handled := func(got NotificationCommand) {
   398  		if want := NoticeApiUpdated; got != want {
   399  			t.Fatalf("want %q, got %q", want, got)
   400  		}
   401  	}
   402  	handleRedisEvent(msg, handled, wg.Done)
   403  
   404  	// Since we already know that reload is queued
   405  	ReloadTick <- time.Time{}
   406  
   407  	// Wait for the reload to finish, then check it worked
   408  	wg.Wait()
   409  	apisMu.RLock()
   410  	if len(apisByID) != 1 {
   411  		t.Error("Should return array with one spec", apisByID)
   412  	}
   413  	apisMu.RUnlock()
   414  }
   415  
   416  func TestRoundRobin(t *testing.T) {
   417  	rr := RoundRobin{}
   418  	for _, want := range []int{0, 1, 2, 0} {
   419  		if got := rr.WithLen(3); got != want {
   420  			t.Errorf("RR Pos wrong: want %d got %d", want, got)
   421  		}
   422  	}
   423  	if got, want := rr.WithLen(0), 0; got != want {
   424  		t.Errorf("RR Pos of 0 wrong: want %d got %d", want, got)
   425  	}
   426  }
   427  
   428  func setupKeepalive(conn net.Conn) error {
   429  	tcpConn := conn.(*net.TCPConn)
   430  	if err := tcpConn.SetKeepAlive(true); err != nil {
   431  		return err
   432  	}
   433  	if err := tcpConn.SetKeepAlivePeriod(30 * time.Second); err != nil {
   434  		return err
   435  	}
   436  	return nil
   437  }
   438  
   439  type customListener struct {
   440  	L net.Listener
   441  }
   442  
   443  func (ln *customListener) Init(addr string) (err error) {
   444  	ln.L, err = net.Listen("tcp", addr)
   445  	return
   446  }
   447  
   448  func (ln *customListener) Accept() (conn net.Conn, err error) {
   449  	c, err := ln.L.Accept()
   450  	if err != nil {
   451  		return
   452  	}
   453  
   454  	if err = setupKeepalive(c); err != nil {
   455  		c.Close()
   456  		return
   457  	}
   458  
   459  	handshake := make([]byte, 6)
   460  	if _, err = io.ReadFull(c, handshake); err != nil {
   461  		return
   462  	}
   463  
   464  	idLenBuf := make([]byte, 1)
   465  	if _, err = io.ReadFull(c, idLenBuf); err != nil {
   466  		return
   467  	}
   468  
   469  	idLen := uint8(idLenBuf[0])
   470  	id := make([]byte, idLen)
   471  	if _, err = io.ReadFull(c, id); err != nil {
   472  		return
   473  	}
   474  
   475  	return c, nil
   476  }
   477  
   478  func (ln *customListener) Close() error {
   479  	return ln.L.Close()
   480  }
   481  
   482  func TestDefaultVersion(t *testing.T) {
   483  	ts := StartTest()
   484  	defer ts.Close()
   485  
   486  	key := testPrepareDefaultVersion()
   487  
   488  	authHeaders := map[string]string{"authorization": key}
   489  
   490  	ts.Run(t, []test.TestCase{
   491  		{Path: "/foo", Headers: authHeaders, Code: http.StatusForbidden},      // Not whitelisted for default v2
   492  		{Path: "/bar", Headers: authHeaders, Code: http.StatusOK},             // Whitelisted for default v2
   493  		{Path: "/foo?v=v1", Headers: authHeaders, Code: http.StatusOK},        // Allowed for v1
   494  		{Path: "/bar?v=v1", Headers: authHeaders, Code: http.StatusForbidden}, // Not allowed for v1
   495  	}...)
   496  }
   497  
   498  func BenchmarkDefaultVersion(b *testing.B) {
   499  	b.ReportAllocs()
   500  
   501  	ts := StartTest()
   502  	defer ts.Close()
   503  
   504  	key := testPrepareDefaultVersion()
   505  
   506  	authHeaders := map[string]string{"authorization": key}
   507  
   508  	for i := 0; i < b.N; i++ {
   509  		ts.Run(
   510  			b,
   511  			[]test.TestCase{
   512  				{Path: "/foo", Headers: authHeaders, Code: http.StatusForbidden},      // Not whitelisted for default v2
   513  				{Path: "/bar", Headers: authHeaders, Code: http.StatusOK},             // Whitelisted for default v2
   514  				{Path: "/foo?v=v1", Headers: authHeaders, Code: http.StatusOK},        // Allowed for v1
   515  				{Path: "/bar?v=v1", Headers: authHeaders, Code: http.StatusForbidden}, // Not allowed for v1
   516  			}...,
   517  		)
   518  	}
   519  }
   520  
   521  func testPrepareDefaultVersion() string {
   522  	BuildAndLoadAPI(func(spec *APISpec) {
   523  		v1 := apidef.VersionInfo{Name: "v1"}
   524  		v1.Name = "v1"
   525  		v1.Paths.WhiteList = []string{"/foo"}
   526  
   527  		v2 := apidef.VersionInfo{Name: "v2"}
   528  		v2.Paths.WhiteList = []string{"/bar"}
   529  
   530  		spec.VersionDefinition.Location = urlParamLocation
   531  		spec.VersionDefinition.Key = "v"
   532  		spec.VersionData.NotVersioned = false
   533  
   534  		spec.VersionData.Versions["v1"] = v1
   535  		spec.VersionData.Versions["v2"] = v2
   536  		spec.VersionData.DefaultVersion = "v2"
   537  		spec.Proxy.ListenPath = "/"
   538  
   539  		spec.UseKeylessAccess = false
   540  	})
   541  
   542  	return CreateSession(func(s *user.SessionState) {
   543  		s.AccessRights = map[string]user.AccessDefinition{"test": {
   544  			APIID: "test", Versions: []string{"v1", "v2"},
   545  		}}
   546  	})
   547  }
   548  
   549  func TestGetVersionFromRequest(t *testing.T) {
   550  	ts := StartTest()
   551  	defer ts.Close()
   552  
   553  	versionInfo := apidef.VersionInfo{}
   554  	versionInfo.Paths.WhiteList = []string{"/foo"}
   555  	versionInfo.Paths.BlackList = []string{"/bar"}
   556  
   557  	t.Run("Header location", func(t *testing.T) {
   558  		BuildAndLoadAPI(func(spec *APISpec) {
   559  			spec.Proxy.ListenPath = "/"
   560  			spec.VersionData.NotVersioned = false
   561  			spec.VersionDefinition.Location = headerLocation
   562  			spec.VersionDefinition.Key = "X-API-Version"
   563  			spec.VersionData.Versions["v1"] = versionInfo
   564  		})
   565  
   566  		headers := map[string]string{"X-API-Version": "v1"}
   567  
   568  		ts.Run(t, []test.TestCase{
   569  			{Path: "/foo", Code: http.StatusOK, Headers: headers},
   570  			{Path: "/bar", Code: http.StatusForbidden, Headers: headers},
   571  		}...)
   572  	})
   573  
   574  	t.Run("URL param location", func(t *testing.T) {
   575  		BuildAndLoadAPI(func(spec *APISpec) {
   576  			spec.Proxy.ListenPath = "/"
   577  			spec.VersionData.NotVersioned = false
   578  			spec.VersionDefinition.Location = urlParamLocation
   579  			spec.VersionDefinition.Key = "version"
   580  			spec.VersionData.Versions["v2"] = versionInfo
   581  		})
   582  
   583  		ts.Run(t, []test.TestCase{
   584  			{Path: "/foo?version=v2", Code: http.StatusOK},
   585  			{Path: "/bar?version=v2", Code: http.StatusForbidden},
   586  		}...)
   587  	})
   588  
   589  	t.Run("URL location", func(t *testing.T) {
   590  		BuildAndLoadAPI(func(spec *APISpec) {
   591  			spec.Proxy.ListenPath = "/"
   592  			spec.VersionData.NotVersioned = false
   593  			spec.VersionDefinition.Location = urlLocation
   594  			spec.VersionData.Versions["v3"] = versionInfo
   595  		})
   596  
   597  		ts.Run(t, []test.TestCase{
   598  			{Path: "/v3/foo", Code: http.StatusOK},
   599  			{Path: "/v3/bar", Code: http.StatusForbidden},
   600  		}...)
   601  	})
   602  }
   603  
   604  func BenchmarkGetVersionFromRequest(b *testing.B) {
   605  	b.ReportAllocs()
   606  	ts := StartTest()
   607  	defer ts.Close()
   608  
   609  	versionInfo := apidef.VersionInfo{}
   610  	versionInfo.Paths.WhiteList = []string{"/foo"}
   611  	versionInfo.Paths.BlackList = []string{"/bar"}
   612  
   613  	b.Run("Header location", func(b *testing.B) {
   614  		b.ReportAllocs()
   615  		BuildAndLoadAPI(func(spec *APISpec) {
   616  			spec.Proxy.ListenPath = "/"
   617  			spec.VersionData.NotVersioned = false
   618  			spec.VersionDefinition.Location = headerLocation
   619  			spec.VersionDefinition.Key = "X-API-Version"
   620  			spec.VersionData.Versions["v1"] = versionInfo
   621  		})
   622  
   623  		headers := map[string]string{"X-API-Version": "v1"}
   624  
   625  		for i := 0; i < b.N; i++ {
   626  			ts.Run(b, []test.TestCase{
   627  				{Path: "/foo", Code: http.StatusOK, Headers: headers},
   628  				{Path: "/bar", Code: http.StatusForbidden, Headers: headers},
   629  			}...)
   630  		}
   631  	})
   632  
   633  	b.Run("URL param location", func(b *testing.B) {
   634  		b.ReportAllocs()
   635  		BuildAndLoadAPI(func(spec *APISpec) {
   636  			spec.Proxy.ListenPath = "/"
   637  			spec.VersionData.NotVersioned = false
   638  			spec.VersionDefinition.Location = urlParamLocation
   639  			spec.VersionDefinition.Key = "version"
   640  			spec.VersionData.Versions["v2"] = versionInfo
   641  		})
   642  
   643  		for i := 0; i < b.N; i++ {
   644  			ts.Run(b, []test.TestCase{
   645  				{Path: "/foo?version=v2", Code: http.StatusOK},
   646  				{Path: "/bar?version=v2", Code: http.StatusForbidden},
   647  			}...)
   648  		}
   649  	})
   650  
   651  	b.Run("URL location", func(b *testing.B) {
   652  		b.ReportAllocs()
   653  		BuildAndLoadAPI(func(spec *APISpec) {
   654  			spec.Proxy.ListenPath = "/"
   655  			spec.VersionData.NotVersioned = false
   656  			spec.VersionDefinition.Location = urlLocation
   657  			spec.VersionData.Versions["v3"] = versionInfo
   658  		})
   659  
   660  		for i := 0; i < b.N; i++ {
   661  			ts.Run(b, []test.TestCase{
   662  				{Path: "/v3/foo", Code: http.StatusOK},
   663  				{Path: "/v3/bar", Code: http.StatusForbidden},
   664  			}...)
   665  		}
   666  	})
   667  }
   668  
   669  func TestSyncAPISpecsDashboardJSONFailure(t *testing.T) {
   670  	// Test Dashboard
   671  	callNum := 0
   672  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   673  		if r.URL.Path == "/system/apis" {
   674  			if callNum == 0 {
   675  				w.Write([]byte(`{"Status": "OK", "Nonce": "1", "Message": [{"api_definition": {}}]}`))
   676  			} else {
   677  				w.Write([]byte(`{"Status": "OK", "Nonce": "1", "Message": "this is a string"`))
   678  			}
   679  
   680  			callNum += 1
   681  		} else {
   682  			t.Fatal("Unknown dashboard API request", r)
   683  		}
   684  	}))
   685  	defer ts.Close()
   686  
   687  	apisMu.Lock()
   688  	apisByID = make(map[string]*APISpec)
   689  	apisMu.Unlock()
   690  
   691  	globalConf := config.Global()
   692  	globalConf.UseDBAppConfigs = true
   693  	globalConf.AllowInsecureConfigs = true
   694  	globalConf.DBAppConfOptions.ConnectionString = ts.URL
   695  	config.SetGlobal(globalConf)
   696  
   697  	defer ResetTestConfig()
   698  
   699  	var wg sync.WaitGroup
   700  	wg.Add(1)
   701  	msg := redis.Message{Data: []byte(`{"Command": "ApiUpdated"}`)}
   702  	handled := func(got NotificationCommand) {
   703  		if want := NoticeApiUpdated; got != want {
   704  			t.Fatalf("want %q, got %q", want, got)
   705  		}
   706  	}
   707  	handleRedisEvent(msg, handled, wg.Done)
   708  
   709  	// Since we already know that reload is queued
   710  	ReloadTick <- time.Time{}
   711  
   712  	// Wait for the reload to finish, then check it worked
   713  	wg.Wait()
   714  	apisMu.RLock()
   715  	if len(apisByID) != 1 {
   716  		t.Error("should return array with one spec", apisByID)
   717  	}
   718  	apisMu.RUnlock()
   719  
   720  	// Second call
   721  
   722  	var wg2 sync.WaitGroup
   723  	wg2.Add(1)
   724  	handleRedisEvent(msg, handled, wg2.Done)
   725  
   726  	// Since we already know that reload is queued
   727  	ReloadTick <- time.Time{}
   728  
   729  	// Wait for the reload to finish, then check it worked
   730  	wg2.Wait()
   731  	apisMu.RLock()
   732  	if len(apisByID) != 1 {
   733  		t.Error("second call should return array with one spec", apisByID)
   734  	}
   735  	apisMu.RUnlock()
   736  
   737  }