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 }