@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,452 @@
1
+ const { Evaluator } = require('../evaluator');
2
+ const {
3
+ basicUser,
4
+ eventFactory,
5
+ prepareQueries,
6
+ makeFlagWithRules,
7
+ asyncEvaluate,
8
+ makeClauseThatDoesNotMatchUser,
9
+ } = require('./evaluator_helpers');
10
+
11
+ // Tests of flag evaluation at the highest level. Rule-level and clause-level behavior is covered
12
+ // in detail in evaluator-rule-test, evaluator-clause-test, and evaluator-segment-match-test.
13
+
14
+ describe('Evaluator - basic flag behavior', () => {
15
+ describe('flag is off', () => {
16
+ it('returns off variation', async () => {
17
+ const flag = {
18
+ key: 'feature',
19
+ on: false,
20
+ offVariation: 1,
21
+ fallthrough: { variation: 0 },
22
+ variations: ['a', 'b', 'c']
23
+ };
24
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
25
+ expect(detail).toMatchObject({ value: 'b', variationIndex: 1, reason: { kind: 'OFF' } });
26
+ expect(events).toBeUndefined();
27
+ });
28
+
29
+ it('returns null if off variation is unspecified', async () => {
30
+ const flag = {
31
+ key: 'feature',
32
+ on: false,
33
+ fallthrough: { variation: 0 },
34
+ variations: ['a', 'b', 'c']
35
+ };
36
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
37
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'OFF' } });
38
+ expect(events).toBeUndefined();
39
+ });
40
+
41
+ it('returns error if off variation is too high', async () => {
42
+ const flag = {
43
+ key: 'feature',
44
+ on: false,
45
+ offVariation: 99,
46
+ fallthrough: { variation: 0 },
47
+ variations: ['a', 'b', 'c']
48
+ };
49
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
50
+ expect(err).toEqual(Error('Invalid variation index in flag'));
51
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } });
52
+ expect(events).toBeUndefined();
53
+ });
54
+
55
+ it('returns error if off variation is negative', async () => {
56
+ const flag = {
57
+ key: 'feature',
58
+ on: false,
59
+ offVariation: -1,
60
+ fallthrough: { variation: 0 },
61
+ variations: ['a', 'b', 'c']
62
+ };
63
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
64
+ expect(err).toEqual(Error('Invalid variation index in flag'));
65
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } });
66
+ expect(events).toBeUndefined();
67
+ });
68
+ });
69
+
70
+ describe('fallthrough - flag is on and no rules match', () => {
71
+ const noMatchClause = makeClauseThatDoesNotMatchUser(basicUser);
72
+
73
+ it('returns fallthrough variation', async () => {
74
+ var rule = { id: 'id', clauses: [noMatchClause], variation: 2 };
75
+ const flag = makeFlagWithRules([rule], { variation: 0 });
76
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
77
+ expect(detail).toMatchObject({ value: 'a', variationIndex: 0, reason: { kind: 'FALLTHROUGH' } });
78
+ expect(events).toBeUndefined();
79
+ });
80
+
81
+ it('returns error if fallthrough variation is too high', async () => {
82
+ var rule = { id: 'id', clauses: [noMatchClause], variation: 99 };
83
+ const flag = makeFlagWithRules([rule], { variation: 99 });
84
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
85
+ expect(err).toEqual(Error('Invalid variation index in flag'));
86
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' }});
87
+ expect(events).toBeUndefined();
88
+ });
89
+
90
+ it('returns error if fallthrough variation is negative', async () => {
91
+ var rule = { id: 'id', clauses: [noMatchClause], variation: 99 };
92
+ const flag = makeFlagWithRules([rule], { variation: -1 });
93
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
94
+ expect(err).toEqual(Error('Invalid variation index in flag'));
95
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' }});
96
+ expect(events).toBeUndefined();
97
+ });
98
+
99
+ it('returns error if fallthrough has no variation or rollout', async () => {
100
+ var rule = { id: 'id', clauses: [noMatchClause], variation: 99 };
101
+ const flag = makeFlagWithRules([rule], { });
102
+ var user = { key: 'x' };
103
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
104
+ expect(err).toEqual(Error('Variation/rollout object with no variation or rollout'));
105
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' }});
106
+ expect(events).toBeUndefined();
107
+ });
108
+
109
+ it('returns error if fallthrough has rollout with no variations', async () => {
110
+ var rule = { id: 'id', clauses: [noMatchClause], variation: 99 };
111
+ const flag = makeFlagWithRules([rule], { rollout: { variations: [] } });
112
+ var user = { key: 'x' };
113
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
114
+ expect(err).toEqual(Error('Variation/rollout object with no variation or rollout'));
115
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' }});
116
+ expect(events).toBeUndefined();
117
+ });
118
+ });
119
+
120
+ describe('prerequisites', () => {
121
+ it('returns off variation if prerequisite is not found', async () => {
122
+ const flag = {
123
+ key: 'feature0',
124
+ on: true,
125
+ prerequisites: [{key: 'badfeature', variation: 1}],
126
+ fallthrough: { variation: 0 },
127
+ offVariation: 1,
128
+ variations: ['a', 'b', 'c']
129
+ };
130
+ const e = Evaluator(prepareQueries({}));
131
+ const [ err, detail, events ] = await asyncEvaluate(e, flag, basicUser, eventFactory);
132
+ expect(detail).toMatchObject({ value: 'b', variationIndex: 1,
133
+ reason: { kind: 'PREREQUISITE_FAILED', prerequisiteKey: 'badfeature' } });
134
+ expect(events).toBeUndefined();
135
+ });
136
+
137
+ it('returns off variation and event if prerequisite is off', async () => {
138
+ const flag = {
139
+ key: 'feature0',
140
+ on: true,
141
+ prerequisites: [{key: 'feature1', variation: 1}],
142
+ fallthrough: { variation: 0 },
143
+ offVariation: 1,
144
+ targets: [],
145
+ rules: [],
146
+ variations: ['a', 'b', 'c'],
147
+ version: 1
148
+ };
149
+ const flag1 = {
150
+ key: 'feature1',
151
+ on: false,
152
+ offVariation: 1,
153
+ // note that even though it returns the desired variation, it is still off and therefore not a match
154
+ fallthrough: { variation: 0 },
155
+ targets: [],
156
+ rules: [],
157
+ variations: ['d', 'e'],
158
+ version: 2
159
+ };
160
+ const e = Evaluator(prepareQueries({flags: [flag, flag1]}));
161
+ const eventsShouldBe = [
162
+ { kind: 'feature', key: 'feature1', variation: 1, value: 'e', version: 2, prereqOf: 'feature0' }
163
+ ];
164
+ const [ err, detail, events ] = await asyncEvaluate(e, flag, basicUser, eventFactory);
165
+ expect(detail).toMatchObject({ value: 'b', variationIndex: 1,
166
+ reason: { kind: 'PREREQUISITE_FAILED', prerequisiteKey: 'feature1' } });
167
+ expect(events).toMatchObject(eventsShouldBe);
168
+ });
169
+
170
+ it('returns off variation and event if prerequisite is not met', async () => {
171
+ const flag = {
172
+ key: 'feature0',
173
+ on: true,
174
+ prerequisites: [{key: 'feature1', variation: 1}],
175
+ fallthrough: { variation: 0 },
176
+ offVariation: 1,
177
+ targets: [],
178
+ rules: [],
179
+ variations: ['a', 'b', 'c'],
180
+ version: 1
181
+ };
182
+ const flag1 = {
183
+ key: 'feature1',
184
+ on: true,
185
+ fallthrough: { variation: 0 },
186
+ targets: [],
187
+ rules: [],
188
+ variations: ['d', 'e'],
189
+ version: 2
190
+ };
191
+ const e = Evaluator(prepareQueries({ flags: [flag, flag1] }));
192
+ const eventsShouldBe = [
193
+ { kind: 'feature', key: 'feature1', variation: 0, value: 'd', version: 2, prereqOf: 'feature0' }
194
+ ];
195
+ const [ err, detail, events ] = await asyncEvaluate(e, flag, basicUser, eventFactory);
196
+ expect(detail).toMatchObject({ value: 'b', variationIndex: 1,
197
+ reason: { kind: 'PREREQUISITE_FAILED', prerequisiteKey: 'feature1' } });
198
+ expect(events).toMatchObject(eventsShouldBe);
199
+ });
200
+
201
+ it('returns fallthrough variation and event if prerequisite is met and there are no rules', async () => {
202
+ const flag = {
203
+ key: 'feature0',
204
+ on: true,
205
+ prerequisites: [{key: 'feature1', variation: 1}],
206
+ fallthrough: { variation: 0 },
207
+ offVariation: 1,
208
+ targets: [],
209
+ rules: [],
210
+ variations: ['a', 'b', 'c'],
211
+ version: 1
212
+ };
213
+ const flag1 = {
214
+ key: 'feature1',
215
+ on: true,
216
+ fallthrough: { variation: 1 },
217
+ targets: [],
218
+ rules: [],
219
+ variations: ['d', 'e'],
220
+ version: 2
221
+ };
222
+ const e = Evaluator(prepareQueries({ flags: [flag, flag1] }))
223
+ const eventsShouldBe = [
224
+ { kind: 'feature', key: 'feature1', variation: 1, value: 'e', version: 2, prereqOf: 'feature0' }
225
+ ];
226
+ const [ err, detail, events ] = await asyncEvaluate(e, flag, basicUser, eventFactory);
227
+ expect(detail).toMatchObject({ value: 'a', variationIndex: 0, reason: { kind: 'FALLTHROUGH' } });
228
+ expect(events).toMatchObject(eventsShouldBe);
229
+ });
230
+ });
231
+
232
+ describe('targets', () => {
233
+ it('matches user from targets', async () => {
234
+ const flag = {
235
+ key: 'feature0',
236
+ on: true,
237
+ rules: [],
238
+ targets: [
239
+ {
240
+ variation: 2,
241
+ values: ['some', 'userkey', 'or', 'other']
242
+ }
243
+ ],
244
+ fallthrough: { variation: 0 },
245
+ offVariation: 1,
246
+ variations: ['a', 'b', 'c']
247
+ };
248
+ const user = { key: 'userkey' };
249
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, user, eventFactory);
250
+ expect(detail).toMatchObject({ value: 'c', variationIndex: 2, reason: { kind: 'TARGET_MATCH' } });
251
+ expect(events).toBeUndefined();
252
+ });
253
+
254
+ it('does not break when there are no values in a target', async () => {
255
+ const flag = {
256
+ key: 'feature0',
257
+ on: true,
258
+ rules: [],
259
+ targets: [
260
+ {
261
+ variation: 2,
262
+ }
263
+ ],
264
+ fallthrough: { variation: 0 },
265
+ offVariation: 1,
266
+ variations: ['a', 'b', 'c']
267
+ };
268
+ const user = { key: 'userkey' };
269
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, user, eventFactory);
270
+ expect(detail).toMatchObject({ value: 'a', variationIndex: 0, reason: { kind: 'FALLTHROUGH' } });
271
+ expect(events).toBeUndefined();
272
+ });
273
+
274
+ it('matches single kind user from targets', async () => {
275
+ const flag = {
276
+ key: 'feature0',
277
+ on: true,
278
+ rules: [],
279
+ targets: [
280
+ {
281
+ variation: 2,
282
+ values: ['some', 'userkey', 'or', 'other']
283
+ }
284
+ ],
285
+ fallthrough: { variation: 0 },
286
+ offVariation: 1,
287
+ variations: ['a', 'b', 'c']
288
+ };
289
+ const context = { kind: 'user', key: 'userkey' };
290
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, context, eventFactory);
291
+ expect(detail).toMatchObject({ value: 'c', variationIndex: 2, reason: { kind: 'TARGET_MATCH' } });
292
+ expect(events).toBeUndefined();
293
+ });
294
+
295
+ it('matches single kind non-user from contextTargets', async () => {
296
+ const flag = {
297
+ key: 'feature0',
298
+ on: true,
299
+ rules: [],
300
+ contextTargets: [
301
+ {
302
+ variation: 2,
303
+ values: ['some', 'nonUserkey', 'or', 'other'],
304
+ contextKind: 'non-user'
305
+ }
306
+ ],
307
+ fallthrough: { variation: 0 },
308
+ offVariation: 1,
309
+ variations: ['a', 'b', 'c']
310
+ };
311
+ const context = { kind: 'non-user', key: 'nonUserkey' };
312
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, context, eventFactory);
313
+ expect(detail).toMatchObject({ value: 'c', variationIndex: 2, reason: { kind: 'TARGET_MATCH' } });
314
+ expect(events).toBeUndefined();
315
+ });
316
+
317
+ it('matches multi-kind context with user from targets', async () => {
318
+ const flag = {
319
+ key: 'feature0',
320
+ on: true,
321
+ rules: [],
322
+ targets: [
323
+ {
324
+ variation: 2,
325
+ values: ['some', 'userkey', 'or', 'other']
326
+ }
327
+ ],
328
+ fallthrough: { variation: 0 },
329
+ offVariation: 1,
330
+ variations: ['a', 'b', 'c']
331
+ };
332
+ const context = { kind: 'multi', user: {key: 'userkey' }};
333
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, context, eventFactory);
334
+ expect(detail).toMatchObject({ value: 'c', variationIndex: 2, reason: { kind: 'TARGET_MATCH' } });
335
+ expect(events).toBeUndefined();
336
+ });
337
+
338
+ it('matches a user in a multi-kind context with contextTargets order', async () => {
339
+ const flag = {
340
+ key: 'feature0',
341
+ on: true,
342
+ rules: [],
343
+ targets: [
344
+ {
345
+ variation: 2,
346
+ values: ['some', 'userkey', 'or', 'other']
347
+ }
348
+ ],
349
+ contextTargets: [{
350
+ variation: 2,
351
+ contextKind: 'user',
352
+ values: []
353
+ },{
354
+ variation: 1,
355
+ contextKind: 'farm',
356
+ values: ['cat']
357
+ }],
358
+ fallthrough: { variation: 0},
359
+ offVariation: 0,
360
+ variations: ['a', 'b', 'c']
361
+ };
362
+
363
+ const context = { kind: 'multi', farm: {key: 'cat' }, user: {key: 'userkey' }};
364
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, context, eventFactory);
365
+ expect(detail).toMatchObject({ value: 'c', variationIndex: 2, reason: { kind: 'TARGET_MATCH' } });
366
+ expect(events).toBeUndefined();
367
+ });
368
+
369
+ it('does not match a user in a multi-kind context with contextTargets order if key is not present', async () => {
370
+ const flag = {
371
+ key: 'feature0',
372
+ on: true,
373
+ rules: [],
374
+ targets: [
375
+ {
376
+ variation: 2,
377
+ values: ['some', 'userkey', 'or', 'other']
378
+ }
379
+ ],
380
+ contextTargets: [{
381
+ variation: 2,
382
+ contextKind: 'user',
383
+ values: []
384
+ },{
385
+ variation: 1,
386
+ contextKind: 'farm',
387
+ values: ['cat']
388
+ }],
389
+ fallthrough: { variation: 0},
390
+ offVariation: 0,
391
+ variations: ['a', 'b', 'c']
392
+ };
393
+
394
+ const context = { kind: 'multi', farm: {key: 'dog' }, user: {key: 'cat' }};
395
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, context, eventFactory);
396
+ expect(detail).toMatchObject({ value: 'a', variationIndex: 0, reason: { kind: 'FALLTHROUGH' } });
397
+ expect(events).toBeUndefined();
398
+ });
399
+
400
+ it('matches contextTargets order with a non-user match ahead of a user.', async () => {
401
+ const flag = {
402
+ key: 'feature0',
403
+ on: true,
404
+ rules: [],
405
+ targets: [
406
+ {
407
+ variation: 2,
408
+ values: ['some', 'userkey', 'or', 'other']
409
+ }
410
+ ],
411
+ contextTargets: [{
412
+ variation: 1,
413
+ contextKind: 'farm',
414
+ values: ['cat']
415
+ }, {
416
+ variation: 2,
417
+ contextKind: 'user',
418
+ values: []
419
+ }],
420
+ fallthrough: { variation: 0},
421
+ offVariation: 0,
422
+ variations: ['a', 'b', 'c']
423
+ };
424
+
425
+ const context = { kind: 'multi', farm: {key: 'cat' }, user: {key: 'userkey' }};
426
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, context, eventFactory);
427
+ expect(detail).toMatchObject({ value: 'b', variationIndex: 1, reason: { kind: 'TARGET_MATCH' } });
428
+ expect(events).toBeUndefined();
429
+ });
430
+
431
+ it('matches a context in a multi-kind context with a contextTarget', async () => {
432
+ const flag = {
433
+ key: 'feature0',
434
+ on: true,
435
+ rules: [],
436
+ contextTargets: [{
437
+ variation: 1,
438
+ contextKind: 'farm',
439
+ values: ['cat']
440
+ }],
441
+ fallthrough: { variation: 0},
442
+ offVariation: 0,
443
+ variations: ['a', 'b', 'c']
444
+ };
445
+
446
+ const context = { kind: 'multi', farm: {key: 'cat' }};
447
+ const [ err, detail, events ] = await asyncEvaluate(Evaluator(), flag, context, eventFactory);
448
+ expect(detail).toMatchObject({ value: 'b', variationIndex: 1, reason: { kind: 'TARGET_MATCH' } });
449
+ expect(events).toBeUndefined();
450
+ });
451
+ });
452
+ });
@@ -0,0 +1,105 @@
1
+ const { Evaluator } = require('../evaluator');
2
+
3
+ const {
4
+ eventFactory,
5
+ asyncEvaluate,
6
+ } = require('./evaluator_helpers');
7
+
8
+ describe('when given a bad context', () => {
9
+ it('handles a legacy user without a key', async () => {
10
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), {}, {}, eventFactory);
11
+ expect(detail).toEqual({
12
+ value: null,
13
+ variationIndex: null,
14
+ reason: {
15
+ kind: 'ERROR',
16
+ errorKind: 'USER_NOT_SPECIFIED'
17
+ }
18
+ });
19
+ });
20
+
21
+ it('handles a single kind context without a key', async () => {
22
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), {}, {
23
+ kind: 'user'
24
+ }, eventFactory);
25
+ expect(detail).toEqual({
26
+ value: null,
27
+ variationIndex: null,
28
+ reason: {
29
+ kind: 'ERROR',
30
+ errorKind: 'USER_NOT_SPECIFIED'
31
+ }
32
+ });
33
+ });
34
+
35
+
36
+ it.each(["", " ", "#^&%&^", "almost ", 8, true, {}])
37
+ ('handles a single kind context with an invalid kind', async (kind) => {
38
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), {}, {
39
+ kind,
40
+ key: 'goodKey'
41
+ }, eventFactory);
42
+ expect(detail).toEqual({
43
+ value: null,
44
+ variationIndex: null,
45
+ reason: {
46
+ kind: 'ERROR',
47
+ errorKind: 'USER_NOT_SPECIFIED'
48
+ }
49
+ });
50
+ });
51
+
52
+ // For a multi-kind context the act of making something a key will
53
+ // produce a string. So testing non-string types is just testing
54
+ // the characters they contain.
55
+ it.each(["", " ", "#^&%&^", "almost "])
56
+ ('handles a multi kind context with an invalid kind', async (kind) => {
57
+ const context = {
58
+ kind: 'multi',
59
+ };
60
+ context[kind] = {
61
+ key: 'goodKey'
62
+ }
63
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), {}, context, eventFactory);
64
+ expect(detail).toEqual({
65
+ value: null,
66
+ variationIndex: null,
67
+ reason: {
68
+ kind: 'ERROR',
69
+ errorKind: 'USER_NOT_SPECIFIED'
70
+ }
71
+ });
72
+ });
73
+
74
+ it.each([undefined, null])
75
+ ('handles a multi kind context with a context missing a key', async (key) => {
76
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), {}, {
77
+ kind: 'multi',
78
+ user: {
79
+ key
80
+ }
81
+ }, eventFactory);
82
+ expect(detail).toEqual({
83
+ value: null,
84
+ variationIndex: null,
85
+ reason: {
86
+ kind: 'ERROR',
87
+ errorKind: 'USER_NOT_SPECIFIED'
88
+ }
89
+ });
90
+ });
91
+ });
92
+
93
+ it('handles a missing flag', async () => {
94
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), undefined, {
95
+ key: "userKey"
96
+ }, eventFactory);
97
+ expect(detail).toEqual({
98
+ value: null,
99
+ variationIndex: null,
100
+ reason: {
101
+ kind: 'ERROR',
102
+ errorKind: 'FLAG_NOT_FOUND'
103
+ }
104
+ });
105
+ });
@@ -0,0 +1,131 @@
1
+ const { Evaluator } = require('../evaluator');
2
+ const {
3
+ basicUser,
4
+ basicSingleKindUser,
5
+ basicMultiKindUser,
6
+ eventFactory,
7
+ makeFlagWithRules,
8
+ asyncEvaluate,
9
+ makeClauseThatMatchesUser,
10
+ makeClauseThatDoesNotMatchUser,
11
+ } = require('./evaluator_helpers');
12
+
13
+ // Tests of flag evaluation at the rule level. Clause-level behavior is covered in detail in
14
+ // evaluator-clause-test and evaluator-segment-match-test.
15
+
16
+ // const basicUser = { key: 'userkey' };
17
+ // const singleKindUser = { kind: 'user', key: 'userkey' };
18
+ // const multiKindWithUser = { kind: 'multi', user: { key: 'userkey' } };
19
+
20
+ describe('Evaluator - rules with user kinds', () => {
21
+ const matchClause = makeClauseThatMatchesUser(basicUser);
22
+ const noMatchClause = makeClauseThatDoesNotMatchUser(basicUser);
23
+
24
+ it.each([basicUser, basicSingleKindUser, basicMultiKindUser])
25
+ ('matches user from rules', async (userToTest) => {
26
+ const rule0 = { id: 'id0', clauses: [noMatchClause], variation: 1 };
27
+ const rule1 = { id: 'id1', clauses: [matchClause], variation: 2 };
28
+ const flag = makeFlagWithRules([rule0, rule1]);
29
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), flag, userToTest, eventFactory);
30
+ expect(detail).toMatchObject({
31
+ value: 'c', variationIndex: 2,
32
+ reason: { kind: 'RULE_MATCH', ruleIndex: 1, ruleId: 'id1' }
33
+ });
34
+ expect(events).toBeUndefined();
35
+ });
36
+
37
+
38
+
39
+ it.each([basicUser, basicSingleKindUser, basicMultiKindUser])
40
+ ('returns error if rule variation is too high', async (userToTest) => {
41
+ const rule = { id: 'id', clauses: [matchClause], variation: 99 };
42
+ const flag = makeFlagWithRules([rule]);
43
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), flag, userToTest, eventFactory);
44
+ expect(err).toEqual(Error('Invalid variation index in flag'));
45
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } });
46
+ expect(events).toBeUndefined();
47
+ });
48
+
49
+ it.each([basicUser, basicSingleKindUser, basicMultiKindUser])
50
+ ('returns error if rule variation is negative', async (userToTest) => {
51
+ const rule = { id: 'id', clauses: [matchClause], variation: -1 };
52
+ const flag = makeFlagWithRules([rule]);
53
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), flag, userToTest, eventFactory);
54
+ expect(err).toEqual(Error('Invalid variation index in flag'));
55
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } });
56
+ expect(events).toBeUndefined();
57
+ });
58
+
59
+ it.each([basicUser, basicSingleKindUser, basicMultiKindUser])
60
+ ('returns error if rule has no variation or rollout', async (userToTest) => {
61
+ const rule = { id: 'id', clauses: [matchClause] };
62
+ const flag = makeFlagWithRules([rule]);
63
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), flag, basicUser, eventFactory);
64
+ expect(err).toEqual(Error('Variation/rollout object with no variation or rollout'));
65
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } });
66
+ expect(events).toBeUndefined();
67
+ });
68
+
69
+ it.each([basicUser, basicSingleKindUser, basicMultiKindUser])
70
+ ('returns error if rule has rollout with no variations', async (userToTest) => {
71
+ const rule = { id: 'id', clauses: [matchClause], rollout: { variations: [] } };
72
+ const flag = makeFlagWithRules([rule]);
73
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), flag, userToTest, eventFactory);
74
+ expect(err).toEqual(Error('Variation/rollout object with no variation or rollout'));
75
+ expect(detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } });
76
+ expect(events).toBeUndefined();
77
+ });
78
+
79
+ it.each([basicUser, basicSingleKindUser, basicMultiKindUser])
80
+ ('does not overflow the call stack when evaluating a huge number of rules', async (userToTest) => {
81
+ const ruleCount = 5000;
82
+ const flag = {
83
+ key: 'flag',
84
+ targets: [],
85
+ on: true,
86
+ variations: [false, true],
87
+ fallthrough: { variation: 0 }
88
+ };
89
+ // Note, for this test to be meaningful, the rules must *not* match the user, since we
90
+ // stop evaluating rules on the first match.
91
+ const rules = [];
92
+ for (var i = 0; i < ruleCount; i++) {
93
+ rules.push({ clauses: [noMatchClause], variation: 1 });
94
+ }
95
+ flag.rules = rules;
96
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), flag, userToTest, eventFactory);
97
+ expect(err).toEqual(null);
98
+ expect(detail.value).toEqual(false);
99
+ });
100
+ });
101
+
102
+ describe('Evaluator - rules with non-user kinds', () => {
103
+ const targetKey = 'targetKey';
104
+ const targetContextKind = 'org';
105
+ const matchClause = { attribute: 'key', op: 'in', values: [targetKey], contextKind: targetContextKind }
106
+ const noMatchClause = { attribute: 'key', op: 'in', values: ['not-' + targetKey], contextKind: targetContextKind }
107
+
108
+ const singleKindContext = {
109
+ kind: targetContextKind,
110
+ key: targetKey
111
+ };
112
+ const multiKindContext = {
113
+ kind: 'multi',
114
+ };
115
+ multiKindContext[targetContextKind] = {
116
+ key: targetKey
117
+ };
118
+
119
+ it.each([singleKindContext, multiKindContext])
120
+ ('matches user from rules', async (contextToTest) => {
121
+ const rule0 = { id: 'id0', clauses: [noMatchClause], variation: 1 };
122
+ const rule1 = { id: 'id1', clauses: [matchClause], variation: 2 };
123
+ const flag = makeFlagWithRules([rule0, rule1]);
124
+ const [err, detail, events] = await asyncEvaluate(Evaluator(), flag, contextToTest, eventFactory);
125
+ expect(detail).toMatchObject({
126
+ value: 'c', variationIndex: 2,
127
+ reason: { kind: 'RULE_MATCH', ruleIndex: 1, ruleId: 'id1' }
128
+ });
129
+ expect(events).toBeUndefined();
130
+ });
131
+ });