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