@depup/launchdarkly-node-server-sdk 7.0.4-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.babelrc +16 -0
  2. package/.circleci/config.yml +89 -0
  3. package/.eslintignore +5 -0
  4. package/.eslintrc.yaml +114 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. package/.github/pull_request_template.md +21 -0
  9. package/.github/workflows/stale.yml +8 -0
  10. package/.hound.yml +33 -0
  11. package/.ldrelease/config.yml +28 -0
  12. package/.prettierrc +6 -0
  13. package/CHANGELOG.md +603 -0
  14. package/CODEOWNERS +2 -0
  15. package/CONTRIBUTING.md +55 -0
  16. package/LICENSE.txt +13 -0
  17. package/README.md +36 -0
  18. package/SECURITY.md +5 -0
  19. package/attribute_reference.js +217 -0
  20. package/big_segments.js +117 -0
  21. package/caching_store_wrapper.js +240 -0
  22. package/changes.json +30 -0
  23. package/configuration.js +235 -0
  24. package/context.js +98 -0
  25. package/context_filter.js +137 -0
  26. package/contract-tests/README.md +7 -0
  27. package/contract-tests/index.js +109 -0
  28. package/contract-tests/log.js +23 -0
  29. package/contract-tests/package.json +15 -0
  30. package/contract-tests/sdkClientEntity.js +110 -0
  31. package/contract-tests/testharness-suppressions.txt +2 -0
  32. package/diagnostic_events.js +151 -0
  33. package/docs/typedoc.js +10 -0
  34. package/errors.js +26 -0
  35. package/evaluator.js +822 -0
  36. package/event_factory.js +121 -0
  37. package/event_processor.js +320 -0
  38. package/event_summarizer.js +101 -0
  39. package/feature_store.js +120 -0
  40. package/feature_store_event_wrapper.js +258 -0
  41. package/file_data_source.js +192 -0
  42. package/flags_state.js +46 -0
  43. package/index.d.ts +2426 -0
  44. package/index.js +452 -0
  45. package/integrations.js +7 -0
  46. package/interfaces.js +2 -0
  47. package/loggers.js +125 -0
  48. package/messages.js +31 -0
  49. package/operators.js +106 -0
  50. package/package.json +105 -0
  51. package/polling.js +70 -0
  52. package/requestor.js +62 -0
  53. package/scripts/better-audit.sh +76 -0
  54. package/sharedtest/big_segment_store_tests.js +86 -0
  55. package/sharedtest/feature_store_tests.js +177 -0
  56. package/sharedtest/persistent_feature_store_tests.js +183 -0
  57. package/sharedtest/store_tests.js +7 -0
  58. package/streaming.js +179 -0
  59. package/test/LDClient-big-segments-test.js +92 -0
  60. package/test/LDClient-end-to-end-test.js +218 -0
  61. package/test/LDClient-evaluation-all-flags-test.js +226 -0
  62. package/test/LDClient-evaluation-test.js +204 -0
  63. package/test/LDClient-events-test.js +502 -0
  64. package/test/LDClient-listeners-test.js +180 -0
  65. package/test/LDClient-test.js +96 -0
  66. package/test/LDClient-tls-test.js +110 -0
  67. package/test/attribute_reference-test.js +494 -0
  68. package/test/big_segments-test.js +182 -0
  69. package/test/caching_store_wrapper-test.js +434 -0
  70. package/test/configuration-test.js +249 -0
  71. package/test/context-test.js +93 -0
  72. package/test/context_filter-test.js +424 -0
  73. package/test/diagnostic_events-test.js +152 -0
  74. package/test/evaluator-big-segments-test.js +301 -0
  75. package/test/evaluator-bucketing-test.js +333 -0
  76. package/test/evaluator-clause-test.js +277 -0
  77. package/test/evaluator-flag-test.js +452 -0
  78. package/test/evaluator-pre-conditions-test.js +105 -0
  79. package/test/evaluator-rule-test.js +131 -0
  80. package/test/evaluator-segment-match-test.js +310 -0
  81. package/test/evaluator_helpers.js +106 -0
  82. package/test/event_processor-test.js +680 -0
  83. package/test/event_summarizer-test.js +146 -0
  84. package/test/feature_store-test.js +42 -0
  85. package/test/feature_store_event_wrapper-test.js +182 -0
  86. package/test/feature_store_test_base.js +60 -0
  87. package/test/file_data_source-test.js +255 -0
  88. package/test/loggers-test.js +126 -0
  89. package/test/operators-test.js +102 -0
  90. package/test/polling-test.js +158 -0
  91. package/test/requestor-test.js +60 -0
  92. package/test/store_tests_big_segments-test.js +61 -0
  93. package/test/streaming-test.js +323 -0
  94. package/test/stubs.js +107 -0
  95. package/test/test_data-test.js +341 -0
  96. package/test/update_queue-test.js +61 -0
  97. package/test-types.ts +210 -0
  98. package/test_data.js +323 -0
  99. package/tsconfig.json +14 -0
  100. package/update_queue.js +28 -0
  101. package/utils/__tests__/httpUtils-test.js +39 -0
  102. package/utils/__tests__/wrapPromiseCallback-test.js +33 -0
  103. package/utils/asyncUtils.js +32 -0
  104. package/utils/httpUtils.js +105 -0
  105. package/utils/stringifyAttrs.js +14 -0
  106. package/utils/wrapPromiseCallback.js +36 -0
  107. package/versioned_data_kind.js +34 -0
@@ -0,0 +1,502 @@
1
+ const { TestData } = require('../integrations');
2
+
3
+ const stubs = require('./stubs');
4
+
5
+ describe('LDClient - analytics events', () => {
6
+
7
+ var eventProcessor;
8
+ var defaultUser = { key: 'user' };
9
+ var anonymousUser = { key: 'anon-user', anonymous: true };
10
+ var userWithNoKey = { name: 'Keyless Joe' };
11
+ var userWithEmptyKey = { key: '' };
12
+
13
+ beforeEach(() => {
14
+ eventProcessor = stubs.stubEventProcessor();
15
+ });
16
+
17
+ describe('feature event', () => {
18
+ it('generates event for existing feature', async () => {
19
+ const td = TestData();
20
+ td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1));
21
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
22
+ await client.waitForInitialization();
23
+ await client.variation('flagkey', defaultUser, 'c');
24
+
25
+ expect(eventProcessor.events).toHaveLength(1);
26
+ var e = eventProcessor.events[0];
27
+ expect(e).toMatchObject({
28
+ kind: 'feature',
29
+ key: 'flagkey',
30
+ version: 1,
31
+ context: defaultUser,
32
+ variation: 1,
33
+ value: 'b',
34
+ default: 'c'
35
+ });
36
+ });
37
+
38
+ it('generates event for existing feature when user is anonymous', async () => {
39
+ const td = TestData();
40
+ td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1));
41
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
42
+ await client.waitForInitialization();
43
+ await client.variation('flagkey', anonymousUser, 'c');
44
+
45
+ expect(eventProcessor.events).toHaveLength(1);
46
+ var e = eventProcessor.events[0];
47
+ expect(e).toMatchObject({
48
+ kind: 'feature',
49
+ key: 'flagkey',
50
+ version: 1,
51
+ context: anonymousUser,
52
+ variation: 1,
53
+ value: 'b',
54
+ default: 'c'
55
+ });
56
+ });
57
+
58
+ it('generates event for existing feature with reason', async () => {
59
+ const td = TestData();
60
+ td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1));
61
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
62
+ await client.waitForInitialization();
63
+ await client.variationDetail('flagkey', defaultUser, 'c');
64
+
65
+ expect(eventProcessor.events).toHaveLength(1);
66
+ var e = eventProcessor.events[0];
67
+ expect(e).toMatchObject({
68
+ kind: 'feature',
69
+ key: 'flagkey',
70
+ version: 1,
71
+ context: defaultUser,
72
+ variation: 1,
73
+ value: 'b',
74
+ default: 'c',
75
+ reason: { kind: 'FALLTHROUGH' }
76
+ });
77
+ });
78
+
79
+ it('forces tracking when a matched rule has trackEvents set', async () => {
80
+ const td = TestData();
81
+ td.usePreconfiguredFlag({ // TestData doesn't normally set trackEvents
82
+ key: 'flagkey',
83
+ version: 1,
84
+ on: true,
85
+ targets: [],
86
+ rules: [
87
+ {
88
+ clauses: [ { attribute: 'key', op: 'in', values: [ defaultUser.key ] } ],
89
+ variation: 0,
90
+ id: 'rule-id',
91
+ trackEvents: true
92
+ }
93
+ ],
94
+ fallthrough: { variation: 1 },
95
+ variations: ['a', 'b']
96
+ });
97
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
98
+ await client.waitForInitialization();
99
+ await client.variation('flagkey', defaultUser, 'c');
100
+
101
+ expect(eventProcessor.events).toHaveLength(1);
102
+ var e = eventProcessor.events[0];
103
+ expect(e).toEqual({
104
+ kind: 'feature',
105
+ creationDate: e.creationDate,
106
+ key: 'flagkey',
107
+ version: 1,
108
+ context: defaultUser,
109
+ variation: 0,
110
+ value: 'a',
111
+ default: 'c',
112
+ trackEvents: true,
113
+ reason: { kind: 'RULE_MATCH', ruleIndex: 0, ruleId: 'rule-id' }
114
+ });
115
+ });
116
+
117
+ it('does not force tracking when a matched rule does not have trackEvents set', async () => {
118
+ const td = TestData();
119
+ td.usePreconfiguredFlag({
120
+ key: 'flagkey',
121
+ version: 1,
122
+ on: true,
123
+ targets: [],
124
+ rules: [
125
+ {
126
+ clauses: [ { attribute: 'key', op: 'in', values: [ defaultUser.key ] } ],
127
+ variation: 0,
128
+ id: 'rule-id'
129
+ }
130
+ ],
131
+ fallthrough: { variation: 1 },
132
+ variations: ['a', 'b']
133
+ });
134
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
135
+ await client.waitForInitialization();
136
+ await client.variation('flagkey', defaultUser, 'c');
137
+
138
+ expect(eventProcessor.events).toHaveLength(1);
139
+ var e = eventProcessor.events[0];
140
+ expect(e).toEqual({
141
+ kind: 'feature',
142
+ creationDate: e.creationDate,
143
+ key: 'flagkey',
144
+ version: 1,
145
+ context: defaultUser,
146
+ variation: 0,
147
+ value: 'a',
148
+ default: 'c'
149
+ });
150
+ });
151
+
152
+ it('forces tracking for fallthrough result when trackEventsFallthrough is set', async () => {
153
+ const td = TestData();
154
+ td.usePreconfiguredFlag({
155
+ key: 'flagkey',
156
+ version: 1,
157
+ on: true,
158
+ targets: [],
159
+ rules: [],
160
+ fallthrough: { variation: 1 },
161
+ variations: ['a', 'b'],
162
+ trackEventsFallthrough: true
163
+ });
164
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
165
+ await client.waitForInitialization();
166
+ await client.variation('flagkey', defaultUser, 'c');
167
+
168
+ expect(eventProcessor.events).toHaveLength(1);
169
+ var e = eventProcessor.events[0];
170
+ expect(e).toEqual({
171
+ kind: 'feature',
172
+ creationDate: e.creationDate,
173
+ key: 'flagkey',
174
+ version: 1,
175
+ context: defaultUser,
176
+ variation: 1,
177
+ value: 'b',
178
+ default: 'c',
179
+ trackEvents: true,
180
+ reason: { kind: 'FALLTHROUGH' },
181
+ });
182
+ });
183
+
184
+ it('forces tracking when an evaluation is in the tracked portion of an experiment rollout', async () => {
185
+ const td = TestData();
186
+ td.usePreconfiguredFlag({
187
+ key: 'flagkey',
188
+ version: 1,
189
+ on: true,
190
+ targets: [],
191
+ rules: [],
192
+ fallthrough: {
193
+ rollout: {
194
+ kind: 'experiment',
195
+ variations: [
196
+ {
197
+ weight: 100000,
198
+ variation: 1,
199
+ },
200
+ ],
201
+ },
202
+ },
203
+ variations: ['a', 'b'],
204
+ });
205
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
206
+ await client.waitForInitialization();
207
+ await client.variation('flagkey', defaultUser, 'c');
208
+
209
+ expect(eventProcessor.events).toHaveLength(1);
210
+ var e = eventProcessor.events[0];
211
+ expect(e).toEqual({
212
+ kind: 'feature',
213
+ creationDate: e.creationDate,
214
+ key: 'flagkey',
215
+ version: 1,
216
+ context: defaultUser,
217
+ variation: 1,
218
+ value: 'b',
219
+ default: 'c',
220
+ trackEvents: true,
221
+ reason: { kind: 'FALLTHROUGH', inExperiment: true },
222
+ });
223
+ });
224
+
225
+ it('does not force tracking when an evaluation is in the untracked portion of an experiment rollout', async () => {
226
+ const td = TestData();
227
+ td.usePreconfiguredFlag({
228
+ key: 'flagkey',
229
+ version: 1,
230
+ on: true,
231
+ targets: [],
232
+ rules: [],
233
+ fallthrough: {
234
+ rollout: {
235
+ kind: 'experiment',
236
+ variations: [
237
+ {
238
+ weight: 100000,
239
+ variation: 1,
240
+ untracked: true,
241
+ },
242
+ ],
243
+ },
244
+ },
245
+ variations: ['a', 'b'],
246
+ });
247
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
248
+ await client.waitForInitialization();
249
+ await client.variation('flagkey', defaultUser, 'c');
250
+
251
+ expect(eventProcessor.events).toHaveLength(1);
252
+ var e = eventProcessor.events[0];
253
+ expect(e).toEqual({
254
+ kind: 'feature',
255
+ creationDate: e.creationDate,
256
+ key: 'flagkey',
257
+ version: 1,
258
+ context: defaultUser,
259
+ variation: 1,
260
+ value: 'b',
261
+ default: 'c',
262
+ });
263
+ });
264
+
265
+ it('does not force tracking for fallthrough result when trackEventsFallthrough is not set', async () => {
266
+ const td = TestData();
267
+ td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1));
268
+ var client = stubs.createClient({ eventProcessor, updateProcessor: td });
269
+ await client.waitForInitialization();
270
+ await client.variation('flagkey', defaultUser, 'c');
271
+
272
+ expect(eventProcessor.events).toHaveLength(1);
273
+ var e = eventProcessor.events[0];
274
+ expect(e).toEqual({
275
+ kind: 'feature',
276
+ creationDate: e.creationDate,
277
+ key: 'flagkey',
278
+ version: 1,
279
+ context: defaultUser,
280
+ variation: 1,
281
+ value: 'b',
282
+ default: 'c'
283
+ });
284
+ });
285
+
286
+ it('generates event for unknown feature', async () => {
287
+ var client = stubs.createClient({ eventProcessor: eventProcessor }, {});
288
+ await client.waitForInitialization();
289
+ await client.variation('flagkey', defaultUser, 'c');
290
+
291
+ expect(eventProcessor.events).toHaveLength(1);
292
+ var e = eventProcessor.events[0];
293
+ expect(e).toMatchObject({
294
+ kind: 'feature',
295
+ key: 'flagkey',
296
+ context: defaultUser,
297
+ value: 'c',
298
+ default: 'c'
299
+ });
300
+ });
301
+
302
+ it('generates event for unknown feature when user is anonymous', async () => {
303
+ var client = stubs.createClient({ eventProcessor: eventProcessor }, {});
304
+ await client.waitForInitialization();
305
+ await client.variation('flagkey', anonymousUser, 'c');
306
+
307
+ expect(eventProcessor.events).toHaveLength(1);
308
+ var e = eventProcessor.events[0];
309
+ expect(e).toMatchObject({
310
+ kind: 'feature',
311
+ key: 'flagkey',
312
+ context: anonymousUser,
313
+ value: 'c',
314
+ default: 'c'
315
+ });
316
+ });
317
+
318
+ it.each([
319
+ { name: 'Bob' },
320
+ undefined,
321
+ null,
322
+ {
323
+ kind: "multi",
324
+ org: {
325
+ // Missing key
326
+ name: "bad org"
327
+ },
328
+ user: {
329
+ key: "user-key"
330
+ }
331
+ },
332
+ {
333
+ kind: 'potato'
334
+ },
335
+ {
336
+ kind: 'kind',
337
+ key: 'bad'
338
+ },
339
+ ])('does not generate events for bad contexts', async (badContext) => {
340
+ const td = TestData();
341
+ td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1));
342
+ const client = stubs.createClient({ eventProcessor, updateProcessor: td });
343
+ await client.waitForInitialization();
344
+ await client.variation('flagkey', badContext, 'c');
345
+
346
+ expect(eventProcessor.events).toHaveLength(0);
347
+ });
348
+
349
+ });
350
+
351
+ describe('identify', () => {
352
+ it('generates an event', async () => {
353
+ var logger = stubs.stubLogger();
354
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
355
+ await client.waitForInitialization();
356
+
357
+ client.identify(defaultUser);
358
+ expect(eventProcessor.events).toHaveLength(1);
359
+ var e = eventProcessor.events[0];
360
+ expect(e).toMatchObject({
361
+ kind: 'identify',
362
+ context: defaultUser
363
+ });
364
+ expect(logger.warn).not.toHaveBeenCalled();
365
+ });
366
+
367
+ it('does not generate an event, and logs a warning, if user is missing', async () => {
368
+ var logger = stubs.stubLogger();
369
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
370
+ await client.waitForInitialization();
371
+
372
+ client.identify();
373
+ expect(eventProcessor.events).toHaveLength(0);
374
+ expect(logger.warn).toHaveBeenCalledTimes(1);
375
+ });
376
+
377
+ it('does not generate an event, and logs a warning, if user has no key', async () => {
378
+ var logger = stubs.stubLogger();
379
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
380
+ await client.waitForInitialization();
381
+
382
+ client.identify(userWithNoKey);
383
+ expect(eventProcessor.events).toHaveLength(0);
384
+ expect(logger.warn).toHaveBeenCalledTimes(1);
385
+ });
386
+
387
+ it('does not generate an event, and logs a warning, if user has empty key', async () => {
388
+ var logger = stubs.stubLogger();
389
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
390
+ await client.waitForInitialization();
391
+
392
+ client.identify(userWithEmptyKey);
393
+ expect(eventProcessor.events).toHaveLength(0);
394
+ expect(logger.warn).toHaveBeenCalledTimes(1);
395
+ });
396
+ });
397
+
398
+ describe('track', () => {
399
+ it('generates an event with data', async () => {
400
+ var data = { thing: 'stuff' };
401
+ var logger = stubs.stubLogger();
402
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
403
+ await client.waitForInitialization();
404
+
405
+ client.track('eventkey', defaultUser, data);
406
+ expect(eventProcessor.events).toHaveLength(1);
407
+ var e = eventProcessor.events[0];
408
+ expect(e).toMatchObject({
409
+ kind: 'custom',
410
+ key: 'eventkey',
411
+ context: defaultUser,
412
+ data: data
413
+ });
414
+ expect(logger.warn).not.toHaveBeenCalled();
415
+ });
416
+
417
+ it('generates an event without data', async () => {
418
+ var logger = stubs.stubLogger();
419
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
420
+ await client.waitForInitialization();
421
+
422
+ client.track('eventkey', defaultUser);
423
+ expect(eventProcessor.events).toHaveLength(1);
424
+ var e = eventProcessor.events[0];
425
+ expect(e).toMatchObject({
426
+ kind: 'custom',
427
+ key: 'eventkey',
428
+ context: defaultUser
429
+ });
430
+ expect(e.metricValue).not.toBe(expect.anything());
431
+ expect(logger.warn).not.toHaveBeenCalled();
432
+ });
433
+
434
+ it('generates an event with a metric value', async () => {
435
+ var data = { thing: 'stuff' };
436
+ var metricValue = 1.5;
437
+ var logger = stubs.stubLogger();
438
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
439
+ await client.waitForInitialization();
440
+
441
+ client.track('eventkey', defaultUser, data, metricValue);
442
+ expect(eventProcessor.events).toHaveLength(1);
443
+ var e = eventProcessor.events[0];
444
+ expect(e).toMatchObject({
445
+ kind: 'custom',
446
+ key: 'eventkey',
447
+ context: defaultUser,
448
+ data: data,
449
+ metricValue: metricValue
450
+ });
451
+ expect(logger.warn).not.toHaveBeenCalled();
452
+ });
453
+
454
+ it('generates an event for an anonymous user', async () => {
455
+ var data = { thing: 'stuff' };
456
+ var logger = stubs.stubLogger();
457
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
458
+ await client.waitForInitialization();
459
+
460
+ client.track('eventkey', anonymousUser, data);
461
+ expect(eventProcessor.events).toHaveLength(1);
462
+ var e = eventProcessor.events[0];
463
+ expect(e).toMatchObject({
464
+ kind: 'custom',
465
+ key: 'eventkey',
466
+ context: anonymousUser,
467
+ data: data
468
+ });
469
+ expect(logger.warn).not.toHaveBeenCalled();
470
+ });
471
+
472
+ it('does not generate an event, and logs a warning, if user is missing', async () => {
473
+ var logger = stubs.stubLogger();
474
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
475
+ await client.waitForInitialization();
476
+
477
+ client.track('eventkey');
478
+ expect(eventProcessor.events).toHaveLength(0);
479
+ expect(logger.warn).toHaveBeenCalledTimes(1);
480
+ });
481
+
482
+ it('does not generate an event, and logs a warning, if user has no key', async () => {
483
+ var logger = stubs.stubLogger();
484
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
485
+ await client.waitForInitialization();
486
+
487
+ client.track('eventkey', userWithNoKey);
488
+ expect(eventProcessor.events).toHaveLength(0);
489
+ expect(logger.warn).toHaveBeenCalledTimes(1);
490
+ });
491
+
492
+ it('does not generate an event, and logs a warning, if user has empty key', async () => {
493
+ var logger = stubs.stubLogger();
494
+ var client = stubs.createClient({ eventProcessor: eventProcessor, logger: logger }, {});
495
+ await client.waitForInitialization();
496
+
497
+ client.track('eventkey', userWithEmptyKey);
498
+ expect(eventProcessor.events).toHaveLength(0);
499
+ expect(logger.warn).toHaveBeenCalledTimes(1);
500
+ });
501
+ });
502
+ });
@@ -0,0 +1,180 @@
1
+ const { TestData } = require('../integrations');
2
+
3
+ const { withClient } = require('./stubs');
4
+ import { AsyncQueue } from 'launchdarkly-js-test-helpers';
5
+ import { makeFlagWithSegmentMatch } from './evaluator_helpers';
6
+
7
+ describe('LDClient event listeners', () => {
8
+ describe('flag change events', () => {
9
+ it('sends event when flag is added', async () => {
10
+ const td = TestData();
11
+ await withClient({ updateProcessor: td }, async client => {
12
+ const changes = new AsyncQueue();
13
+ client.on('update', params => changes.add(params));
14
+
15
+ td.update(td.flag('new-flag'));
16
+
17
+ const change = await changes.take();
18
+ expect(change.key).toEqual('new-flag');
19
+ });
20
+ });
21
+
22
+ it('sends event when flag is updated', async () => {
23
+ const td = TestData();
24
+ td.update(td.flag('flag1').on(true));
25
+ td.update(td.flag('flag2').on(true));
26
+
27
+ await withClient({ updateProcessor: td }, async client => {
28
+ const changes = new AsyncQueue();
29
+ const flag2Changes = new AsyncQueue();
30
+ client.on('update', params => changes.add(params));
31
+ client.on('update:flag2', params => flag2Changes.add(params));
32
+
33
+ td.update(td.flag('flag1').on(false));
34
+ td.update(td.flag('flag2').on(false));
35
+
36
+ const change1 = await changes.take();
37
+ expect(change1.key).toEqual('flag1');
38
+ const change2 = await changes.take();
39
+ expect(change2.key).toEqual('flag2');
40
+
41
+ const flag2Change = await flag2Changes.take();
42
+ expect(flag2Change.key).toEqual('flag2');
43
+ });
44
+ });
45
+
46
+ it('sends an event when a segment used by a flag is updated', async () => {
47
+ const td = TestData();
48
+ const segment = {
49
+ key: 'segment1',
50
+ includedContexts: [ {contextKind: 'org', values: ['org-key']} ],
51
+ version: 1
52
+ };
53
+
54
+ td.usePreconfiguredSegment(segment);
55
+ td.usePreconfiguredFlag(makeFlagWithSegmentMatch(segment));
56
+
57
+ await withClient({ updateProcessor: td }, async client => {
58
+ const flagChanges = new AsyncQueue();
59
+ client.on('update:feature', params => flagChanges.add(params));
60
+
61
+ td.usePreconfiguredSegment({
62
+ key: 'segment1',
63
+ includedContexts: [ {contextKind: 'org', values: ['org-key', 'second-key']} ],
64
+ version: 2
65
+ });
66
+
67
+ const flagChange = await flagChanges.take();
68
+ expect(flagChange.key).toEqual('feature');
69
+ });
70
+ });
71
+ });
72
+
73
+ it('sends an event for a nested segment update', async () => {
74
+ const td = TestData();
75
+ const segment1 = {
76
+ key: 'segment1',
77
+ includedContexts: [ {contextKind: 'org', values: ['org-key']} ],
78
+ version: 1
79
+ };
80
+ const segment2 = {
81
+ key: 'segment2',
82
+ rules: [
83
+ {
84
+ clauses: [ { attribute: '', op: 'segmentMatch', values: [segment1.key] } ],
85
+ weight: 100000
86
+ }
87
+ ],
88
+ version: 1
89
+ };
90
+ td.usePreconfiguredSegment(segment1);
91
+ td.usePreconfiguredSegment(segment2);
92
+ td.usePreconfiguredFlag(makeFlagWithSegmentMatch(segment2));
93
+
94
+ await withClient({ updateProcessor: td }, async client => {
95
+ const flagChanges = new AsyncQueue();
96
+ client.on('update:feature', params => flagChanges.add(params));
97
+
98
+ td.usePreconfiguredSegment({
99
+ key: 'segment1',
100
+ includedContexts: [ {contextKind: 'org', values: ['org-key', 'second-key']} ],
101
+ version: 2
102
+ });
103
+
104
+ const flagChange = await flagChanges.take();
105
+ expect(flagChange.key).toEqual('feature');
106
+ });
107
+ });
108
+
109
+ it('does not hang on circular segment dependencies', async () => {
110
+ const td = TestData();
111
+ const segment1 = {
112
+ key: 'segment1',
113
+ clauses: [ { attribute: '', op: 'segmentMatch', values: ['segment2'] } ],
114
+ version: 1
115
+ };
116
+ const segment2 = {
117
+ key: 'segment2',
118
+ rules: [
119
+ {
120
+ clauses: [ { attribute: '', op: 'segmentMatch', values: [segment1.key] } ],
121
+ weight: 100000
122
+ }
123
+ ],
124
+ version: 1
125
+ };
126
+
127
+ td.usePreconfiguredSegment(segment1);
128
+ td.usePreconfiguredSegment(segment2);
129
+ td.usePreconfiguredFlag(makeFlagWithSegmentMatch(segment2));
130
+
131
+ // The implementation happens to produce the event anyway,
132
+ // but what we really care about is that it doesn't hang.
133
+ // So, if in the future, it didn't produce an event for this malformed flag,
134
+ // then that would likely be ok. The malformed nature of a circular
135
+ // dependency should be transient.
136
+ await withClient({ updateProcessor: td }, async client => {
137
+ const flagChanges = new AsyncQueue();
138
+ client.on('update:feature', params => flagChanges.add(params));
139
+
140
+ td.usePreconfiguredSegment({
141
+ key: 'segment1',
142
+ includedContexts: [ {contextKind: 'org', values: ['org-key', 'second-key']} ],
143
+ version: 2
144
+ });
145
+
146
+ const flagChange = await flagChanges.take();
147
+ expect(flagChange.key).toEqual('feature');
148
+ });
149
+ });
150
+
151
+ describe('bigSegmentStoreStatusProvider', () => {
152
+ it('returns unavailable status when not configured', async () => {
153
+ await withClient({}, async client => {
154
+ expect(client.bigSegmentStoreStatusProvider.getStatus()).toBeUndefined();
155
+ const status = await client.bigSegmentStoreStatusProvider.requireStatus();
156
+ expect(status.available).toBe(false);
157
+ expect(status.stale).toBe(false);
158
+ });
159
+ });
160
+
161
+ it('sends status updates', async () => {
162
+ const store = {
163
+ getMetadata: async () => { return { lastUpToDate: new Date().getTime() }; },
164
+ };
165
+ const config = { bigSegments: { store: () => store, statusPollInterval: 0.01 } };
166
+ await withClient(config, async client => {
167
+ const status1 = await client.bigSegmentStoreStatusProvider.requireStatus();
168
+ expect(status1.available).toBe(true);
169
+
170
+ const statuses = new AsyncQueue();
171
+ client.bigSegmentStoreStatusProvider.on('change', s => statuses.add(s));
172
+
173
+ store.getMetadata = async () => { throw new Exception('sorry'); };
174
+
175
+ const status2 = await statuses.take();
176
+ expect(status2.available).toBe(false);
177
+ });
178
+ });
179
+ });
180
+ });