1 package ldevents
2
3 import (
4 "fmt"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8 "time"
9
10 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
11 "github.com/launchdarkly/go-sdk-common/v3/ldtime"
12
13 "github.com/launchdarkly/go-test-helpers/v3/httphelpers"
14
15 "github.com/stretchr/testify/assert"
16 )
17
18 type errorInfo struct {
19 status int
20 }
21
22 func (ei errorInfo) Handler() http.Handler {
23 if ei.status > 0 {
24 return httphelpers.HandlerWithStatus(ei.status)
25 }
26 return httphelpers.BrokenConnectionHandler()
27 }
28
29 func (ei errorInfo) String() string {
30 if ei.status > 0 {
31 return fmt.Sprintf("error %d", ei.status)
32 }
33 return "network error"
34 }
35
36 func TestDataIsSentToAnalyticsURI(t *testing.T) {
37 es, requestsCh := makeEventSenderWithRequestSink()
38
39 result := es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
40 assert.True(t, result.Success)
41
42 assert.Equal(t, 1, len(requestsCh))
43 r := <-requestsCh
44 assert.Equal(t, fakeEventsURI, r.Request.URL.String())
45 assert.Equal(t, arbitraryJSONData, r.Body)
46 }
47
48 func TestDataIsSentToDiagnosticURI(t *testing.T) {
49 es, requestsCh := makeEventSenderWithRequestSink()
50
51 result := es.SendEventData(DiagnosticEventDataKind, arbitraryJSONData, 1)
52 assert.True(t, result.Success)
53
54 assert.Equal(t, 1, len(requestsCh))
55 r := <-requestsCh
56 assert.Equal(t, fakeDiagnosticURI, r.Request.URL.String())
57 assert.Equal(t, arbitraryJSONData, r.Body)
58 }
59
60 func TestUnknownDataKindIsIgnored(t *testing.T) {
61 es, requestsCh := makeEventSenderWithRequestSink()
62
63 result := es.SendEventData(EventDataKind("not valid"), arbitraryJSONData, 1)
64 assert.False(t, result.Success)
65 assert.False(t, result.MustShutDown)
66 assert.Len(t, requestsCh, 0)
67 }
68
69 func TestAnalyticsEventsHaveSchemaAndPayloadIDHeaders(t *testing.T) {
70 es, requestsCh := makeEventSenderWithRequestSink()
71
72 es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
73 es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
74
75 assert.Equal(t, 2, len(requestsCh))
76 r0 := <-requestsCh
77 r1 := <-requestsCh
78
79 assert.Equal(t, currentEventSchema, r0.Request.Header.Get(eventSchemaHeader))
80 assert.Equal(t, currentEventSchema, r1.Request.Header.Get(eventSchemaHeader))
81
82 id0 := r0.Request.Header.Get(payloadIDHeader)
83 id1 := r1.Request.Header.Get(payloadIDHeader)
84 assert.NotEqual(t, "", id0)
85 assert.NotEqual(t, "", id1)
86 assert.NotEqual(t, id0, id1)
87 }
88
89 func TestDiagnosticEventsDoNotHaveSchemaOrPayloadID(t *testing.T) {
90 es, requestsCh := makeEventSenderWithRequestSink()
91
92 es.SendEventData(DiagnosticEventDataKind, arbitraryJSONData, 1)
93
94 assert.Equal(t, 1, len(requestsCh))
95 r := <-requestsCh
96 assert.Equal(t, "", r.Request.Header.Get(eventSchemaHeader))
97 assert.Equal(t, "", r.Request.Header.Get(payloadIDHeader))
98 }
99
100 func TestEventSenderParsesTimeFromServer(t *testing.T) {
101 expectedTime := ldtime.UnixMillisFromTime(time.Date(1940, time.February, 15, 12, 13, 14, 0, time.UTC))
102 headers := make(http.Header)
103 headers.Set("Date", "Thu, 15 Feb 1940 12:13:14 GMT")
104 handler := httphelpers.HandlerWithResponse(202, headers, nil)
105 es := makeEventSenderWithHTTPClient(httphelpers.ClientFromHandler(handler))
106
107 result := es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
108 assert.True(t, result.Success)
109 assert.Equal(t, expectedTime, result.TimeFromServer)
110 }
111
112 func TestEventSenderRetriesOnRecoverableError(t *testing.T) {
113 errs := []errorInfo{{400}, {408}, {429}, {500}, {503}, {0}}
114 for _, errorInfo := range errs {
115 t.Run(fmt.Sprintf("Retries once after %s", errorInfo), func(t *testing.T) {
116 handler, requestsCh := httphelpers.RecordingHandler(
117 httphelpers.SequentialHandler(
118 errorInfo.Handler(),
119 httphelpers.HandlerWithStatus(202),
120 ),
121 )
122 es := makeEventSenderWithHTTPClient(httphelpers.ClientFromHandler(handler))
123
124 result := es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
125
126 assert.True(t, result.Success)
127 assert.False(t, result.MustShutDown)
128
129 assert.Equal(t, 2, len(requestsCh))
130 r0 := <-requestsCh
131 r1 := <-requestsCh
132 assert.Equal(t, arbitraryJSONData, r0.Body)
133 assert.Equal(t, arbitraryJSONData, r1.Body)
134 id0 := r0.Request.Header.Get(payloadIDHeader)
135 assert.NotEqual(t, "", id0)
136 assert.Equal(t, id0, r1.Request.Header.Get(payloadIDHeader))
137 })
138
139 t.Run(fmt.Sprintf("Does not retry more than once after %s", errorInfo), func(t *testing.T) {
140 handler, requestsCh := httphelpers.RecordingHandler(
141 httphelpers.SequentialHandler(
142 errorInfo.Handler(),
143 errorInfo.Handler(),
144 httphelpers.HandlerWithStatus(202),
145 ),
146 )
147 es := makeEventSenderWithHTTPClient(httphelpers.ClientFromHandler(handler))
148
149 result := es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
150
151 assert.False(t, result.Success)
152 assert.False(t, result.MustShutDown)
153
154 assert.Equal(t, 2, len(requestsCh))
155 r0 := <-requestsCh
156 r1 := <-requestsCh
157 assert.Equal(t, arbitraryJSONData, r0.Body)
158 assert.Equal(t, arbitraryJSONData, r1.Body)
159 id0 := r0.Request.Header.Get(payloadIDHeader)
160 assert.NotEqual(t, "", id0)
161 assert.Equal(t, id0, r1.Request.Header.Get(payloadIDHeader))
162 })
163 }
164 }
165
166 func TestEventSenderFailsOnUnrecoverableError(t *testing.T) {
167 errs := []errorInfo{{401}, {403}}
168 for _, errorInfo := range errs {
169 t.Run(fmt.Sprintf("Fails permanently after %s", errorInfo), func(t *testing.T) {
170 handler, requestsCh := httphelpers.RecordingHandler(
171 httphelpers.SequentialHandler(
172 errorInfo.Handler(),
173 httphelpers.HandlerWithStatus(202),
174 ),
175 )
176 es := makeEventSenderWithHTTPClient(httphelpers.ClientFromHandler(handler))
177
178 result := es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
179
180 assert.False(t, result.Success)
181 assert.True(t, result.MustShutDown)
182
183 assert.Equal(t, 1, len(requestsCh))
184 r := <-requestsCh
185 assert.Equal(t, arbitraryJSONData, r.Body)
186 })
187 }
188 }
189
190 func TestServerSideSenderSetsURIsFromBase(t *testing.T) {
191 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerWithStatus(202))
192 client := httphelpers.ClientFromHandler(handler)
193 es := NewServerSideEventSender(EventSenderConfiguration{Client: client, BaseURI: fakeBaseURI, Loggers: ldlog.NewDisabledLoggers()},
194 sdkKey)
195
196 es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
197 es.SendEventData(DiagnosticEventDataKind, arbitraryJSONData, 1)
198
199 assert.Equal(t, 2, len(requestsCh))
200 r0 := <-requestsCh
201 r1 := <-requestsCh
202 assert.Equal(t, fakeEventsURI, r0.Request.URL.String())
203 assert.Equal(t, fakeDiagnosticURI, r1.Request.URL.String())
204 }
205
206 func TestServerSideSenderHasDefaultBaseURI(t *testing.T) {
207 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerWithStatus(202))
208 client := httphelpers.ClientFromHandler(handler)
209 es := NewServerSideEventSender(
210 EventSenderConfiguration{
211 Client: client,
212 Loggers: ldlog.NewDisabledLoggers(),
213 },
214 sdkKey,
215 )
216
217 es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
218 es.SendEventData(DiagnosticEventDataKind, arbitraryJSONData, 1)
219
220 assert.Equal(t, 2, len(requestsCh))
221 r0 := <-requestsCh
222 r1 := <-requestsCh
223 assert.Equal(t, "https://events.launchdarkly.com/bulk", r0.Request.URL.String())
224 assert.Equal(t, "https://events.launchdarkly.com/diagnostic", r1.Request.URL.String())
225 }
226
227 func TestServerSideSenderAddsAuthorizationHeader(t *testing.T) {
228 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerWithStatus(202))
229 client := httphelpers.ClientFromHandler(handler)
230 extraHeaders := make(http.Header)
231 extraHeaders.Set("my-header", "my-value")
232 es := NewServerSideEventSender(
233 EventSenderConfiguration{
234 Client: client,
235 BaseURI: fakeBaseURI,
236 BaseHeaders: func() http.Header { return extraHeaders },
237 Loggers: ldlog.NewDisabledLoggers(),
238 },
239 sdkKey,
240 )
241
242 es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
243
244 assert.Equal(t, 1, len(requestsCh))
245 r := <-requestsCh
246 assert.Equal(t, sdkKey, r.Request.Header.Get("Authorization"))
247 assert.Equal(t, "my-value", r.Request.Header.Get("my-header"))
248 }
249
250 func TestSchemaVersionCannotBeOverriddenWithServerSideSender(t *testing.T) {
251 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerForMethod("POST", httphelpers.HandlerWithStatus(202), nil))
252 client := httphelpers.ClientFromHandler(handler)
253
254 config := EventSenderConfiguration{Client: client, SchemaVersion: 99}
255 es := makeEventSenderWithConfig(config)
256
257 es.SendEventData(AnalyticsEventDataKind, arbitraryJSONData, 1)
258
259 r := <-requestsCh
260 assert.Equal(t, currentEventSchema, r.Request.Header.Get(eventSchemaHeader))
261 }
262
263 func TestSchemaVersionCanBeOverriddenWithDirectSend(t *testing.T) {
264 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerForMethod("POST", httphelpers.HandlerWithStatus(202), nil))
265 client := httphelpers.ClientFromHandler(handler)
266
267 config := EventSenderConfiguration{Client: client, SchemaVersion: 99}
268
269 _ = SendEventDataWithRetry(config, AnalyticsEventDataKind, "", arbitraryJSONData, 1)
270
271 r := <-requestsCh
272 assert.Equal(t, "/bulk", r.Request.URL.Path)
273 assert.Equal(t, "99", r.Request.Header.Get(eventSchemaHeader))
274 }
275
276 func TestSendEventDataCanUseDefaultHTTPClient(t *testing.T) {
277 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerForMethod("POST", httphelpers.HandlerWithStatus(202), nil))
278 server := httptest.NewServer(handler)
279 defer server.Close()
280
281 config := EventSenderConfiguration{BaseURI: server.URL}
282
283 _ = SendEventDataWithRetry(config, AnalyticsEventDataKind, "", arbitraryJSONData, 1)
284
285 r := <-requestsCh
286 assert.Equal(t, "/bulk", r.Request.URL.Path)
287 assert.Equal(t, string(arbitraryJSONData), string(r.Body))
288 }
289
290 func TestSendEventDataCanOverrideURI(t *testing.T) {
291 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerForMethod("POST", httphelpers.HandlerWithStatus(202), nil))
292 server := httptest.NewServer(handler)
293 defer server.Close()
294
295 config := EventSenderConfiguration{BaseURI: server.URL}
296
297 _ = SendEventDataWithRetry(config, AnalyticsEventDataKind, "/other/path", arbitraryJSONData, 1)
298 _ = SendEventDataWithRetry(config, AnalyticsEventDataKind, "other/path", arbitraryJSONData, 1)
299
300 r1, r2 := <-requestsCh, <-requestsCh
301 assert.Equal(t, "/other/path", r1.Request.URL.Path)
302 assert.Equal(t, "/other/path", r2.Request.URL.Path)
303 }
304
305 func makeEventSenderWithConfig(config EventSenderConfiguration) EventSender {
306 config.BaseURI = fakeBaseURI
307 config.Loggers = ldlog.NewDisabledLoggers()
308 if config.RetryDelay == 0 {
309 config.RetryDelay = briefRetryDelay
310 }
311 return NewServerSideEventSender(config, sdkKey)
312 }
313
314 func makeEventSenderWithHTTPClient(client *http.Client) EventSender {
315 return makeEventSenderWithConfig(EventSenderConfiguration{Client: client})
316 }
317
318 func makeEventSenderWithRequestSink() (EventSender, <-chan httphelpers.HTTPRequestInfo) {
319 handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerForMethod("POST", httphelpers.HandlerWithStatus(202), nil))
320 client := httphelpers.ClientFromHandler(handler)
321 return makeEventSenderWithHTTPClient(client), requestsCh
322 }
323
View as plain text