go.uber.org/yarpc@v1.72.1/yarpcconfig/configurator_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package yarpcconfig 22 23 import ( 24 "reflect" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/golang/mock/gomock" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 "go.uber.org/yarpc" 33 "go.uber.org/yarpc/api/transport/transporttest" 34 "go.uber.org/yarpc/internal/interpolate" 35 "go.uber.org/yarpc/internal/whitespace" 36 "go.uber.org/zap/zapcore" 37 "gopkg.in/yaml.v2" 38 ) 39 40 func TestConfiguratorRegisterErrors(t *testing.T) { 41 require.Panics(t, func() { New().MustRegisterTransport(TransportSpec{}) }) 42 err := New().RegisterTransport(TransportSpec{}) 43 require.Error(t, err, "expected failure") 44 assert.Contains(t, err.Error(), "name is required") 45 err = New().RegisterTransport(TransportSpec{Name: "test"}) 46 require.Error(t, err, "expected failure") 47 assert.Contains(t, err.Error(), "invalid TransportSpec for \"test\":") 48 49 require.Panics(t, func() { New().MustRegisterPeerChooser(PeerChooserSpec{}) }) 50 err = New().RegisterPeerChooser(PeerChooserSpec{}) 51 require.Error(t, err, "expected failure") 52 assert.Contains(t, err.Error(), "name is required") 53 err = New().RegisterPeerChooser(PeerChooserSpec{Name: "test"}) 54 require.Error(t, err, "expected failure") 55 assert.Contains(t, err.Error(), "invalid PeerChooserSpec for \"test\":") 56 57 require.Panics(t, func() { New().MustRegisterPeerList(PeerListSpec{}) }) 58 err = New().RegisterPeerList(PeerListSpec{}) 59 require.Error(t, err, "expected failure") 60 assert.Contains(t, err.Error(), "name is required") 61 err = New().RegisterPeerList(PeerListSpec{Name: "test"}) 62 require.Error(t, err, "expected failure") 63 assert.Contains(t, err.Error(), "invalid PeerListSpec for \"test\":") 64 65 require.Panics(t, func() { New().MustRegisterPeerListUpdater(PeerListUpdaterSpec{}) }) 66 err = New().RegisterPeerListUpdater(PeerListUpdaterSpec{}) 67 require.Error(t, err, "expected failure") 68 assert.Contains(t, err.Error(), "name is required") 69 err = New().RegisterPeerListUpdater(PeerListUpdaterSpec{Name: "test"}) 70 require.Error(t, err, "expected failure") 71 assert.Contains(t, err.Error(), "invalid PeerListUpdaterSpec for \"test\":") 72 } 73 74 func TestConfigurator(t *testing.T) { 75 // For better test output, we have split the test case into a testCase 76 // struct that defines the test parameters and a different anonymous 77 // struct used in the table test to give a name to the test. 78 79 type testCase struct { 80 // List of TransportSpecs to register with the Configurator 81 specs []TransportSpec 82 83 // Name of the service or empty string to use the default 84 serviceName string 85 86 // YAML to parse using the configurator 87 give string 88 89 // Environment variables 90 env map[string]string 91 92 // If non-empty, an error is expected where the message matches all 93 // strings in this slice 94 wantErr []string 95 96 // For success cases, the output Config must match this 97 wantConfig yarpc.Config 98 } 99 100 tests := []struct { 101 desc string 102 test func(*testing.T, *gomock.Controller) testCase 103 }{ 104 { 105 desc: "no inbounds or outbounds", 106 test: func(*testing.T, *gomock.Controller) (tt testCase) { 107 tt.serviceName = "foo" 108 tt.give = whitespace.Expand(``) 109 tt.wantConfig = yarpc.Config{Name: "foo"} 110 return 111 }, 112 }, 113 { 114 desc: "application error debug logging", 115 test: func(*testing.T, *gomock.Controller) (tt testCase) { 116 debugLevel := zapcore.DebugLevel 117 118 tt.serviceName = "foo" 119 tt.give = whitespace.Expand(` 120 logging: 121 levels: 122 applicationError: debug 123 `) 124 tt.wantConfig = yarpc.Config{ 125 Name: "foo", 126 Logging: yarpc.LoggingConfig{ 127 Levels: yarpc.LogLevelConfig{ 128 ApplicationError: &debugLevel, 129 }, 130 }, 131 } 132 return 133 }, 134 }, 135 { 136 desc: "server error debug logging", 137 test: func(*testing.T, *gomock.Controller) (tt testCase) { 138 debugLevel := zapcore.DebugLevel 139 140 tt.serviceName = "foo" 141 tt.give = whitespace.Expand(` 142 logging: 143 levels: 144 serverError: debug 145 `) 146 tt.wantConfig = yarpc.Config{ 147 Name: "foo", 148 Logging: yarpc.LoggingConfig{ 149 Levels: yarpc.LogLevelConfig{ 150 ServerError: &debugLevel, 151 }, 152 }, 153 } 154 return 155 }, 156 }, 157 { 158 desc: "client error debug logging", 159 test: func(*testing.T, *gomock.Controller) (tt testCase) { 160 debugLevel := zapcore.DebugLevel 161 162 tt.serviceName = "foo" 163 tt.give = whitespace.Expand(` 164 logging: 165 levels: 166 clientError: debug 167 `) 168 tt.wantConfig = yarpc.Config{ 169 Name: "foo", 170 Logging: yarpc.LoggingConfig{ 171 Levels: yarpc.LogLevelConfig{ 172 ClientError: &debugLevel, 173 }, 174 }, 175 } 176 return 177 }, 178 }, 179 { 180 desc: "client and server error debug logging", 181 test: func(*testing.T, *gomock.Controller) (tt testCase) { 182 debugLevel := zapcore.DebugLevel 183 infoLevel := zapcore.InfoLevel 184 185 tt.serviceName = "foo" 186 tt.give = whitespace.Expand(` 187 logging: 188 levels: 189 serverError: debug 190 clientError: info 191 `) 192 tt.wantConfig = yarpc.Config{ 193 Name: "foo", 194 Logging: yarpc.LoggingConfig{ 195 Levels: yarpc.LogLevelConfig{ 196 ClientError: &infoLevel, 197 ServerError: &debugLevel, 198 }, 199 }, 200 } 201 return 202 }, 203 }, 204 { 205 desc: "outbound success info logging", 206 test: func(*testing.T, *gomock.Controller) (tt testCase) { 207 debugLevel := zapcore.DebugLevel 208 infoLevel := zapcore.InfoLevel 209 210 tt.serviceName = "foo" 211 tt.give = whitespace.Expand(` 212 logging: 213 levels: 214 success: debug 215 outbound: 216 success: info 217 `) 218 tt.wantConfig = yarpc.Config{ 219 Name: "foo", 220 Logging: yarpc.LoggingConfig{ 221 Levels: yarpc.LogLevelConfig{ 222 Success: &debugLevel, 223 Outbound: yarpc.DirectionalLogLevelConfig{ 224 Success: &infoLevel, 225 }, 226 }, 227 }, 228 } 229 return 230 }, 231 }, 232 { 233 desc: "outbound success server error info logging", 234 test: func(*testing.T, *gomock.Controller) (tt testCase) { 235 debugLevel := zapcore.DebugLevel 236 infoLevel := zapcore.InfoLevel 237 238 tt.serviceName = "foo" 239 tt.give = whitespace.Expand(` 240 logging: 241 levels: 242 serverError: debug 243 outbound: 244 serverError: info 245 `) 246 tt.wantConfig = yarpc.Config{ 247 Name: "foo", 248 Logging: yarpc.LoggingConfig{ 249 Levels: yarpc.LogLevelConfig{ 250 ServerError: &debugLevel, 251 Outbound: yarpc.DirectionalLogLevelConfig{ 252 ServerError: &infoLevel, 253 }, 254 }, 255 }, 256 } 257 return 258 }, 259 }, 260 { 261 desc: "outbound success client error info logging", 262 test: func(*testing.T, *gomock.Controller) (tt testCase) { 263 debugLevel := zapcore.DebugLevel 264 infoLevel := zapcore.InfoLevel 265 266 tt.serviceName = "foo" 267 tt.give = whitespace.Expand(` 268 logging: 269 levels: 270 clientError: debug 271 outbound: 272 clientError: info 273 `) 274 tt.wantConfig = yarpc.Config{ 275 Name: "foo", 276 Logging: yarpc.LoggingConfig{ 277 Levels: yarpc.LogLevelConfig{ 278 ClientError: &debugLevel, 279 Outbound: yarpc.DirectionalLogLevelConfig{ 280 ClientError: &infoLevel, 281 }, 282 }, 283 }, 284 } 285 return 286 }, 287 }, 288 { 289 desc: "inbound success server error info logging", 290 test: func(*testing.T, *gomock.Controller) (tt testCase) { 291 debugLevel := zapcore.DebugLevel 292 infoLevel := zapcore.InfoLevel 293 294 tt.serviceName = "foo" 295 tt.give = whitespace.Expand(` 296 logging: 297 levels: 298 serverError: debug 299 inbound: 300 serverError: info 301 `) 302 tt.wantConfig = yarpc.Config{ 303 Name: "foo", 304 Logging: yarpc.LoggingConfig{ 305 Levels: yarpc.LogLevelConfig{ 306 ServerError: &debugLevel, 307 Inbound: yarpc.DirectionalLogLevelConfig{ 308 ServerError: &infoLevel, 309 }, 310 }, 311 }, 312 } 313 return 314 }, 315 }, 316 { 317 desc: "inbound success client error info logging", 318 test: func(*testing.T, *gomock.Controller) (tt testCase) { 319 debugLevel := zapcore.DebugLevel 320 infoLevel := zapcore.InfoLevel 321 322 tt.serviceName = "foo" 323 tt.give = whitespace.Expand(` 324 logging: 325 levels: 326 clientError: debug 327 inbound: 328 clientError: info 329 `) 330 tt.wantConfig = yarpc.Config{ 331 Name: "foo", 332 Logging: yarpc.LoggingConfig{ 333 Levels: yarpc.LogLevelConfig{ 334 ClientError: &debugLevel, 335 Inbound: yarpc.DirectionalLogLevelConfig{ 336 ClientError: &infoLevel, 337 }, 338 }, 339 }, 340 } 341 return 342 }, 343 }, 344 { 345 desc: "metric tags blocklist", 346 test: func(*testing.T, *gomock.Controller) (tt testCase) { 347 tt.serviceName = "foo" 348 tt.give = whitespace.Expand(` 349 metrics: 350 tagsBlocklist: 351 - "routing_delegate" 352 `) 353 tt.wantConfig = yarpc.Config{ 354 Name: "foo", 355 Metrics: yarpc.MetricsConfig{ 356 TagsBlocklist: []string{ 357 "routing_delegate", 358 }, 359 }, 360 } 361 return 362 }, 363 }, 364 { 365 desc: "application error, invalid type", 366 test: func(*testing.T, *gomock.Controller) (tt testCase) { 367 tt.give = whitespace.Expand(` 368 logging: 369 levels: 370 applicationError: 42 371 `) 372 tt.wantErr = []string{ 373 "error decoding 'logging.levels.applicationError':", 374 "could not decode Zap log level:", 375 "expected type 'string', got unconvertible type 'int'", 376 } 377 return 378 }, 379 }, 380 { 381 desc: "application error, invalid level", 382 test: func(*testing.T, *gomock.Controller) (tt testCase) { 383 tt.give = whitespace.Expand(` 384 logging: 385 levels: 386 applicationError: not a level 387 `) 388 tt.wantErr = []string{ 389 "error decoding 'logging.levels.applicationError':", 390 "could not decode Zap log level:", 391 `unrecognized level: "not a level"`, 392 } 393 return 394 }, 395 }, 396 { 397 desc: "server error, invalid type", 398 test: func(*testing.T, *gomock.Controller) (tt testCase) { 399 tt.give = whitespace.Expand(` 400 logging: 401 levels: 402 serverError: 42 403 `) 404 tt.wantErr = []string{ 405 "error decoding 'logging.levels.serverError':", 406 "could not decode Zap log level:", 407 "expected type 'string', got unconvertible type 'int'", 408 } 409 return 410 }, 411 }, 412 { 413 desc: "server error, invalid level", 414 test: func(*testing.T, *gomock.Controller) (tt testCase) { 415 tt.give = whitespace.Expand(` 416 logging: 417 levels: 418 serverError: not a level 419 `) 420 tt.wantErr = []string{ 421 "error decoding 'logging.levels.serverError':", 422 "could not decode Zap log level:", 423 `unrecognized level: "not a level"`, 424 } 425 return 426 }, 427 }, 428 { 429 desc: "client error, invalid type", 430 test: func(*testing.T, *gomock.Controller) (tt testCase) { 431 tt.give = whitespace.Expand(` 432 logging: 433 levels: 434 clientError: 42 435 `) 436 tt.wantErr = []string{ 437 "error decoding 'logging.levels.clientError':", 438 "could not decode Zap log level:", 439 "expected type 'string', got unconvertible type 'int'", 440 } 441 return 442 }, 443 }, 444 { 445 desc: "client error, invalid level", 446 test: func(*testing.T, *gomock.Controller) (tt testCase) { 447 tt.give = whitespace.Expand(` 448 logging: 449 levels: 450 clientError: not a level 451 `) 452 tt.wantErr = []string{ 453 "error decoding 'logging.levels.clientError':", 454 "could not decode Zap log level:", 455 `unrecognized level: "not a level"`, 456 } 457 return 458 }, 459 }, 460 { 461 desc: "invalid usage of application error with server error", 462 test: func(*testing.T, *gomock.Controller) (tt testCase) { 463 tt.give = whitespace.Expand(` 464 logging: 465 levels: 466 applicationError: debug 467 serverError: debug 468 `) 469 tt.wantErr = []string{ 470 "invalid logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 471 } 472 return 473 }, 474 }, 475 { 476 desc: "invalid usage of outbound application error with client error", 477 test: func(*testing.T, *gomock.Controller) (tt testCase) { 478 tt.give = whitespace.Expand(` 479 logging: 480 levels: 481 outbound: 482 applicationError: debug 483 clientError: debug 484 `) 485 tt.wantErr = []string{ 486 "invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 487 } 488 return 489 }, 490 }, 491 { 492 desc: "invalid usage of outbound failure with server error", 493 test: func(*testing.T, *gomock.Controller) (tt testCase) { 494 tt.give = whitespace.Expand(` 495 logging: 496 levels: 497 outbound: 498 failure: debug 499 serverError: debug 500 `) 501 tt.wantErr = []string{ 502 "invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 503 } 504 return 505 }, 506 }, 507 { 508 desc: "invalid usage of outbound failure with client error", 509 test: func(*testing.T, *gomock.Controller) (tt testCase) { 510 tt.give = whitespace.Expand(` 511 logging: 512 levels: 513 outbound: 514 failure: debug 515 clientError: debug 516 `) 517 tt.wantErr = []string{ 518 "invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 519 } 520 return 521 }, 522 }, 523 524 { 525 desc: "invalid usage of outbound application error with server error", 526 test: func(*testing.T, *gomock.Controller) (tt testCase) { 527 tt.give = whitespace.Expand(` 528 logging: 529 levels: 530 outbound: 531 applicationError: debug 532 serverError: debug 533 `) 534 tt.wantErr = []string{ 535 "invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 536 } 537 return 538 }, 539 }, 540 { 541 desc: "invalid usage of outbound application error with client error", 542 test: func(*testing.T, *gomock.Controller) (tt testCase) { 543 tt.give = whitespace.Expand(` 544 logging: 545 levels: 546 outbound: 547 applicationError: debug 548 clientError: debug 549 `) 550 tt.wantErr = []string{ 551 "invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 552 } 553 return 554 }, 555 }, 556 { 557 desc: "invalid usage of outbound failure with server error", 558 test: func(*testing.T, *gomock.Controller) (tt testCase) { 559 tt.give = whitespace.Expand(` 560 logging: 561 levels: 562 outbound: 563 failure: debug 564 serverError: debug 565 `) 566 tt.wantErr = []string{ 567 "invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 568 } 569 return 570 }, 571 }, 572 { 573 desc: "invalid usage of outbound failure with client error", 574 test: func(*testing.T, *gomock.Controller) (tt testCase) { 575 tt.give = whitespace.Expand(` 576 logging: 577 levels: 578 outbound: 579 failure: debug 580 clientError: debug 581 `) 582 tt.wantErr = []string{ 583 "invalid outbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 584 } 585 return 586 }, 587 }, 588 589 { 590 desc: "invalid usage of inbound application error with client error", 591 test: func(*testing.T, *gomock.Controller) (tt testCase) { 592 tt.give = whitespace.Expand(` 593 logging: 594 levels: 595 inbound: 596 applicationError: debug 597 clientError: debug 598 `) 599 tt.wantErr = []string{ 600 "invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 601 } 602 return 603 }, 604 }, 605 { 606 desc: "invalid usage of inbound failure with server error", 607 test: func(*testing.T, *gomock.Controller) (tt testCase) { 608 tt.give = whitespace.Expand(` 609 logging: 610 levels: 611 inbound: 612 failure: debug 613 serverError: debug 614 `) 615 tt.wantErr = []string{ 616 "invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 617 } 618 return 619 }, 620 }, 621 { 622 desc: "invalid usage of inbound failure with client error", 623 test: func(*testing.T, *gomock.Controller) (tt testCase) { 624 tt.give = whitespace.Expand(` 625 logging: 626 levels: 627 inbound: 628 failure: debug 629 clientError: debug 630 `) 631 tt.wantErr = []string{ 632 "invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 633 } 634 return 635 }, 636 }, 637 638 { 639 desc: "invalid usage of inbound application error with server error", 640 test: func(*testing.T, *gomock.Controller) (tt testCase) { 641 tt.give = whitespace.Expand(` 642 logging: 643 levels: 644 inbound: 645 applicationError: debug 646 serverError: debug 647 `) 648 tt.wantErr = []string{ 649 "invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 650 } 651 return 652 }, 653 }, 654 { 655 desc: "invalid usage of inbound application error with client error", 656 test: func(*testing.T, *gomock.Controller) (tt testCase) { 657 tt.give = whitespace.Expand(` 658 logging: 659 levels: 660 inbound: 661 applicationError: debug 662 clientError: debug 663 `) 664 tt.wantErr = []string{ 665 "invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 666 } 667 return 668 }, 669 }, 670 { 671 desc: "invalid usage of inbound failure with server error", 672 test: func(*testing.T, *gomock.Controller) (tt testCase) { 673 tt.give = whitespace.Expand(` 674 logging: 675 levels: 676 inbound: 677 failure: debug 678 serverError: debug 679 `) 680 tt.wantErr = []string{ 681 "invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 682 } 683 return 684 }, 685 }, 686 { 687 desc: "invalid usage of inbound failure with client error", 688 test: func(*testing.T, *gomock.Controller) (tt testCase) { 689 tt.give = whitespace.Expand(` 690 logging: 691 levels: 692 inbound: 693 failure: debug 694 clientError: debug 695 `) 696 tt.wantErr = []string{ 697 "invalid inbound logging configuration, failure/applicationError configuration can not be used with serverError/clientError", 698 } 699 return 700 }, 701 }, 702 { 703 desc: "unknown inbound", 704 test: func(*testing.T, *gomock.Controller) (tt testCase) { 705 tt.give = whitespace.Expand(` 706 inbounds: 707 bar: {} 708 `) 709 tt.wantErr = []string{ 710 "failed to load inbound", 711 `unknown transport "bar"`, 712 } 713 return 714 }, 715 }, 716 { 717 desc: "unknown implicit outbound", 718 test: func(*testing.T, *gomock.Controller) (tt testCase) { 719 tt.give = whitespace.Expand(` 720 outbounds: 721 myservice: 722 http: {url: "http://localhost:8080/yarpc"} 723 `) 724 tt.wantErr = []string{ 725 `failed to load configuration for outbound "myservice"`, 726 `unknown transport "http"`, 727 } 728 return 729 }, 730 }, 731 { 732 desc: "unknown unary outbound", 733 test: func(*testing.T, *gomock.Controller) (tt testCase) { 734 tt.give = whitespace.Expand(` 735 outbounds: 736 someservice: 737 unary: 738 tchannel: 739 address: localhost:4040 740 `) 741 tt.wantErr = []string{ 742 `failed to load configuration for outbound "someservice"`, 743 `unknown transport "tchannel"`, 744 } 745 return 746 }, 747 }, 748 { 749 desc: "unknown oneway outbound", 750 test: func(*testing.T, *gomock.Controller) (tt testCase) { 751 tt.give = whitespace.Expand(` 752 outbounds: 753 keyvalue: 754 oneway: 755 kafka: {queue: requests} 756 `) 757 tt.wantErr = []string{ 758 `failed to load configuration for outbound "keyvalue"`, 759 `unknown transport "kafka"`, 760 } 761 return 762 }, 763 }, 764 { 765 desc: "unknown stream outbound", 766 test: func(*testing.T, *gomock.Controller) (tt testCase) { 767 tt.give = whitespace.Expand(` 768 outbounds: 769 keyvalue: 770 stream: 771 kafka: {queue: requests} 772 `) 773 tt.wantErr = []string{ 774 `failed to load configuration for outbound "keyvalue"`, 775 `unknown transport "kafka"`, 776 } 777 return 778 }, 779 }, 780 { 781 desc: "unused transport", 782 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 783 type fooTransportConfig struct{ Items []int } 784 785 tt.serviceName = "foo" 786 tt.give = whitespace.Expand(` 787 transports: 788 bar: 789 items: [1, 2, 3] 790 `) 791 792 foo := mockTransportSpecBuilder{ 793 Name: "bar", 794 TransportConfig: reflect.TypeOf(&fooTransportConfig{}), 795 }.Build(mockCtrl) 796 797 tt.specs = []TransportSpec{foo.Spec()} 798 tt.wantConfig = yarpc.Config{Name: "foo"} 799 800 return 801 }, 802 }, 803 { 804 desc: "transport config error", 805 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 806 type transportConfig struct{ KeepAlive time.Duration } 807 type inboundConfig struct{ Address string } 808 809 tt.give = whitespace.Expand(` 810 inbounds: 811 http: {address: ":80"} 812 transports: 813 http: 814 keepAlive: thirty 815 `) 816 817 http := mockTransportSpecBuilder{ 818 Name: "http", 819 TransportConfig: reflect.TypeOf(&transportConfig{}), 820 InboundConfig: reflect.TypeOf(&inboundConfig{}), 821 }.Build(mockCtrl) 822 tt.specs = []TransportSpec{http.Spec()} 823 824 tt.wantErr = []string{ 825 "failed to decode transport configuration:", 826 "error decoding 'KeepAlive'", 827 `invalid duration`, 828 `thirty`, 829 } 830 831 return 832 }, 833 }, 834 { 835 desc: "inbound", 836 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 837 type inboundConfig struct{ Address string } 838 tt.serviceName = "myservice" 839 tt.give = whitespace.Expand(` 840 inbounds: 841 http: {address: ":80"} 842 `) 843 844 http := mockTransportSpecBuilder{ 845 Name: "http", 846 TransportConfig: _typeOfEmptyStruct, 847 InboundConfig: reflect.TypeOf(&inboundConfig{}), 848 }.Build(mockCtrl) 849 850 transport := transporttest.NewMockTransport(mockCtrl) 851 inbound := transporttest.NewMockInbound(mockCtrl) 852 853 http.EXPECT(). 854 BuildTransport(struct{}{}, kitMatcher{ServiceName: "myservice"}). 855 Return(transport, nil) 856 http.EXPECT(). 857 BuildInbound( 858 &inboundConfig{Address: ":80"}, transport, 859 kitMatcher{ServiceName: "myservice"}). 860 Return(inbound, nil) 861 862 tt.specs = []TransportSpec{http.Spec()} 863 tt.wantConfig = yarpc.Config{ 864 Name: "myservice", 865 Inbounds: yarpc.Inbounds{inbound}, 866 } 867 return 868 }, 869 }, 870 { 871 desc: "inbounds unsupported", 872 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 873 tt.give = whitespace.Expand(` 874 inbounds: 875 outgoing-only: 876 foo: bar 877 `) 878 879 spec := mockTransportSpecBuilder{ 880 Name: "outgoing-only", 881 TransportConfig: _typeOfEmptyStruct, 882 }.Build(mockCtrl) 883 tt.specs = []TransportSpec{spec.Spec()} 884 tt.wantErr = []string{ 885 `transport "outgoing-only" does not support inbound requests`, 886 } 887 888 return 889 }, 890 }, 891 { 892 desc: "duplicate inbounds", 893 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 894 type inboundConfig struct{ Address string } 895 tt.serviceName = "foo" 896 tt.give = whitespace.Expand(` 897 inbounds: 898 http: 899 address: ":8080" 900 http2: 901 type: http 902 address: ":8081" 903 `) 904 905 http := mockTransportSpecBuilder{ 906 Name: "http", 907 TransportConfig: _typeOfEmptyStruct, 908 InboundConfig: reflect.TypeOf(&inboundConfig{}), 909 }.Build(mockCtrl) 910 transport := transporttest.NewMockTransport(mockCtrl) 911 http.EXPECT(). 912 BuildTransport(struct{}{}, kitMatcher{ServiceName: "foo"}). 913 Return(transport, nil) 914 915 inbound := transporttest.NewMockInbound(mockCtrl) 916 inbound2 := transporttest.NewMockInbound(mockCtrl) 917 918 http.EXPECT(). 919 BuildInbound( 920 &inboundConfig{Address: ":8080"}, 921 transport, 922 kitMatcher{ServiceName: "foo"}). 923 Return(inbound, nil) 924 http.EXPECT(). 925 BuildInbound( 926 &inboundConfig{Address: ":8081"}, 927 transport, 928 kitMatcher{ServiceName: "foo"}). 929 Return(inbound2, nil) 930 931 tt.specs = []TransportSpec{http.Spec()} 932 tt.wantConfig = yarpc.Config{ 933 Name: "foo", 934 Inbounds: yarpc.Inbounds{inbound, inbound2}, 935 } 936 937 return 938 }, 939 }, 940 { 941 desc: "disabled inbound", 942 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 943 type inboundConfig struct{ Address string } 944 tt.serviceName = "foo" 945 tt.give = whitespace.Expand(` 946 inbounds: 947 http: 948 disabled: true 949 address: ":8080" 950 http2: 951 type: http 952 address: ":8081" 953 `) 954 955 http := mockTransportSpecBuilder{ 956 Name: "http", 957 TransportConfig: _typeOfEmptyStruct, 958 InboundConfig: reflect.TypeOf(&inboundConfig{}), 959 }.Build(mockCtrl) 960 961 transport := transporttest.NewMockTransport(mockCtrl) 962 inbound := transporttest.NewMockInbound(mockCtrl) 963 964 http.EXPECT(). 965 BuildTransport(struct{}{}, kitMatcher{ServiceName: "foo"}). 966 Return(transport, nil) 967 http.EXPECT(). 968 BuildInbound( 969 &inboundConfig{Address: ":8081"}, 970 transport, 971 kitMatcher{ServiceName: "foo"}). 972 Return(inbound, nil) 973 974 tt.specs = []TransportSpec{http.Spec()} 975 tt.wantConfig = yarpc.Config{ 976 Name: "foo", 977 Inbounds: yarpc.Inbounds{inbound}, 978 } 979 980 return 981 }, 982 }, 983 { 984 desc: "inbound error", 985 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 986 tt.serviceName = "foo" 987 tt.give = whitespace.Expand(` 988 inbounds: 989 foo: 990 unexpected: bar 991 `) 992 993 foo := mockTransportSpecBuilder{ 994 Name: "foo", 995 TransportConfig: _typeOfEmptyStruct, 996 InboundConfig: _typeOfEmptyStruct, 997 }.Build(mockCtrl) 998 tt.specs = []TransportSpec{foo.Spec()} 999 tt.wantErr = []string{ 1000 "failed to decode inbound configuration: failed to decode struct", 1001 "invalid keys: unexpected", 1002 } 1003 1004 return 1005 }, 1006 }, 1007 { 1008 desc: "implicit outbound no support", 1009 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1010 tt.give = whitespace.Expand(` 1011 outbounds: 1012 myservice: 1013 sink: 1014 foo: bar 1015 `) 1016 1017 sink := mockTransportSpecBuilder{ 1018 Name: "sink", 1019 TransportConfig: _typeOfEmptyStruct, 1020 InboundConfig: _typeOfEmptyStruct, 1021 }.Build(mockCtrl) 1022 1023 tt.specs = []TransportSpec{sink.Spec()} 1024 tt.wantErr = []string{`transport "sink" does not support outbound requests`} 1025 return 1026 }, 1027 }, 1028 { 1029 desc: "implicit outbound unary", 1030 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1031 type outboundConfig struct{ Address string } 1032 tt.serviceName = "foo" 1033 tt.give = whitespace.Expand(` 1034 outbounds: 1035 bar: 1036 tchannel: 1037 address: localhost:4040 1038 `) 1039 1040 tchan := mockTransportSpecBuilder{ 1041 Name: "tchannel", 1042 TransportConfig: _typeOfEmptyStruct, 1043 UnaryOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1044 }.Build(mockCtrl) 1045 1046 transport := transporttest.NewMockTransport(mockCtrl) 1047 outbound := transporttest.NewMockUnaryOutbound(mockCtrl) 1048 1049 tchan.EXPECT(). 1050 BuildTransport(struct{}{}, kitMatcher{ServiceName: "foo"}). 1051 Return(transport, nil) 1052 tchan.EXPECT(). 1053 BuildUnaryOutbound( 1054 &outboundConfig{Address: "localhost:4040"}, 1055 transport, 1056 kitMatcher{ServiceName: "foo", OutboundServiceName: "bar"}). 1057 Return(outbound, nil) 1058 1059 tt.specs = []TransportSpec{tchan.Spec()} 1060 tt.wantConfig = yarpc.Config{ 1061 Name: "foo", 1062 Outbounds: yarpc.Outbounds{ 1063 "bar": { 1064 Unary: outbound, 1065 }, 1066 }, 1067 } 1068 1069 return 1070 }, 1071 }, 1072 { 1073 desc: "implicit outbound oneway", 1074 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1075 type transportConfig struct{ Address string } 1076 type outboundConfig struct{ Queue string } 1077 tt.serviceName = "foo" 1078 tt.give = whitespace.Expand(` 1079 outbounds: 1080 bar: 1081 redis: 1082 queue: requests 1083 transports: 1084 redis: 1085 address: localhost:6379 1086 `) 1087 1088 redis := mockTransportSpecBuilder{ 1089 Name: "redis", 1090 TransportConfig: reflect.TypeOf(transportConfig{}), 1091 OnewayOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1092 }.Build(mockCtrl) 1093 1094 transport := transporttest.NewMockTransport(mockCtrl) 1095 outbound := transporttest.NewMockOnewayOutbound(mockCtrl) 1096 1097 redis.EXPECT(). 1098 BuildTransport( 1099 transportConfig{Address: "localhost:6379"}, 1100 kitMatcher{ServiceName: "foo"}). 1101 Return(transport, nil) 1102 redis.EXPECT(). 1103 BuildOnewayOutbound( 1104 &outboundConfig{Queue: "requests"}, 1105 transport, 1106 kitMatcher{ServiceName: "foo", OutboundServiceName: "bar"}). 1107 Return(outbound, nil) 1108 1109 tt.specs = []TransportSpec{redis.Spec()} 1110 tt.wantConfig = yarpc.Config{ 1111 Name: "foo", 1112 Outbounds: yarpc.Outbounds{ 1113 "bar": { 1114 Oneway: outbound, 1115 }, 1116 }, 1117 } 1118 1119 return 1120 }, 1121 }, 1122 { 1123 desc: "implicit outbound stream", 1124 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1125 type transportConfig struct{ Address string } 1126 type outboundConfig struct{ Queue string } 1127 tt.serviceName = "foo" 1128 tt.give = whitespace.Expand(` 1129 outbounds: 1130 bar: 1131 fake-stream-transport: 1132 queue: requests 1133 transports: 1134 fake-stream-transport: 1135 address: localhost:6379 1136 `) 1137 1138 redis := mockTransportSpecBuilder{ 1139 Name: "fake-stream-transport", 1140 TransportConfig: reflect.TypeOf(transportConfig{}), 1141 StreamOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1142 }.Build(mockCtrl) 1143 1144 transport := transporttest.NewMockTransport(mockCtrl) 1145 outbound := transporttest.NewMockStreamOutbound(mockCtrl) 1146 1147 redis.EXPECT(). 1148 BuildTransport( 1149 transportConfig{Address: "localhost:6379"}, 1150 kitMatcher{ServiceName: "foo"}). 1151 Return(transport, nil) 1152 redis.EXPECT(). 1153 BuildStreamOutbound( 1154 &outboundConfig{Queue: "requests"}, 1155 transport, 1156 kitMatcher{ServiceName: "foo", OutboundServiceName: "bar"}). 1157 Return(outbound, nil) 1158 1159 tt.specs = []TransportSpec{redis.Spec()} 1160 tt.wantConfig = yarpc.Config{ 1161 Name: "foo", 1162 Outbounds: yarpc.Outbounds{ 1163 "bar": { 1164 Stream: outbound, 1165 }, 1166 }, 1167 } 1168 1169 return 1170 }, 1171 }, 1172 { 1173 desc: "implicit outbound unary, oneway, stream", 1174 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1175 type transportConfig struct{ KeepAlive time.Duration } 1176 type outboundConfig struct{ URL string } 1177 tt.serviceName = "foo" 1178 tt.give = whitespace.Expand(` 1179 outbounds: 1180 baz: 1181 http: 1182 url: http://localhost:8080/yarpc 1183 transports: 1184 http: 1185 keepAlive: 60s 1186 `) 1187 1188 http := mockTransportSpecBuilder{ 1189 Name: "http", 1190 TransportConfig: reflect.TypeOf(&transportConfig{}), 1191 OnewayOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1192 StreamOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1193 UnaryOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1194 }.Build(mockCtrl) 1195 1196 transport := transporttest.NewMockTransport(mockCtrl) 1197 unary := transporttest.NewMockUnaryOutbound(mockCtrl) 1198 oneway := transporttest.NewMockOnewayOutbound(mockCtrl) 1199 stream := transporttest.NewMockStreamOutbound(mockCtrl) 1200 1201 http.EXPECT(). 1202 BuildTransport( 1203 &transportConfig{KeepAlive: time.Minute}, 1204 kitMatcher{ServiceName: "foo"}). 1205 Return(transport, nil) 1206 1207 outcfg := outboundConfig{URL: "http://localhost:8080/yarpc"} 1208 http.EXPECT(). 1209 BuildUnaryOutbound(&outcfg, transport, kitMatcher{ServiceName: "foo", OutboundServiceName: "baz"}). 1210 Return(unary, nil) 1211 http.EXPECT(). 1212 BuildOnewayOutbound(&outcfg, transport, kitMatcher{ServiceName: "foo", OutboundServiceName: "baz"}). 1213 Return(oneway, nil) 1214 http.EXPECT(). 1215 BuildStreamOutbound(&outcfg, transport, kitMatcher{ServiceName: "foo", OutboundServiceName: "baz"}). 1216 Return(stream, nil) 1217 1218 tt.specs = []TransportSpec{http.Spec()} 1219 tt.wantConfig = yarpc.Config{ 1220 Name: "foo", 1221 Outbounds: yarpc.Outbounds{ 1222 "baz": { 1223 Unary: unary, 1224 Oneway: oneway, 1225 Stream: stream, 1226 }, 1227 }, 1228 } 1229 1230 return 1231 }, 1232 }, 1233 { 1234 desc: "implicit outbound error", 1235 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1236 type outboundConfig struct{ URL string } 1237 tt.give = whitespace.Expand(` 1238 outbounds: 1239 qux: 1240 http: 1241 uri: http://localhost:8080/yarpc 1242 `) 1243 1244 http := mockTransportSpecBuilder{ 1245 Name: "http", 1246 TransportConfig: _typeOfEmptyStruct, 1247 OnewayOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1248 UnaryOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1249 StreamOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1250 }.Build(mockCtrl) 1251 1252 tt.specs = []TransportSpec{http.Spec()} 1253 tt.wantErr = []string{ 1254 `failed to add outbound "qux"`, 1255 "failed to decode oneway outbound configuration", 1256 "failed to decode unary outbound configuration", 1257 "failed to decode stream outbound configuration", 1258 "invalid keys: uri", 1259 } 1260 1261 return 1262 }, 1263 }, 1264 { 1265 desc: "explicit outbounds", 1266 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1267 type ( 1268 httpOutboundConfig struct{ URL string } 1269 httpTransportConfig struct{ KeepAlive time.Duration } 1270 redisOutboundConfig struct{ Queue string } 1271 redisTransportConfig struct{ Address string } 1272 ) 1273 1274 tt.serviceName = "myservice" 1275 tt.give = whitespace.Expand(` 1276 transports: 1277 http: 1278 keepAlive: 5m 1279 redis: 1280 address: "127.0.0.1:6379" 1281 outbounds: 1282 foo: 1283 unary: 1284 http: 1285 url: http://localhost:8080/yarpc/v1 1286 oneway: 1287 http: 1288 url: http://localhost:8081/yarpc/v2 1289 stream: 1290 http: 1291 url: http://localhost:8081/yarpc/v3 1292 bar: 1293 oneway: 1294 redis: 1295 queue: requests 1296 `) 1297 1298 http := mockTransportSpecBuilder{ 1299 Name: "http", 1300 TransportConfig: reflect.TypeOf(httpTransportConfig{}), 1301 OnewayOutboundConfig: reflect.TypeOf(httpOutboundConfig{}), 1302 StreamOutboundConfig: reflect.TypeOf(httpOutboundConfig{}), 1303 UnaryOutboundConfig: reflect.TypeOf(httpOutboundConfig{}), 1304 }.Build(mockCtrl) 1305 1306 redis := mockTransportSpecBuilder{ 1307 Name: "redis", 1308 TransportConfig: reflect.TypeOf(redisTransportConfig{}), 1309 OnewayOutboundConfig: reflect.TypeOf(redisOutboundConfig{}), 1310 }.Build(mockCtrl) 1311 1312 httpTransport := transporttest.NewMockTransport(mockCtrl) 1313 httpUnary := transporttest.NewMockUnaryOutbound(mockCtrl) 1314 httpOneway := transporttest.NewMockOnewayOutbound(mockCtrl) 1315 httpStream := transporttest.NewMockStreamOutbound(mockCtrl) 1316 http.EXPECT(). 1317 BuildTransport( 1318 httpTransportConfig{KeepAlive: 5 * time.Minute}, 1319 kitMatcher{ServiceName: "myservice"}). 1320 Return(httpTransport, nil) 1321 1322 redisTransport := transporttest.NewMockTransport(mockCtrl) 1323 redisOneway := transporttest.NewMockOnewayOutbound(mockCtrl) 1324 redis.EXPECT(). 1325 BuildTransport( 1326 redisTransportConfig{Address: "127.0.0.1:6379"}, 1327 kitMatcher{ServiceName: "myservice"}). 1328 Return(redisTransport, nil) 1329 1330 http.EXPECT(). 1331 BuildUnaryOutbound( 1332 httpOutboundConfig{URL: "http://localhost:8080/yarpc/v1"}, 1333 httpTransport, 1334 kitMatcher{ServiceName: "myservice", OutboundServiceName: "foo"}). 1335 Return(httpUnary, nil) 1336 http.EXPECT(). 1337 BuildOnewayOutbound( 1338 httpOutboundConfig{URL: "http://localhost:8081/yarpc/v2"}, 1339 httpTransport, 1340 kitMatcher{ServiceName: "myservice", OutboundServiceName: "foo"}). 1341 Return(httpOneway, nil) 1342 http.EXPECT(). 1343 BuildStreamOutbound( 1344 httpOutboundConfig{URL: "http://localhost:8081/yarpc/v3"}, 1345 httpTransport, 1346 kitMatcher{ServiceName: "myservice", OutboundServiceName: "foo"}). 1347 Return(httpStream, nil) 1348 1349 redis.EXPECT(). 1350 BuildOnewayOutbound( 1351 redisOutboundConfig{Queue: "requests"}, 1352 redisTransport, 1353 kitMatcher{ServiceName: "myservice", OutboundServiceName: "bar"}). 1354 Return(redisOneway, nil) 1355 1356 tt.specs = []TransportSpec{http.Spec(), redis.Spec()} 1357 tt.wantConfig = yarpc.Config{ 1358 Name: "myservice", 1359 Outbounds: yarpc.Outbounds{ 1360 "foo": {Unary: httpUnary, Oneway: httpOneway, Stream: httpStream}, 1361 "bar": {Oneway: redisOneway}, 1362 }, 1363 } 1364 1365 return 1366 }, 1367 }, 1368 { 1369 desc: "explicit unary error", 1370 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1371 type outboundConfig struct{ URL string } 1372 tt.give = whitespace.Expand(` 1373 outbounds: 1374 hello: 1375 unary: 1376 http: 1377 scheme: https 1378 host: localhost 1379 port: 8088 1380 path: /yarpc 1381 `) 1382 1383 http := mockTransportSpecBuilder{ 1384 Name: "http", 1385 TransportConfig: _typeOfEmptyStruct, 1386 OnewayOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1387 UnaryOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1388 }.Build(mockCtrl) 1389 1390 tt.specs = []TransportSpec{http.Spec()} 1391 tt.wantErr = []string{ 1392 "failed to decode unary outbound configuration", 1393 "invalid keys: host, path, port, scheme", 1394 } 1395 1396 return 1397 }, 1398 }, 1399 { 1400 desc: "explicit oneway error", 1401 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1402 type outboundConfig struct{ Address string } 1403 tt.give = whitespace.Expand(` 1404 outbounds: 1405 hello: 1406 oneway: 1407 redis: 1408 host: localhost 1409 port: 6379 1410 `) 1411 1412 redis := mockTransportSpecBuilder{ 1413 Name: "redis", 1414 TransportConfig: _typeOfEmptyStruct, 1415 OnewayOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1416 }.Build(mockCtrl) 1417 1418 tt.specs = []TransportSpec{redis.Spec()} 1419 tt.wantErr = []string{ 1420 "failed to decode oneway outbound configuration", 1421 "invalid keys: host, port", 1422 } 1423 1424 return 1425 }, 1426 }, 1427 { 1428 desc: "explicit stream error", 1429 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1430 type outboundConfig struct{ Address string } 1431 tt.give = whitespace.Expand(` 1432 outbounds: 1433 hello: 1434 stream: 1435 redis: 1436 host: localhost 1437 port: 6379 1438 `) 1439 1440 redis := mockTransportSpecBuilder{ 1441 Name: "redis", 1442 TransportConfig: _typeOfEmptyStruct, 1443 StreamOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1444 }.Build(mockCtrl) 1445 1446 tt.specs = []TransportSpec{redis.Spec()} 1447 tt.wantErr = []string{ 1448 "failed to decode stream outbound configuration", 1449 "invalid keys: host, port", 1450 } 1451 1452 return 1453 }, 1454 }, 1455 { 1456 desc: "explicit unary not supported", 1457 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1458 type outboundConfig struct{ Queue string } 1459 tt.give = whitespace.Expand(` 1460 outbounds: 1461 bar: 1462 unary: 1463 redis: 1464 queue: requests 1465 `) 1466 1467 redis := mockTransportSpecBuilder{ 1468 Name: "redis", 1469 TransportConfig: _typeOfEmptyStruct, 1470 OnewayOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1471 }.Build(mockCtrl) 1472 1473 tt.specs = []TransportSpec{redis.Spec()} 1474 tt.wantErr = []string{ 1475 `transport "redis" does not support unary outbound requests`, 1476 } 1477 1478 return 1479 }, 1480 }, 1481 { 1482 desc: "explicit oneway not supported", 1483 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1484 type outboundConfig struct{ Address string } 1485 tt.give = whitespace.Expand(` 1486 outbounds: 1487 bar: 1488 oneway: 1489 tchannel: 1490 address: localhost:4040 1491 `) 1492 1493 tchan := mockTransportSpecBuilder{ 1494 Name: "tchannel", 1495 TransportConfig: _typeOfEmptyStruct, 1496 UnaryOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1497 }.Build(mockCtrl) 1498 1499 tt.specs = []TransportSpec{tchan.Spec()} 1500 tt.wantErr = []string{ 1501 `transport "tchannel" does not support oneway outbound requests`, 1502 } 1503 1504 return 1505 }, 1506 }, 1507 { 1508 desc: "explicit stream not supported", 1509 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1510 type outboundConfig struct{ Address string } 1511 tt.give = whitespace.Expand(` 1512 outbounds: 1513 bar: 1514 stream: 1515 tchannel: 1516 address: localhost:4040 1517 `) 1518 1519 tchan := mockTransportSpecBuilder{ 1520 Name: "tchannel", 1521 TransportConfig: _typeOfEmptyStruct, 1522 UnaryOutboundConfig: reflect.TypeOf(&outboundConfig{}), 1523 }.Build(mockCtrl) 1524 1525 tt.specs = []TransportSpec{tchan.Spec()} 1526 tt.wantErr = []string{ 1527 `transport "tchannel" does not support stream outbound requests`, 1528 } 1529 1530 return 1531 }, 1532 }, 1533 { 1534 desc: "implicit outbound service name override", 1535 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1536 type outboundConfig struct{ URL string } 1537 tt.serviceName = "foo" 1538 tt.give = whitespace.Expand(` 1539 outbounds: 1540 bar: 1541 http: 1542 url: http://localhost:8080/bar 1543 bar-staging: 1544 service: bar 1545 http: 1546 url: http://localhost:8081/bar 1547 `) 1548 1549 http := mockTransportSpecBuilder{ 1550 Name: "http", 1551 TransportConfig: _typeOfEmptyStruct, 1552 UnaryOutboundConfig: reflect.TypeOf(outboundConfig{}), 1553 OnewayOutboundConfig: reflect.TypeOf(outboundConfig{}), 1554 }.Build(mockCtrl) 1555 1556 transport := transporttest.NewMockTransport(mockCtrl) 1557 unary := transporttest.NewMockUnaryOutbound(mockCtrl) 1558 oneway := transporttest.NewMockOnewayOutbound(mockCtrl) 1559 unaryStaging := transporttest.NewMockUnaryOutbound(mockCtrl) 1560 onewayStaging := transporttest.NewMockOnewayOutbound(mockCtrl) 1561 1562 http.EXPECT(). 1563 BuildTransport(struct{}{}, kitMatcher{ServiceName: "foo"}). 1564 Return(transport, nil) 1565 1566 http.EXPECT(). 1567 BuildUnaryOutbound( 1568 outboundConfig{URL: "http://localhost:8080/bar"}, 1569 transport, 1570 kitMatcher{ServiceName: "foo", OutboundServiceName: "bar"}). 1571 Return(unary, nil) 1572 http.EXPECT(). 1573 BuildOnewayOutbound( 1574 outboundConfig{URL: "http://localhost:8080/bar"}, 1575 transport, 1576 kitMatcher{ServiceName: "foo", OutboundServiceName: "bar"}). 1577 Return(oneway, nil) 1578 1579 http.EXPECT(). 1580 BuildUnaryOutbound( 1581 outboundConfig{URL: "http://localhost:8081/bar"}, 1582 transport, 1583 kitMatcher{ServiceName: "foo", OutboundServiceName: "bar"}). 1584 Return(unaryStaging, nil) 1585 http.EXPECT(). 1586 BuildOnewayOutbound( 1587 outboundConfig{URL: "http://localhost:8081/bar"}, 1588 transport, 1589 kitMatcher{ServiceName: "foo", OutboundServiceName: "bar"}). 1590 Return(onewayStaging, nil) 1591 1592 tt.specs = []TransportSpec{http.Spec()} 1593 tt.wantConfig = yarpc.Config{ 1594 Name: "foo", 1595 Outbounds: yarpc.Outbounds{ 1596 "bar": {Unary: unary, Oneway: oneway}, 1597 "bar-staging": { 1598 ServiceName: "bar", 1599 Unary: unaryStaging, 1600 Oneway: onewayStaging, 1601 }, 1602 }, 1603 } 1604 1605 return 1606 }, 1607 }, 1608 { 1609 desc: "interpolated string", 1610 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1611 type transportConfig struct { 1612 ServerAddress string `config:",interpolate"` 1613 } 1614 1615 type outboundConfig struct { 1616 QueueName string `config:"queue,interpolate"` 1617 } 1618 1619 tt.serviceName = "foo" 1620 tt.give = whitespace.Expand(` 1621 transports: 1622 redis: 1623 serverAddress: ${REDIS_ADDRESS}:${REDIS_PORT} 1624 outbounds: 1625 myservice: 1626 redis: 1627 queue: /${MYSERVICE_QUEUE}/inbound 1628 `) 1629 tt.env = map[string]string{ 1630 "REDIS_ADDRESS": "127.0.0.1", 1631 "REDIS_PORT": "6379", 1632 "MYSERVICE_QUEUE": "myservice", 1633 } 1634 1635 redis := mockTransportSpecBuilder{ 1636 Name: "redis", 1637 TransportConfig: reflect.TypeOf(transportConfig{}), 1638 OnewayOutboundConfig: reflect.TypeOf(outboundConfig{}), 1639 }.Build(mockCtrl) 1640 1641 kit := kitMatcher{ServiceName: "foo"} 1642 transport := transporttest.NewMockTransport(mockCtrl) 1643 oneway := transporttest.NewMockOnewayOutbound(mockCtrl) 1644 1645 redis.EXPECT(). 1646 BuildTransport(transportConfig{ServerAddress: "127.0.0.1:6379"}, kit). 1647 Return(transport, nil) 1648 redis.EXPECT(). 1649 BuildOnewayOutbound(outboundConfig{QueueName: "/myservice/inbound"}, transport, 1650 kitMatcher{ServiceName: "foo", OutboundServiceName: "myservice"}). 1651 Return(oneway, nil) 1652 1653 tt.specs = []TransportSpec{redis.Spec()} 1654 tt.wantConfig = yarpc.Config{ 1655 Name: "foo", 1656 Outbounds: yarpc.Outbounds{"myservice": {Oneway: oneway}}, 1657 } 1658 1659 return 1660 }, 1661 }, 1662 { 1663 desc: "interpolated integer", 1664 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1665 type inboundConfig struct { 1666 Port int `config:",interpolate"` 1667 } 1668 1669 tt.serviceName = "hi" 1670 tt.give = whitespace.Expand(` 1671 inbounds: 1672 http: 1673 port: 1${HTTP_PORT} 1674 `) 1675 tt.env = map[string]string{"HTTP_PORT": "8080"} 1676 1677 http := mockTransportSpecBuilder{ 1678 Name: "http", 1679 TransportConfig: _typeOfEmptyStruct, 1680 InboundConfig: reflect.TypeOf(inboundConfig{}), 1681 }.Build(mockCtrl) 1682 1683 kit := kitMatcher{ServiceName: "hi"} 1684 transport := transporttest.NewMockTransport(mockCtrl) 1685 inbound := transporttest.NewMockInbound(mockCtrl) 1686 1687 http.EXPECT().BuildTransport(struct{}{}, kit).Return(transport, nil) 1688 http.EXPECT(). 1689 BuildInbound(inboundConfig{Port: 18080}, transport, kit). 1690 Return(inbound, nil) 1691 1692 tt.specs = []TransportSpec{http.Spec()} 1693 tt.wantConfig = yarpc.Config{ 1694 Name: "hi", 1695 Inbounds: yarpc.Inbounds{inbound}, 1696 } 1697 1698 return 1699 }, 1700 }, 1701 { 1702 desc: "intepolate non-string", 1703 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1704 type inboundConfig struct { 1705 Port int `config:",interpolate"` 1706 } 1707 1708 tt.serviceName = "foo" 1709 tt.give = whitespace.Expand(` 1710 inbounds: 1711 http: 1712 port: 80 1713 `) 1714 1715 http := mockTransportSpecBuilder{ 1716 Name: "http", 1717 TransportConfig: _typeOfEmptyStruct, 1718 InboundConfig: reflect.TypeOf(inboundConfig{}), 1719 }.Build(mockCtrl) 1720 1721 kit := kitMatcher{ServiceName: "foo"} 1722 transport := transporttest.NewMockTransport(mockCtrl) 1723 inbound := transporttest.NewMockInbound(mockCtrl) 1724 1725 http.EXPECT().BuildTransport(struct{}{}, kit).Return(transport, nil) 1726 http.EXPECT(). 1727 BuildInbound(inboundConfig{Port: 80}, transport, kit). 1728 Return(inbound, nil) 1729 1730 tt.specs = []TransportSpec{http.Spec()} 1731 tt.wantConfig = yarpc.Config{ 1732 Name: "foo", 1733 Inbounds: yarpc.Inbounds{inbound}, 1734 } 1735 1736 return 1737 }, 1738 }, 1739 { 1740 desc: "bad interpolation string", 1741 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1742 type inboundConfig struct { 1743 Address string `config:",interpolate"` 1744 } 1745 1746 tt.serviceName = "hi" 1747 tt.give = whitespace.Expand(` 1748 inbounds: 1749 http: 1750 address: :${HTTP_PORT 1751 `) 1752 tt.env = map[string]string{"HTTP_PORT": "8080"} 1753 1754 http := mockTransportSpecBuilder{ 1755 Name: "http", 1756 TransportConfig: _typeOfEmptyStruct, 1757 InboundConfig: reflect.TypeOf(inboundConfig{}), 1758 }.Build(mockCtrl) 1759 1760 tt.specs = []TransportSpec{http.Spec()} 1761 tt.wantErr = []string{ 1762 "failed to decode inbound configuration:", 1763 `error reading into field "Address":`, 1764 `failed to parse ":${HTTP_PORT" for interpolation`, 1765 } 1766 1767 return 1768 }, 1769 }, 1770 { 1771 desc: "missing envvar", 1772 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1773 type inboundConfig struct { 1774 Address string `config:",interpolate"` 1775 } 1776 1777 tt.serviceName = "hi" 1778 tt.give = whitespace.Expand(` 1779 inbounds: 1780 http: 1781 address: :${HTTP_PORT} 1782 `) 1783 1784 http := mockTransportSpecBuilder{ 1785 Name: "http", 1786 TransportConfig: _typeOfEmptyStruct, 1787 InboundConfig: reflect.TypeOf(inboundConfig{}), 1788 }.Build(mockCtrl) 1789 1790 tt.specs = []TransportSpec{http.Spec()} 1791 tt.wantErr = []string{ 1792 "failed to decode inbound configuration:", 1793 `error reading into field "Address":`, 1794 `failed to render ":${HTTP_PORT}" with environment variables:`, 1795 `variable "HTTP_PORT" does not have a value or a default`, 1796 } 1797 1798 return 1799 }, 1800 }, 1801 { 1802 desc: "time.Duration from env", 1803 test: func(t *testing.T, mockCtrl *gomock.Controller) (tt testCase) { 1804 type inboundConfig struct { 1805 Timeout time.Duration `config:",interpolate"` 1806 } 1807 1808 tt.serviceName = "foo" 1809 tt.give = whitespace.Expand(` 1810 inbounds: 1811 http: 1812 timeout: ${TIMEOUT} 1813 `) 1814 tt.env = map[string]string{"TIMEOUT": "5s"} 1815 1816 http := mockTransportSpecBuilder{ 1817 Name: "http", 1818 TransportConfig: _typeOfEmptyStruct, 1819 InboundConfig: reflect.TypeOf(inboundConfig{}), 1820 }.Build(mockCtrl) 1821 1822 kit := kitMatcher{ServiceName: "foo"} 1823 transport := transporttest.NewMockTransport(mockCtrl) 1824 inbound := transporttest.NewMockInbound(mockCtrl) 1825 1826 http.EXPECT().BuildTransport(struct{}{}, kit).Return(transport, nil) 1827 http.EXPECT(). 1828 BuildInbound(inboundConfig{Timeout: 5 * time.Second}, transport, kit). 1829 Return(inbound, nil) 1830 1831 tt.specs = []TransportSpec{http.Spec()} 1832 tt.wantConfig = yarpc.Config{ 1833 Name: "foo", 1834 Inbounds: yarpc.Inbounds{inbound}, 1835 } 1836 1837 return 1838 }, 1839 }, 1840 } 1841 1842 // We want to parameterize all tests over YAML and non-YAML modes. To 1843 // avoid two layers of nesting, we let this helper function call our test 1844 // runner. 1845 runTest := func(name string, f func(t *testing.T, useYAML bool)) { 1846 t.Run(name, func(t *testing.T) { 1847 t.Run("yaml", func(t *testing.T) { f(t, true) }) 1848 t.Run("direct", func(t *testing.T) { f(t, false) }) 1849 }) 1850 } 1851 1852 for _, tc := range tests { 1853 runTest(tc.desc, func(t *testing.T, useYAML bool) { 1854 mockCtrl := gomock.NewController(t) 1855 defer mockCtrl.Finish() 1856 1857 tt := tc.test(t, mockCtrl) 1858 cfg := New(InterpolationResolver(mapVariableResolver(tt.env))) 1859 1860 if tt.specs != nil { 1861 for _, spec := range tt.specs { 1862 err := cfg.RegisterTransport(spec) 1863 require.NoError(t, err, "failed to register transport %q", spec.Name) 1864 } 1865 } 1866 1867 var ( 1868 gotConfig yarpc.Config 1869 err error 1870 ) 1871 if useYAML { 1872 gotConfig, err = cfg.LoadConfigFromYAML(tt.serviceName, strings.NewReader(tt.give)) 1873 } else { 1874 var data map[string]interface{} 1875 require.NoError(t, yaml.Unmarshal([]byte(tt.give), &data), "failed to parse YAML") 1876 1877 gotConfig, err = cfg.LoadConfig(tt.serviceName, data) 1878 } 1879 1880 if len(tt.wantErr) > 0 { 1881 require.Error(t, err, "expected failure") 1882 for _, msg := range tt.wantErr { 1883 assert.Contains(t, err.Error(), msg) 1884 } 1885 return 1886 } 1887 1888 require.NoError(t, err, "expected success") 1889 assert.Equal(t, tt.wantConfig, gotConfig, "config did not match") 1890 }) 1891 } 1892 } 1893 1894 func mapVariableResolver(m map[string]string) interpolate.VariableResolver { 1895 return func(name string) (value string, ok bool) { 1896 value, ok = m[name] 1897 return 1898 } 1899 }