1 package ldcomponents
2
3 import (
4 "crypto/x509"
5 "net/http"
6 "net/http/httptest"
7 "net/url"
8 "os"
9 "reflect"
10 "testing"
11 "time"
12
13 "github.com/launchdarkly/go-server-sdk/v6/interfaces"
14 "github.com/launchdarkly/go-server-sdk/v6/internal"
15 "github.com/launchdarkly/go-server-sdk/v6/subsystems"
16
17 helpers "github.com/launchdarkly/go-test-helpers/v3"
18 "github.com/launchdarkly/go-test-helpers/v3/httphelpers"
19
20 "github.com/stretchr/testify/assert"
21 "github.com/stretchr/testify/require"
22 )
23
24 func TestHTTPConfigurationBuilder(t *testing.T) {
25 basicConfig := subsystems.BasicClientContext{SDKKey: "test-key"}
26
27 t.Run("defaults", func(t *testing.T) {
28 c, err := HTTPConfiguration().Build(basicConfig)
29 require.NoError(t, err)
30
31 headers := c.DefaultHeaders
32 assert.Len(t, headers, 2)
33 assert.Equal(t, "test-key", headers.Get("Authorization"))
34 assert.Equal(t, "GoClient/"+internal.SDKVersion, headers.Get("User-Agent"))
35
36 client := c.CreateHTTPClient()
37 assert.Equal(t, DefaultConnectTimeout, client.Timeout)
38
39 require.NotNil(t, client.Transport)
40 transport := client.Transport.(*http.Transport)
41 require.NotNil(t, transport)
42 assert.Equal(t, reflect.ValueOf(http.ProxyFromEnvironment).Pointer(), reflect.ValueOf(transport.Proxy).Pointer())
43 assert.Equal(t, 100, transport.MaxIdleConns)
44 assert.Equal(t, 90*time.Second, transport.IdleConnTimeout)
45 assert.Equal(t, 10*time.Second, transport.TLSHandshakeTimeout)
46 assert.Equal(t, 1*time.Second, transport.ExpectContinueTimeout)
47 })
48
49 t.Run("CACert", func(t *testing.T) {
50 httphelpers.WithSelfSignedServer(httphelpers.HandlerWithStatus(200), func(server *httptest.Server, certData []byte, certs *x509.CertPool) {
51 _, err := HTTPConfiguration().
52 CACert(certData).
53 Build(basicConfig)
54 require.NoError(t, err)
55 })
56 })
57
58 t.Run("CACert with invalid certificate", func(t *testing.T) {
59 badCertData := []byte("no")
60 _, err := HTTPConfiguration().
61 CACert(badCertData).
62 Build(basicConfig)
63 require.Error(t, err)
64 })
65
66 t.Run("CACertFile", func(t *testing.T) {
67 httphelpers.WithSelfSignedServer(httphelpers.HandlerWithStatus(200), func(server *httptest.Server, certData []byte, certs *x509.CertPool) {
68 helpers.WithTempFileData(certData, func(filename string) {
69 _, err := HTTPConfiguration().
70 CACertFile(filename).
71 Build(basicConfig)
72 require.NoError(t, err)
73 })
74 })
75 })
76
77 t.Run("CACertFile with invalid certificate", func(t *testing.T) {
78 badCertData := []byte("no")
79 helpers.WithTempFileData(badCertData, func(filename string) {
80 _, err := HTTPConfiguration().
81 CACertFile(filename).
82 Build(basicConfig)
83 require.Error(t, err)
84 })
85 })
86
87 t.Run("CACertFile with missing file", func(t *testing.T) {
88 helpers.WithTempFile(func(filename string) {
89 _ = os.Remove(filename)
90 _, err := HTTPConfiguration().
91 CACertFile(filename).
92 Build(basicConfig)
93 require.Error(t, err)
94 })
95 })
96
97 t.Run("ConnectTimeout", func(t *testing.T) {
98 timeout := 700 * time.Millisecond
99 c1, err := HTTPConfiguration().
100 ConnectTimeout(timeout).
101 Build(basicConfig)
102 require.NoError(t, err)
103
104 client1 := c1.CreateHTTPClient()
105 assert.Equal(t, timeout, client1.Timeout)
106
107 c2, err := HTTPConfiguration().
108 ConnectTimeout(-1 * time.Millisecond).
109 Build(basicConfig)
110 require.NoError(t, err)
111
112 client2 := c2.CreateHTTPClient()
113 assert.Equal(t, DefaultConnectTimeout, client2.Timeout)
114
115 })
116
117 t.Run("HTTPClientFactory", func(t *testing.T) {
118 hc := &http.Client{Timeout: time.Hour}
119
120 c, err := HTTPConfiguration().
121 HTTPClientFactory(func() *http.Client { return hc }).
122 Build(basicConfig)
123 require.NoError(t, err)
124
125 assert.Equal(t, hc, c.CreateHTTPClient())
126 })
127
128 t.Run("ProxyURL", func(t *testing.T) {
129
130
131
132
133
134 fakeTargetURL := "http://example/"
135 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerWithStatus(200))
136
137 httphelpers.WithServer(handler, func(server *httptest.Server) {
138 c, err := HTTPConfiguration().
139 ProxyURL(server.URL).
140 Build(basicConfig)
141 require.NoError(t, err)
142
143 client := c.CreateHTTPClient()
144 resp, err := client.Get(fakeTargetURL)
145 require.NoError(t, err)
146 assert.Equal(t, 200, resp.StatusCode)
147
148 r := <-requestsCh
149 assert.Equal(t, fakeTargetURL, r.Request.RequestURI)
150 })
151 })
152
153 t.Run("ProxyURL with basicauth", func(t *testing.T) {
154
155 fakeTargetURL := "http://example/"
156 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerWithStatus(200))
157
158 httphelpers.WithServer(handler, func(server *httptest.Server) {
159 parsedURL, _ := url.Parse(server.URL)
160 urlWithCredentials := "http://lucy:cat@" + parsedURL.Host
161 c, err := HTTPConfiguration().
162 ProxyURL(urlWithCredentials).
163 Build(basicConfig)
164 require.NoError(t, err)
165 client := c.CreateHTTPClient()
166 resp, err := client.Get(fakeTargetURL)
167 require.NoError(t, err)
168 assert.Equal(t, 200, resp.StatusCode)
169 r := <-requestsCh
170 assert.Equal(t, fakeTargetURL, r.Request.RequestURI)
171 assert.Equal(t, "Basic bHVjeTpjYXQ=", r.Request.Header.Get("Proxy-Authorization"))
172 })
173 })
174
175 t.Run("ProxyURL with invalid URL", func(t *testing.T) {
176 proxyURL := ":///"
177
178 _, err := HTTPConfiguration().
179 ProxyURL(proxyURL).
180 Build(basicConfig)
181 require.Error(t, err)
182 })
183
184 t.Run("Custom header set/get", func(t *testing.T) {
185 c, err := HTTPConfiguration().
186 Header("Custom-Header", "foo").
187 Build(basicConfig)
188 require.NoError(t, err)
189 assert.Equal(t, "foo", c.DefaultHeaders.Get("Custom-Header"))
190 })
191
192 t.Run("Repeat assignments of custom header take latest value", func(t *testing.T) {
193 c, err := HTTPConfiguration().
194 Header("Custom-Header", "foo").
195 Header("Custom-Header", "bar").
196 Header("Custom-Header", "baz").
197 Build(basicConfig)
198 require.NoError(t, err)
199 assert.Equal(t, "baz", c.DefaultHeaders.Get("Custom-Header"))
200 })
201
202 t.Run("Custom header values overwrite required headers", func(t *testing.T) {
203 c, err := HTTPConfiguration().
204 Header("User-Agent", "foo").
205 Header("Authorization", "bar").
206 Build(basicConfig)
207 require.NoError(t, err)
208 assert.Equal(t, "foo", c.DefaultHeaders.Get("User-Agent"))
209 assert.Equal(t, "bar", c.DefaultHeaders.Get("Authorization"))
210 })
211
212 t.Run("User-Agent", func(t *testing.T) {
213 c, err := HTTPConfiguration().
214 UserAgent("extra").
215 Build(basicConfig)
216 require.NoError(t, err)
217
218 assert.Equal(t, "GoClient/"+internal.SDKVersion+" extra", c.DefaultHeaders.Get("User-Agent"))
219 })
220
221 t.Run("Wrapper", func(t *testing.T) {
222 c1, err := HTTPConfiguration().
223 Wrapper("FancySDK", "").
224 Build(basicConfig)
225 require.NoError(t, err)
226
227 assert.Equal(t, "FancySDK", c1.DefaultHeaders.Get("X-LaunchDarkly-Wrapper"))
228
229 c2, err := HTTPConfiguration().
230 Wrapper("FancySDK", "2.0").
231 Build(basicConfig)
232 require.NoError(t, err)
233
234 assert.Equal(t, "FancySDK/2.0", c2.DefaultHeaders.Get("X-LaunchDarkly-Wrapper"))
235 })
236
237 t.Run("tags header", func(t *testing.T) {
238 t.Run("no tags", func(t *testing.T) {
239 c, err := HTTPConfiguration().Build(basicConfig)
240 require.NoError(t, err)
241 assert.Nil(t, c.DefaultHeaders.Values("X-LaunchDarkly-Tags"))
242 })
243
244 t.Run("some tags", func(t *testing.T) {
245 bc := basicConfig
246 bc.ApplicationInfo = interfaces.ApplicationInfo{ApplicationID: "appid", ApplicationVersion: "appver"}
247 c, err := HTTPConfiguration().Build(bc)
248 require.NoError(t, err)
249 assert.Equal(t, "application-id/appid application-version/appver", c.DefaultHeaders.Get("X-LaunchDarkly-Tags"))
250 })
251 })
252
253 t.Run("nil safety", func(t *testing.T) {
254 var b *HTTPConfigurationBuilder = nil
255 b = b.ConnectTimeout(0).Header("a", "b").ProxyURL("c").Wrapper("d", "e")
256 _ = b.DescribeConfiguration(subsystems.BasicClientContext{})
257 _, _ = b.Build(subsystems.BasicClientContext{})
258 })
259 }
260
View as plain text