go.uber.org/yarpc@v1.72.1/transport/http/config_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 http
    22  
    23  import (
    24  	"crypto/tls"
    25  	"errors"
    26  	"fmt"
    27  	"net/http"
    28  	"reflect"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  	yarpctls "go.uber.org/yarpc/api/transport/tls"
    35  	"go.uber.org/yarpc/yarpcconfig"
    36  )
    37  
    38  type fakeOutboundTLSConfigProvider struct {
    39  	returnErr         error
    40  	expectedSpiffeIDs []string
    41  }
    42  
    43  func (f fakeOutboundTLSConfigProvider) ClientTLSConfig(spiffeIDs []string) (*tls.Config, error) {
    44  	if f.returnErr != nil {
    45  		return nil, f.returnErr
    46  	}
    47  	if !reflect.DeepEqual(f.expectedSpiffeIDs, spiffeIDs) {
    48  		return nil, errors.New("spiffe IDs do not match")
    49  	}
    50  	return &tls.Config{}, nil
    51  }
    52  
    53  func TestTransportSpec(t *testing.T) {
    54  	// This test is a cross-product of the transport, inbound and outbound
    55  	// test assertions.
    56  	//
    57  	// Configuration, environment variables, and TransportSpec options are all
    58  	// combined. If any entry had a non-empty wantErrors, any test case with
    59  	// that entry is expected to fail.
    60  	//
    61  	// If the inbound and outbound tests state that they are both empty, the
    62  	// test case will be skipped because we don't build a transport if there
    63  	// is no inbound or outbound.
    64  
    65  	type attrs map[string]interface{}
    66  
    67  	type transportTest struct {
    68  		desc string            // description
    69  		cfg  attrs             // transport.http section of the config
    70  		env  map[string]string // environment variables
    71  		opts []Option          // transport spec options
    72  
    73  		wantErrors []string
    74  		wantClient *wantHTTPClient
    75  	}
    76  
    77  	type wantInbound struct {
    78  		Address         string
    79  		Mux             *http.ServeMux
    80  		MuxPattern      string
    81  		GrabHeaders     map[string]struct{}
    82  		ShutdownTimeout time.Duration
    83  		TLSMode         yarpctls.Mode
    84  	}
    85  
    86  	type inboundTest struct {
    87  		desc string            // description
    88  		cfg  attrs             // inbounds.http section of the config
    89  		env  map[string]string // environment variables
    90  		opts []Option          // transport spec options
    91  
    92  		empty bool // whether this test case is empty
    93  
    94  		wantErrors  []string
    95  		wantInbound *wantInbound
    96  	}
    97  
    98  	type wantOutbound struct {
    99  		URLTemplate string
   100  		Headers     http.Header
   101  		TLSConfig   bool
   102  	}
   103  
   104  	type outboundTest struct {
   105  		desc string            // description
   106  		cfg  attrs             // outbounds section of the config
   107  		env  map[string]string // environment variables
   108  		opts []Option          // transport spec options
   109  
   110  		empty bool // whether this test case is empty
   111  
   112  		wantErrors    []string
   113  		wantOutbounds map[string]wantOutbound
   114  	}
   115  
   116  	transportTests := []transportTest{
   117  		{
   118  			desc: "no transport config",
   119  			wantClient: &wantHTTPClient{
   120  				KeepAlive:           30 * time.Second,
   121  				MaxIdleConnsPerHost: 2,
   122  				ConnTimeout:         defaultConnTimeout,
   123  				IdleConnTimeout:     defaultIdleConnTimeout,
   124  			},
   125  		},
   126  		{
   127  			desc: "transport options",
   128  			opts: []Option{
   129  				KeepAlive(5 * time.Second),
   130  				MaxIdleConnsPerHost(42),
   131  			},
   132  			wantClient: &wantHTTPClient{
   133  				KeepAlive:           5 * time.Second,
   134  				MaxIdleConnsPerHost: 42,
   135  				ConnTimeout:         defaultConnTimeout,
   136  				IdleConnTimeout:     defaultIdleConnTimeout,
   137  			},
   138  		},
   139  		{
   140  			desc: "explicit transport config",
   141  			cfg: attrs{
   142  				"keepAlive":             "5s",
   143  				"maxIdleConns":          1,
   144  				"maxIdleConnsPerHost":   2,
   145  				"idleConnTimeout":       "5s",
   146  				"connTimeout":           "1s",
   147  				"disableKeepAlives":     true,
   148  				"disableCompression":    true,
   149  				"responseHeaderTimeout": "1s",
   150  			},
   151  			wantClient: &wantHTTPClient{
   152  				KeepAlive:             5 * time.Second,
   153  				MaxIdleConns:          1,
   154  				MaxIdleConnsPerHost:   2,
   155  				IdleConnTimeout:       5 * time.Second,
   156  				ConnTimeout:           1 * time.Second,
   157  				DisableKeepAlives:     true,
   158  				DisableCompression:    true,
   159  				ResponseHeaderTimeout: 1 * time.Second,
   160  			},
   161  		},
   162  	}
   163  
   164  	serveMux := http.NewServeMux()
   165  
   166  	inboundTests := []inboundTest{
   167  		{desc: "no inbound", empty: true},
   168  		{
   169  			desc:       "inbound without address",
   170  			cfg:        attrs{},
   171  			wantErrors: []string{"inbound address is required"},
   172  		},
   173  		{
   174  			desc:        "simple inbound",
   175  			cfg:         attrs{"address": ":8080"},
   176  			wantInbound: &wantInbound{Address: ":8080", ShutdownTimeout: defaultShutdownTimeout},
   177  		},
   178  		{
   179  			desc: "inbound tls",
   180  			cfg: attrs{
   181  				"address": ":8080",
   182  				"tls": attrs{
   183  					"mode": "permissive",
   184  				},
   185  			},
   186  			wantInbound: &wantInbound{Address: ":8080", ShutdownTimeout: defaultShutdownTimeout, TLSMode: yarpctls.Permissive},
   187  		},
   188  		{
   189  			desc: "inbound tls mode overridden by inbound option",
   190  			cfg: attrs{
   191  				"address": ":8080",
   192  				"tls": attrs{
   193  					"mode": "enforced",
   194  				},
   195  			},
   196  			opts:        []Option{InboundTLSMode(yarpctls.Permissive)},
   197  			wantInbound: &wantInbound{Address: ":8080", ShutdownTimeout: defaultShutdownTimeout, TLSMode: yarpctls.Permissive},
   198  		},
   199  		{
   200  			desc: "simple inbound with grab headers",
   201  			cfg:  attrs{"address": ":8080", "grabHeaders": []string{"x-foo", "x-bar"}},
   202  			wantInbound: &wantInbound{
   203  				Address:         ":8080",
   204  				GrabHeaders:     map[string]struct{}{"x-foo": {}, "x-bar": {}},
   205  				ShutdownTimeout: defaultShutdownTimeout,
   206  			},
   207  		},
   208  		{
   209  			desc:        "inbound interpolation",
   210  			cfg:         attrs{"address": "${HOST:}:${PORT}"},
   211  			env:         map[string]string{"HOST": "127.0.0.1", "PORT": "80"},
   212  			wantInbound: &wantInbound{Address: "127.0.0.1:80", ShutdownTimeout: defaultShutdownTimeout},
   213  		},
   214  		{
   215  			desc: "serve mux",
   216  			cfg:  attrs{"address": ":8080"},
   217  			opts: []Option{
   218  				Mux("/yarpc", serveMux),
   219  			},
   220  			wantInbound: &wantInbound{
   221  				Address:         ":8080",
   222  				Mux:             serveMux,
   223  				MuxPattern:      "/yarpc",
   224  				ShutdownTimeout: defaultShutdownTimeout,
   225  			},
   226  		},
   227  		{
   228  			desc:        "shutdown timeout",
   229  			cfg:         attrs{"address": ":8080", "shutdownTimeout": "1s"},
   230  			wantInbound: &wantInbound{Address: ":8080", ShutdownTimeout: time.Second},
   231  		},
   232  		{
   233  			desc:        "shutdown timeout 0",
   234  			cfg:         attrs{"address": ":8080", "shutdownTimeout": "0s"},
   235  			wantInbound: &wantInbound{Address: ":8080", ShutdownTimeout: 0},
   236  		},
   237  		{
   238  			desc:       "shutdown timeout err",
   239  			cfg:        attrs{"address": ":8080", "shutdownTimeout": "-1s"},
   240  			wantErrors: []string{`shutdownTimeout must not be negative, got: "-1s"`},
   241  		},
   242  	}
   243  
   244  	outboundTests := []outboundTest{
   245  		{desc: "no outbound", empty: true},
   246  		{
   247  			desc: "simple outbound",
   248  			cfg: attrs{
   249  				"myservice": attrs{
   250  					"http": attrs{"url": "http://localhost:4040/yarpc"},
   251  				},
   252  			},
   253  			wantOutbounds: map[string]wantOutbound{
   254  				"myservice": {
   255  					URLTemplate: "http://localhost:4040/yarpc",
   256  				},
   257  			},
   258  		},
   259  		{
   260  			desc: "outbound interpolation",
   261  			env:  map[string]string{"ADDR": "127.0.0.1:80"},
   262  			cfg: attrs{
   263  				"myservice": attrs{
   264  					"http": attrs{"url": "http://${ADDR}/yarpc"},
   265  				},
   266  			},
   267  			wantOutbounds: map[string]wantOutbound{
   268  				"myservice": {
   269  					URLTemplate: "http://127.0.0.1:80/yarpc",
   270  				},
   271  			},
   272  		},
   273  		{
   274  			desc: "outbound url template option",
   275  			opts: []Option{
   276  				URLTemplate("http://127.0.0.1:8080/yarpc"),
   277  			},
   278  			cfg: attrs{
   279  				"myservice": attrs{
   280  					"http": attrs{"peer": "127.0.0.1:8888"},
   281  				},
   282  			},
   283  			wantOutbounds: map[string]wantOutbound{
   284  				"myservice": {
   285  					URLTemplate: "http://127.0.0.1:8080/yarpc",
   286  				},
   287  			},
   288  		},
   289  		{
   290  			desc: "outbound url template option override",
   291  			opts: []Option{
   292  				URLTemplate("http://127.0.0.1:8080/yarpc"),
   293  			},
   294  			cfg: attrs{
   295  				"myservice": attrs{
   296  					"http": attrs{
   297  						"url":  "http://host/yarpc/v1",
   298  						"peer": "127.0.0.1:8888",
   299  					},
   300  				},
   301  			},
   302  			wantOutbounds: map[string]wantOutbound{
   303  				"myservice": {
   304  					URLTemplate: "http://host/yarpc/v1",
   305  				},
   306  			},
   307  		},
   308  		{
   309  			desc: "outbound header options",
   310  			opts: []Option{
   311  				AddHeader("X-Token", "token-1"),
   312  				AddHeader("X-Token-2", "token-2"),
   313  				AddHeader("X-Token", "token-3"),
   314  			},
   315  			cfg: attrs{
   316  				"myservice": attrs{
   317  					"http": attrs{"url": "http://localhost/"},
   318  				},
   319  			},
   320  			wantOutbounds: map[string]wantOutbound{
   321  				"myservice": {
   322  					URLTemplate: "http://localhost/",
   323  					Headers: http.Header{
   324  						"X-Token":   {"token-1", "token-3"},
   325  						"X-Token-2": {"token-2"},
   326  					},
   327  				},
   328  			},
   329  		},
   330  		{
   331  			desc: "outbound header config",
   332  			opts: []Option{
   333  				AddHeader("X-Token", "token-1"),
   334  			},
   335  			cfg: attrs{
   336  				"myservice": attrs{
   337  					"http": attrs{
   338  						"url": "http://localhost/",
   339  						"addHeaders": attrs{
   340  							"x-token":   "token-3",
   341  							"X-Token-2": "token-2",
   342  						},
   343  					},
   344  				},
   345  			},
   346  			wantOutbounds: map[string]wantOutbound{
   347  				"myservice": {
   348  					URLTemplate: "http://localhost/",
   349  					Headers: http.Header{
   350  						"X-Token":   {"token-1", "token-3"},
   351  						"X-Token-2": {"token-2"},
   352  					},
   353  				},
   354  			},
   355  		},
   356  		{
   357  			desc: "outbound header config with peer",
   358  			cfg: attrs{
   359  				"myservice": attrs{
   360  					"http": attrs{
   361  						"url":        "http://localhost/yarpc",
   362  						"peer":       "127.0.0.1:8080",
   363  						"addHeaders": attrs{"x-token": "token"},
   364  					},
   365  				},
   366  			},
   367  			wantOutbounds: map[string]wantOutbound{
   368  				"myservice": {
   369  					URLTemplate: "http://localhost/yarpc",
   370  					Headers: http.Header{
   371  						"X-Token": {"token"},
   372  					},
   373  				},
   374  			},
   375  		},
   376  		{
   377  			desc: "outbound peer build error",
   378  			cfg: attrs{
   379  				"myservice": attrs{
   380  					"http": attrs{
   381  						"least-pending": []string{
   382  							"127.0.0.1:8080",
   383  							"127.0.0.1:8081",
   384  							"127.0.0.1:8082",
   385  						},
   386  					},
   387  				},
   388  			},
   389  			wantErrors: []string{
   390  				"cannot configure peer chooser for HTTP outbound",
   391  				`failed to read attribute "least-pending"`,
   392  			},
   393  		},
   394  		{
   395  			desc: "unknown preset",
   396  			cfg: attrs{
   397  				"myservice": attrs{
   398  					"http": attrs{"with": "derp"},
   399  				},
   400  			},
   401  			wantErrors: []string{
   402  				`failed to configure unary outbound for "myservice":`,
   403  				"cannot configure peer chooser for HTTP outbound:",
   404  				`no recognized peer chooser preset "derp"`,
   405  			},
   406  		},
   407  		{
   408  			desc: "simple TLS outbound",
   409  			cfg: attrs{
   410  				"myservice": attrs{
   411  					TransportName: attrs{
   412  						"url": "http://localhost/yarpc",
   413  						"tls": attrs{
   414  							"mode":       yarpctls.Enforced,
   415  							"spiffe-ids": []string{"spiffe-test-1"},
   416  						},
   417  					},
   418  				},
   419  			},
   420  			opts: []Option{OutboundTLSConfigProvider(&fakeOutboundTLSConfigProvider{
   421  				expectedSpiffeIDs: []string{"spiffe-test-1"},
   422  			})},
   423  			wantOutbounds: map[string]wantOutbound{
   424  				"myservice": {
   425  					URLTemplate: "https://localhost/yarpc",
   426  					TLSConfig:   true,
   427  				},
   428  			},
   429  		},
   430  		{
   431  			desc: "TLS outbound without spiffe id",
   432  			cfg: attrs{
   433  				"myservice": attrs{
   434  					TransportName: attrs{
   435  						"url": "http://localhost/yarpc",
   436  						"tls": attrs{
   437  							"mode": yarpctls.Enforced,
   438  						},
   439  					},
   440  				},
   441  			},
   442  			opts: []Option{OutboundTLSConfigProvider(&fakeOutboundTLSConfigProvider{})},
   443  			wantOutbounds: map[string]wantOutbound{
   444  				"myservice": {
   445  					URLTemplate: "https://localhost/yarpc",
   446  					TLSConfig:   true,
   447  				},
   448  			},
   449  		},
   450  		{
   451  			desc: "fail TLS outbound with invalid tls mode",
   452  			cfg: attrs{
   453  				"myservice": attrs{
   454  					TransportName: attrs{
   455  						"url": "http://localhost/yarpc",
   456  						"tls": attrs{
   457  							"mode": yarpctls.Permissive,
   458  						},
   459  					},
   460  				},
   461  			},
   462  			opts:       []Option{OutboundTLSConfigProvider(&fakeOutboundTLSConfigProvider{})},
   463  			wantErrors: []string{"outbound does not support permissive TLS mode"},
   464  		},
   465  		{
   466  			desc: "fail TLS outbound when tls config provider returns error",
   467  			cfg: attrs{
   468  				"myservice": attrs{
   469  					TransportName: attrs{
   470  						"url": "http://localhost/yarpc",
   471  						"tls": attrs{
   472  							"mode":       yarpctls.Enforced,
   473  							"spiffe-ids": []string{"test-spiffe"},
   474  						},
   475  					},
   476  				},
   477  			},
   478  			opts:       []Option{OutboundTLSConfigProvider(&fakeOutboundTLSConfigProvider{returnErr: errors.New("test error")})},
   479  			wantErrors: []string{"test error"},
   480  		},
   481  		{
   482  			desc: "fail TLS outbound without outbound tls config provider",
   483  			cfg: attrs{
   484  				"myservice": attrs{
   485  					TransportName: attrs{
   486  						"url": "http://localhost/yarpc",
   487  						"tls": attrs{
   488  							"mode":       yarpctls.Enforced,
   489  							"spiffe-ids": []string{"test-spiffe"},
   490  						},
   491  					},
   492  				},
   493  			},
   494  			wantErrors: []string{"outbound TLS enforced but outbound TLS config provider is nil"},
   495  		},
   496  	}
   497  
   498  	runTest := func(t *testing.T, trans transportTest, inbound inboundTest, outbound outboundTest) {
   499  		env := make(map[string]string)
   500  		for k, v := range trans.env {
   501  			env[k] = v
   502  		}
   503  		for k, v := range inbound.env {
   504  			_, ok := env[k]
   505  			require.False(t, ok,
   506  				"invalid test: environment variable %q is defined multiple times", k)
   507  			env[k] = v
   508  		}
   509  		for k, v := range outbound.env {
   510  			_, ok := env[k]
   511  			require.False(t, ok,
   512  				"invalid test: environment variable %q is defined multiple times", k)
   513  			env[k] = v
   514  		}
   515  		configurator := yarpcconfig.New(yarpcconfig.InterpolationResolver(mapResolver(env)))
   516  
   517  		opts := append(append(trans.opts, inbound.opts...), outbound.opts...)
   518  		if trans.wantClient != nil {
   519  			opts = append(opts, useFakeBuildClient(t, trans.wantClient))
   520  		}
   521  		err := configurator.RegisterTransport(TransportSpec(opts...))
   522  		require.NoError(t, err, "failed to register transport spec")
   523  
   524  		cfgData := make(attrs)
   525  		if trans.cfg != nil {
   526  			cfgData["transports"] = attrs{"http": trans.cfg}
   527  		}
   528  		if inbound.cfg != nil {
   529  			cfgData["inbounds"] = attrs{"http": inbound.cfg}
   530  		}
   531  		if outbound.cfg != nil {
   532  			cfgData["outbounds"] = outbound.cfg
   533  		}
   534  		cfg, err := configurator.LoadConfig("foo", cfgData)
   535  
   536  		wantErrors := append(append(trans.wantErrors, inbound.wantErrors...), outbound.wantErrors...)
   537  		if len(wantErrors) > 0 {
   538  			require.Error(t, err, "expected failure while loading config %+v", cfgData)
   539  			for _, msg := range wantErrors {
   540  				assert.Contains(t, err.Error(), msg)
   541  			}
   542  			return
   543  		}
   544  
   545  		require.NoError(t, err, "expected success while loading config %+v", cfgData)
   546  
   547  		if want := inbound.wantInbound; want != nil {
   548  			assert.Len(t, cfg.Inbounds, 1, "expected exactly one inbound in %+v", cfgData)
   549  			ib, ok := cfg.Inbounds[0].(*Inbound)
   550  			if assert.True(t, ok, "expected *Inbound, got %T", cfg.Inbounds[0]) {
   551  				assert.Equal(t, want.Address, ib.addr, "inbound address should match")
   552  				assert.Equal(t, want.MuxPattern, ib.muxPattern,
   553  					"inbound mux pattern should match")
   554  				// == because we want it to be the same object
   555  				assert.True(t, want.Mux == ib.mux, "inbound mux should match")
   556  				// this has to be done because assert.Equal returns false if one map
   557  				// is nil and the other is empty
   558  				if len(want.GrabHeaders) > 0 {
   559  					assert.Equal(t, want.GrabHeaders, ib.grabHeaders, "inbound grab headers should match")
   560  				} else {
   561  					assert.Empty(t, ib.grabHeaders)
   562  				}
   563  				assert.Equal(t, want.ShutdownTimeout, ib.shutdownTimeout, "shutdownTimeout should match")
   564  				assert.Equal(t, "foo", ib.transport.serviceName, "service name must match")
   565  				assert.Equal(t, want.TLSMode, ib.tlsMode, "tlsMode should match")
   566  			}
   567  		}
   568  
   569  		for svc, want := range outbound.wantOutbounds {
   570  			ob, ok := cfg.Outbounds[svc].Unary.(*Outbound)
   571  			if assert.True(t, ok, "expected *Outbound for %q, got %T", svc, cfg.Outbounds[svc].Unary) {
   572  				// Verify that we install a oneway too
   573  				_, ok := cfg.Outbounds[svc].Oneway.(*Outbound)
   574  				assert.True(t, ok, "expected *Outbound for %q oneway, got %T", svc, cfg.Outbounds[svc].Oneway)
   575  
   576  				assert.Equal(t, want.URLTemplate, ob.urlTemplate.String(), "outbound URLTemplate should match")
   577  				assert.Equal(t, want.Headers, ob.headers, "outbound headers should match")
   578  				assert.Equal(t, svc, ob.destServiceName, "outbound destination service name must match")
   579  				assert.Equal(t, want.TLSConfig, ob.tlsConfig != nil, "unexpected outbound tls config")
   580  			}
   581  
   582  		}
   583  	}
   584  
   585  	for _, transTT := range transportTests {
   586  		for _, inboundTT := range inboundTests {
   587  			for _, outboundTT := range outboundTests {
   588  				// Special case: No inbounds or outbounds so we have nothing
   589  				// to test.
   590  				if inboundTT.empty && outboundTT.empty {
   591  					continue
   592  				}
   593  
   594  				desc := fmt.Sprintf("%v/%v/%v", transTT.desc, inboundTT.desc, outboundTT.desc)
   595  				t.Run(desc, func(t *testing.T) {
   596  					runTest(t, transTT, inboundTT, outboundTT)
   597  				})
   598  			}
   599  		}
   600  	}
   601  }
   602  
   603  func mapResolver(m map[string]string) func(string) (string, bool) {
   604  	return func(k string) (v string, ok bool) {
   605  		if m != nil {
   606  			v, ok = m[k]
   607  		}
   608  		return
   609  	}
   610  }
   611  
   612  type wantHTTPClient struct {
   613  	KeepAlive             time.Duration
   614  	MaxIdleConns          int
   615  	MaxIdleConnsPerHost   int
   616  	IdleConnTimeout       time.Duration
   617  	DisableKeepAlives     bool
   618  	DisableCompression    bool
   619  	ResponseHeaderTimeout time.Duration
   620  	ConnTimeout           time.Duration
   621  }
   622  
   623  // useFakeBuildClient verifies the configuration we use to build an HTTP
   624  // client.
   625  func useFakeBuildClient(t *testing.T, want *wantHTTPClient) TransportOption {
   626  	return buildClient(func(options *transportOptions) *http.Client {
   627  		assert.Equal(t, want.KeepAlive, options.keepAlive, "http.Client: KeepAlive should match")
   628  		assert.Equal(t, want.MaxIdleConns, options.maxIdleConns, "http.Client: MaxIdleConns should match")
   629  		assert.Equal(t, want.MaxIdleConnsPerHost, options.maxIdleConnsPerHost, "http.Client: MaxIdleConnsPerHost should match")
   630  		assert.Equal(t, want.IdleConnTimeout, options.idleConnTimeout, "http.Client: IdleConnTimeout should match")
   631  		assert.Equal(t, want.DisableKeepAlives, options.disableKeepAlives, "http.Client: DisableKeepAlives should match")
   632  		assert.Equal(t, want.DisableCompression, options.disableCompression, "http.Client: DisableCompression should match")
   633  		assert.Equal(t, want.ResponseHeaderTimeout, options.responseHeaderTimeout, "http.Client: ResponseHeaderTimeout should match")
   634  		assert.Equal(t, want.ConnTimeout, options.connTimeout, "http.Client: ConnTimeout should match")
   635  		return buildHTTPClient(options)
   636  	})
   637  }