1 package ldclient
2
3 import (
4 "testing"
5 "time"
6
7 "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"
8
9 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
10 "github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
11 "github.com/launchdarkly/go-sdk-common/v3/lduser"
12 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
13 ldevents "github.com/launchdarkly/go-sdk-events/v2"
14 "github.com/launchdarkly/go-server-sdk/v6/interfaces"
15 "github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
16 helpers "github.com/launchdarkly/go-test-helpers/v3"
17
18 "github.com/stretchr/testify/assert"
19 "github.com/stretchr/testify/require"
20 )
21
22 func TestIdentifySendsIdentifyEvent(t *testing.T) {
23 client := makeTestClient()
24 defer client.Close()
25
26 user := lduser.NewUser("userKey")
27 err := client.Identify(user)
28 assert.NoError(t, err)
29
30 events := client.eventProcessor.(*mocks.CapturingEventProcessor).Events
31 assert.Equal(t, 1, len(events))
32 e := events[0].(ldevents.IdentifyEventData)
33 assert.Equal(t, ldevents.Context(user), e.Context)
34 }
35
36 func TestIdentifyWithEmptyUserKeySendsNoEvent(t *testing.T) {
37 client := makeTestClient()
38 defer client.Close()
39
40 err := client.Identify(lduser.NewUser(""))
41 assert.NoError(t, err)
42
43 events := client.eventProcessor.(*mocks.CapturingEventProcessor).Events
44 assert.Equal(t, 0, len(events))
45 }
46
47 func TestTrackEventSendsCustomEvent(t *testing.T) {
48 client := makeTestClient()
49 defer client.Close()
50
51 user := lduser.NewUser("userKey")
52 key := "eventKey"
53 err := client.TrackEvent(key, user)
54 assert.NoError(t, err)
55
56 events := client.eventProcessor.(*mocks.CapturingEventProcessor).Events
57 assert.Equal(t, 1, len(events))
58 e := events[0].(ldevents.CustomEventData)
59 assert.Equal(t, ldevents.Context(user), e.Context)
60 assert.Equal(t, key, e.Key)
61 assert.Equal(t, ldvalue.Null(), e.Data)
62 assert.False(t, e.HasMetric)
63 }
64
65 func TestTrackDataSendsCustomEventWithData(t *testing.T) {
66 client := makeTestClient()
67 defer client.Close()
68
69 user := lduser.NewUser("userKey")
70 key := "eventKey"
71 data := ldvalue.ArrayOf(ldvalue.String("a"), ldvalue.String("b"))
72 err := client.TrackData(key, user, data)
73 assert.NoError(t, err)
74
75 events := client.eventProcessor.(*mocks.CapturingEventProcessor).Events
76 assert.Equal(t, 1, len(events))
77 e := events[0].(ldevents.CustomEventData)
78 assert.Equal(t, ldevents.Context(user), e.Context)
79 assert.Equal(t, key, e.Key)
80 assert.Equal(t, data, e.Data)
81 assert.False(t, e.HasMetric)
82 }
83
84 func TestTrackMetricSendsCustomEventWithMetricAndData(t *testing.T) {
85 client := makeTestClient()
86 defer client.Close()
87
88 user := lduser.NewUser("userKey")
89 key := "eventKey"
90 data := ldvalue.ArrayOf(ldvalue.String("a"), ldvalue.String("b"))
91 metric := float64(1.5)
92 err := client.TrackMetric(key, user, metric, data)
93 assert.NoError(t, err)
94
95 events := client.eventProcessor.(*mocks.CapturingEventProcessor).Events
96 assert.Equal(t, 1, len(events))
97 e := events[0].(ldevents.CustomEventData)
98 assert.Equal(t, ldevents.Context(user), e.Context)
99 assert.Equal(t, key, e.Key)
100 assert.Equal(t, data, e.Data)
101 assert.True(t, e.HasMetric)
102 assert.Equal(t, metric, e.MetricValue)
103 }
104
105 func TestTrackWithEmptyUserKeySendsNoEvent(t *testing.T) {
106 client := makeTestClient()
107 defer client.Close()
108
109 err := client.TrackEvent("eventkey", lduser.NewUser(""))
110 assert.NoError(t, err)
111
112 events := client.eventProcessor.(*mocks.CapturingEventProcessor).Events
113 assert.Equal(t, 0, len(events))
114 }
115
116 func TestTrackMetricWithEmptyUserKeySendsNoEvent(t *testing.T) {
117 client := makeTestClient()
118 defer client.Close()
119
120 err := client.TrackMetric("eventKey", lduser.NewUser(""), 2.5, ldvalue.Null())
121 assert.NoError(t, err)
122
123 events := client.eventProcessor.(*mocks.CapturingEventProcessor).Events
124 assert.Equal(t, 0, len(events))
125 }
126
127 func TestIdentifyWithEventsDisabledDoesNotCauseError(t *testing.T) {
128 mockLog := ldlogtest.NewMockLog()
129 client := makeTestClientWithConfig(func(c *Config) {
130 c.Events = ldcomponents.NoEvents()
131 c.Logging = ldcomponents.Logging().Loggers(mockLog.Loggers)
132 })
133 defer client.Close()
134
135 require.NoError(t, client.Identify(lduser.NewUser("")))
136
137 assert.Len(t, mockLog.GetOutput(ldlog.Warn), 0)
138 }
139
140 func TestTrackWithEventsDisabledDoesNotCauseError(t *testing.T) {
141 mockLog := ldlogtest.NewMockLog()
142 client := makeTestClientWithConfig(func(c *Config) {
143 c.Events = ldcomponents.NoEvents()
144 c.Logging = ldcomponents.Logging().Loggers(mockLog.Loggers)
145 })
146 defer client.Close()
147
148 require.NoError(t, client.TrackEvent("eventkey", lduser.NewUser("")))
149 require.NoError(t, client.TrackMetric("eventkey", lduser.NewUser(""), 0, ldvalue.Null()))
150
151 assert.Len(t, mockLog.GetOutput(ldlog.Warn), 0)
152 }
153
154 func TestWithEventsDisabledDecorator(t *testing.T) {
155 doTest := func(name string, fn func(*LDClient) interfaces.LDClientInterface, shouldBeSent bool) {
156 t.Run(name, func(t *testing.T) {
157 events := &mocks.CapturingEventProcessor{}
158 config := Config{
159 DataSource: ldcomponents.ExternalUpdatesOnly(),
160 Events: mocks.SingleComponentConfigurer[ldevents.EventProcessor]{Instance: events},
161 }
162 client, err := MakeCustomClient("", config, 0)
163 require.NoError(t, err)
164
165 ci := fn(client)
166 checkEvents := func(action func()) {
167 events.Events = nil
168 action()
169 if shouldBeSent {
170 assert.Len(t, events.Events, 1, "should have recorded an event, but did not")
171 } else {
172 assert.Len(t, events.Events, 0, "should not have recorded an event, but did")
173 }
174 }
175 user := lduser.NewUser("userkey")
176 checkEvents(func() { _, _ = ci.BoolVariation("flagkey", user, false) })
177 checkEvents(func() { _, _, _ = ci.BoolVariationDetail("flagkey", user, false) })
178 checkEvents(func() { _, _ = ci.IntVariation("flagkey", user, 0) })
179 checkEvents(func() { _, _, _ = ci.IntVariationDetail("flagkey", user, 0) })
180 checkEvents(func() { _, _ = ci.Float64Variation("flagkey", user, 0) })
181 checkEvents(func() { _, _, _ = ci.Float64VariationDetail("flagkey", user, 0) })
182 checkEvents(func() { _, _ = ci.StringVariation("flagkey", user, "") })
183 checkEvents(func() { _, _, _ = ci.StringVariationDetail("flagkey", user, "") })
184 checkEvents(func() { _, _ = ci.JSONVariation("flagkey", user, ldvalue.Null()) })
185 checkEvents(func() { _, _, _ = ci.JSONVariationDetail("flagkey", user, ldvalue.Null()) })
186 checkEvents(func() { ci.Identify(user) })
187 checkEvents(func() { ci.TrackEvent("eventkey", user) })
188 checkEvents(func() { ci.TrackData("eventkey", user, ldvalue.Bool(true)) })
189 checkEvents(func() { ci.TrackMetric("eventkey", user, 1.5, ldvalue.Null()) })
190
191 state := ci.AllFlagsState(user)
192 assert.True(t, state.IsValid())
193 })
194 }
195
196 doTest("client.WithEventsDisabled(false)",
197 func(c *LDClient) interfaces.LDClientInterface { return c.WithEventsDisabled(false) },
198 true)
199
200 doTest("client.WithEventsDisabled(true)",
201 func(c *LDClient) interfaces.LDClientInterface { return c.WithEventsDisabled(true) },
202 false)
203
204 doTest("client.WithEventsDisabled(true).WithEventsDisabled(false)",
205 func(c *LDClient) interfaces.LDClientInterface {
206 return c.WithEventsDisabled(true).WithEventsDisabled(false)
207 },
208 true)
209
210 doTest("client.WithEventsDisabled(true).WithEventsDisabled(true)",
211 func(c *LDClient) interfaces.LDClientInterface {
212 return c.WithEventsDisabled(true).WithEventsDisabled(true)
213 },
214 false)
215 }
216
217 func TestFlushAsync(t *testing.T) {
218 g := newGatedEventSender()
219 client := makeTestClientWithEventSender(g)
220 defer client.Close()
221
222 client.Identify(evalTestUser)
223 client.Flush()
224
225 helpers.AssertNoMoreValues(t, g.didSendCh, time.Millisecond*50)
226
227 g.canSendCh <- struct{}{}
228
229 helpers.RequireValue(t, g.didSendCh, time.Millisecond*100)
230 }
231
232 func TestFlushAndWaitSucceeds(t *testing.T) {
233 g := newGatedEventSender()
234 client := makeTestClientWithEventSender(g)
235 defer client.Close()
236
237 client.Identify(evalTestUser)
238
239 go func() {
240 time.Sleep(time.Millisecond * 20)
241 g.canSendCh <- struct{}{}
242 }()
243
244 sent := client.FlushAndWait(time.Millisecond * 500)
245 assert.True(t, sent)
246
247 helpers.RequireValue(t, g.didSendCh, time.Millisecond*50)
248 }
249
250 func TestFlushAndWaitTimesOut(t *testing.T) {
251 g := newGatedEventSender()
252 client := makeTestClientWithEventSender(g)
253 defer client.Close()
254
255 client.Identify(evalTestUser)
256
257 go func() {
258 time.Sleep(time.Millisecond * 200)
259 g.canSendCh <- struct{}{}
260 }()
261
262 sent := client.FlushAndWait(time.Millisecond * 10)
263 assert.False(t, sent)
264
265 helpers.AssertNoMoreValues(t, g.didSendCh, time.Millisecond*50)
266 }
267
268 type gatedEventSender struct {
269 canSendCh chan struct{}
270 didSendCh chan struct{}
271 }
272
273 func newGatedEventSender() *gatedEventSender {
274 return &gatedEventSender{canSendCh: make(chan struct{}, 100), didSendCh: make(chan struct{}, 100)}
275 }
276
277 func (g *gatedEventSender) SendEventData(kind ldevents.EventDataKind, data []byte, eventCount int) ldevents.EventSenderResult {
278 <-g.canSendCh
279 g.didSendCh <- struct{}{}
280 return ldevents.EventSenderResult{Success: true}
281 }
282
283 func makeTestClientWithEventSender(s ldevents.EventSender) *LDClient {
284 eventsConfig := ldevents.EventsConfiguration{
285 Capacity: 1000,
286 EventSender: s,
287 FlushInterval: time.Hour,
288 Loggers: ldlog.NewDisabledLoggers(),
289 UserKeysCapacity: 1000,
290 UserKeysFlushInterval: time.Hour,
291 }
292 ep := ldevents.NewDefaultEventProcessor(eventsConfig)
293 return makeTestClientWithConfig(func(c *Config) {
294 c.Events = mocks.SingleComponentConfigurer[ldevents.EventProcessor]{Instance: ep}
295 })
296 }
297
View as plain text