posthog-node 5.0.0-alpha.1 → 5.0.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 (65) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/lib/edge/index.cjs +3567 -0
  3. package/lib/edge/index.cjs.map +1 -0
  4. package/lib/edge/index.mjs +3541 -0
  5. package/lib/edge/index.mjs.map +1 -0
  6. package/lib/index.d.ts +938 -810
  7. package/lib/{index.cjs.js → node/index.cjs} +3241 -3326
  8. package/lib/node/index.cjs.map +1 -0
  9. package/lib/{index.esm.js → node/index.mjs} +3242 -3324
  10. package/lib/node/index.mjs.map +1 -0
  11. package/package.json +33 -6
  12. package/index.ts +0 -3
  13. package/lib/index.cjs.js.map +0 -1
  14. package/lib/index.esm.js.map +0 -1
  15. package/lib/posthog-core/src/eventemitter.d.ts +0 -8
  16. package/lib/posthog-core/src/featureFlagUtils.d.ts +0 -34
  17. package/lib/posthog-core/src/index.d.ts +0 -242
  18. package/lib/posthog-core/src/lz-string.d.ts +0 -8
  19. package/lib/posthog-core/src/storage-memory.d.ts +0 -6
  20. package/lib/posthog-core/src/types.d.ts +0 -422
  21. package/lib/posthog-core/src/utils.d.ts +0 -19
  22. package/lib/posthog-core/src/vendor/uuidv7.d.ts +0 -179
  23. package/lib/posthog-node/index.d.ts +0 -3
  24. package/lib/posthog-node/src/crypto-helpers.d.ts +0 -3
  25. package/lib/posthog-node/src/crypto.d.ts +0 -2
  26. package/lib/posthog-node/src/error-tracking.d.ts +0 -12
  27. package/lib/posthog-node/src/extensions/error-tracking/autocapture.d.ts +0 -3
  28. package/lib/posthog-node/src/extensions/error-tracking/context-lines.d.ts +0 -6
  29. package/lib/posthog-node/src/extensions/error-tracking/error-conversion.d.ts +0 -2
  30. package/lib/posthog-node/src/extensions/error-tracking/reduceable-cache.d.ts +0 -12
  31. package/lib/posthog-node/src/extensions/error-tracking/stack-trace.d.ts +0 -15
  32. package/lib/posthog-node/src/extensions/error-tracking/type-checking.d.ts +0 -7
  33. package/lib/posthog-node/src/extensions/error-tracking/types.d.ts +0 -57
  34. package/lib/posthog-node/src/extensions/express.d.ts +0 -17
  35. package/lib/posthog-node/src/extensions/sentry-integration.d.ts +0 -51
  36. package/lib/posthog-node/src/feature-flags.d.ts +0 -84
  37. package/lib/posthog-node/src/lazy.d.ts +0 -23
  38. package/lib/posthog-node/src/posthog-node.d.ts +0 -91
  39. package/lib/posthog-node/src/types.d.ts +0 -203
  40. package/lib/posthog-node/test/test-utils.d.ts +0 -17
  41. package/src/crypto-helpers.ts +0 -36
  42. package/src/crypto.ts +0 -22
  43. package/src/error-tracking.ts +0 -67
  44. package/src/extensions/error-tracking/autocapture.ts +0 -65
  45. package/src/extensions/error-tracking/context-lines.ts +0 -425
  46. package/src/extensions/error-tracking/error-conversion.ts +0 -262
  47. package/src/extensions/error-tracking/reduceable-cache.ts +0 -39
  48. package/src/extensions/error-tracking/stack-trace.ts +0 -269
  49. package/src/extensions/error-tracking/type-checking.ts +0 -40
  50. package/src/extensions/error-tracking/types.ts +0 -65
  51. package/src/extensions/express.ts +0 -37
  52. package/src/extensions/sentry-integration.ts +0 -196
  53. package/src/feature-flags.ts +0 -863
  54. package/src/lazy.ts +0 -55
  55. package/src/posthog-node.ts +0 -567
  56. package/src/types.ts +0 -231
  57. package/test/crypto.spec.ts +0 -36
  58. package/test/extensions/error-conversion.spec.ts +0 -44
  59. package/test/extensions/sentry-integration.spec.ts +0 -162
  60. package/test/feature-flags.decide.spec.ts +0 -378
  61. package/test/feature-flags.spec.ts +0 -4681
  62. package/test/lazy.spec.ts +0 -71
  63. package/test/posthog-node.spec.ts +0 -1341
  64. package/test/test-utils.ts +0 -106
  65. package/tsconfig.json +0 -7
@@ -1,4681 +0,0 @@
1
- // import { PostHog, PostHogOptions } from '../'
2
- // Uncomment below line while developing to not compile code everytime
3
- import { PostHog as PostHog, PostHogOptions } from '../src/posthog-node'
4
- import { matchProperty, InconclusiveMatchError, relativeDateParseForFeatureFlagMatching } from '../src/feature-flags'
5
- import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './test-utils'
6
- import { waitForPromises } from 'posthog-core/test/test-utils/test-utils'
7
-
8
- jest.spyOn(console, 'debug').mockImplementation()
9
-
10
- const mockedFetch = jest.spyOn(globalThis, 'fetch').mockImplementation()
11
-
12
- const posthogImmediateResolveOptions: PostHogOptions = {
13
- fetchRetryCount: 0,
14
- }
15
-
16
- describe('local evaluation', () => {
17
- let posthog: PostHog
18
-
19
- jest.useFakeTimers()
20
-
21
- afterEach(async () => {
22
- // ensure clean shutdown & no test interdependencies
23
- await posthog.shutdown()
24
- })
25
-
26
- it('evaluates person properties with undefined property values', async () => {
27
- const flags = {
28
- flags: [
29
- {
30
- id: 1,
31
- name: 'Beta Feature',
32
- key: 'person-flag',
33
- active: true,
34
- filters: {
35
- groups: [
36
- {
37
- variant: null,
38
- properties: [
39
- {
40
- key: 'latestBuildVersion',
41
- type: 'person',
42
- value: '.+',
43
- operator: 'regex',
44
- },
45
- {
46
- key: 'latestBuildVersionMajor',
47
- type: 'person',
48
- value: '23',
49
- operator: 'gt',
50
- },
51
- {
52
- key: 'latestBuildVersionMinor',
53
- type: 'person',
54
- value: '31',
55
- operator: 'gt',
56
- },
57
- {
58
- key: 'latestBuildVersionPatch',
59
- type: 'person',
60
- value: '0',
61
- operator: 'gt',
62
- },
63
- ],
64
- rollout_percentage: 100,
65
- },
66
- ],
67
- },
68
- },
69
- ],
70
- }
71
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
72
-
73
- posthog = new PostHog('TEST_API_KEY', {
74
- host: 'http://example.com',
75
- personalApiKey: 'TEST_PERSONAL_API_KEY',
76
- ...posthogImmediateResolveOptions,
77
- })
78
-
79
- expect(
80
- await posthog.getFeatureFlag('person-flag', 'some-distinct-id', {
81
- personProperties: {
82
- latestBuildVersion: undefined,
83
- latestBuildVersionMajor: undefined,
84
- latestBuildVersionMinor: undefined,
85
- latestBuildVersionPatch: undefined,
86
- } as unknown as Record<string, string>,
87
- })
88
- ).toEqual(false)
89
-
90
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
91
- })
92
-
93
- it('evaluates person properties', async () => {
94
- const flags = {
95
- flags: [
96
- {
97
- id: 1,
98
- name: 'Beta Feature',
99
- key: 'person-flag',
100
- active: true,
101
- filters: {
102
- groups: [
103
- {
104
- properties: [
105
- {
106
- key: 'region',
107
- operator: 'exact',
108
- value: ['USA'],
109
- type: 'person',
110
- },
111
- ],
112
- rollout_percentage: null,
113
- },
114
- ],
115
- },
116
- },
117
- ],
118
- }
119
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
120
-
121
- posthog = new PostHog('TEST_API_KEY', {
122
- host: 'http://example.com',
123
- personalApiKey: 'TEST_PERSONAL_API_KEY',
124
- ...posthogImmediateResolveOptions,
125
- })
126
-
127
- expect(
128
- await posthog.getFeatureFlag('person-flag', 'some-distinct-id', { personProperties: { region: 'USA' } })
129
- ).toEqual(true)
130
- expect(
131
- await posthog.getFeatureFlag('person-flag', 'some-distinct-id', { personProperties: { region: 'Canada' } })
132
- ).toEqual(false)
133
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
134
- })
135
-
136
- it('evaluates group properties', async () => {
137
- const flags = {
138
- flags: [
139
- {
140
- id: 1,
141
- name: 'Beta Feature',
142
- key: 'group-flag',
143
- active: true,
144
- filters: {
145
- aggregation_group_type_index: 0,
146
- groups: [
147
- {
148
- properties: [
149
- {
150
- group_type_index: 0,
151
- key: 'name',
152
- operator: 'exact',
153
- value: ['Project Name 1'],
154
- type: 'group',
155
- },
156
- ],
157
- rollout_percentage: 35,
158
- },
159
- ],
160
- },
161
- },
162
- ],
163
- group_type_mapping: { '0': 'company', '1': 'project' },
164
- }
165
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
166
-
167
- posthog = new PostHog('TEST_API_KEY', {
168
- host: 'http://example.com',
169
- personalApiKey: 'TEST_PERSONAL_API_KEY',
170
- ...posthogImmediateResolveOptions,
171
- })
172
-
173
- // # groups not passed in, hence false
174
- expect(
175
- await posthog.getFeatureFlag('group-flag', 'some-distinct-id', {
176
- groupProperties: { company: { name: 'Project Name 1' } },
177
- })
178
- ).toEqual(false)
179
- expect(
180
- await posthog.getFeatureFlag('group-flag', 'some-distinct-2', {
181
- groupProperties: { company: { name: 'Project Name 2' } },
182
- })
183
- ).toEqual(false)
184
-
185
- // # this is good
186
- expect(
187
- await posthog.getFeatureFlag('group-flag', 'some-distinct-2', {
188
- groups: { company: 'amazon_without_rollout' },
189
- groupProperties: { company: { name: 'Project Name 1' } },
190
- })
191
- ).toEqual(true)
192
-
193
- // # rollout % not met
194
- expect(
195
- await posthog.getFeatureFlag('group-flag', 'some-distinct-2', {
196
- groups: { company: 'amazon' },
197
- groupProperties: { company: { name: 'Project Name 1' } },
198
- })
199
- ).toEqual(false)
200
-
201
- // # property mismatch
202
- expect(
203
- await posthog.getFeatureFlag('group-flag', 'some-distinct-2', {
204
- groups: { company: 'amazon_without_rollout' },
205
- groupProperties: { company: { name: 'Project Name 2' } },
206
- })
207
- ).toEqual(false)
208
-
209
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
210
- // decide not called
211
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
212
- })
213
-
214
- it('evaluates group properties and falls back to decide when group_type_mappings not present', async () => {
215
- const flags = {
216
- flags: [
217
- {
218
- id: 1,
219
- name: 'Beta Feature',
220
- key: 'group-flag',
221
- active: true,
222
- filters: {
223
- aggregation_group_type_index: 0,
224
- groups: [
225
- {
226
- properties: [
227
- {
228
- group_type_index: 0,
229
- key: 'name',
230
- operator: 'exact',
231
- value: ['Project Name 1'],
232
- type: 'group',
233
- },
234
- ],
235
- rollout_percentage: 35,
236
- },
237
- ],
238
- },
239
- },
240
- ],
241
- // "group_type_mapping": {"0": "company", "1": "project"}
242
- }
243
- mockedFetch.mockImplementation(
244
- apiImplementation({ localFlags: flags, decideFlags: { 'group-flag': 'decide-fallback-value' } })
245
- )
246
-
247
- posthog = new PostHog('TEST_API_KEY', {
248
- host: 'http://example.com',
249
- personalApiKey: 'TEST_PERSONAL_API_KEY',
250
- ...posthogImmediateResolveOptions,
251
- })
252
- // # group_type_mappings not present, so fallback to `/flags`
253
- expect(
254
- await posthog.getFeatureFlag('group-flag', 'some-distinct-2', {
255
- groupProperties: {
256
- company: { name: 'Project Name 1' },
257
- },
258
- })
259
- ).toEqual('decide-fallback-value')
260
- })
261
-
262
- it('evaluates flag with complex definition', async () => {
263
- const flags = {
264
- flags: [
265
- {
266
- id: 1,
267
- name: 'Beta Feature',
268
- key: 'complex-flag',
269
- active: true,
270
- filters: {
271
- groups: [
272
- {
273
- properties: [
274
- {
275
- key: 'region',
276
- operator: 'exact',
277
- value: ['USA'],
278
- type: 'person',
279
- },
280
- {
281
- key: 'name',
282
- operator: 'exact',
283
- value: ['Aloha'],
284
- type: 'person',
285
- },
286
- ],
287
- rollout_percentage: undefined,
288
- },
289
- {
290
- properties: [
291
- {
292
- key: 'email',
293
- operator: 'exact',
294
- value: ['a@b.com', 'b@c.com'],
295
- type: 'person',
296
- },
297
- ],
298
- rollout_percentage: 30,
299
- },
300
- {
301
- properties: [
302
- {
303
- key: 'doesnt_matter',
304
- operator: 'exact',
305
- value: ['1', '2'],
306
- type: 'person',
307
- },
308
- ],
309
- rollout_percentage: 0,
310
- },
311
- ],
312
- },
313
- },
314
- ],
315
- }
316
- mockedFetch.mockImplementation(
317
- apiImplementation({ localFlags: flags, decideFlags: { 'complex-flag': 'decide-fallback-value' } })
318
- )
319
-
320
- posthog = new PostHog('TEST_API_KEY', {
321
- host: 'http://example.com',
322
- personalApiKey: 'TEST_PERSONAL_API_KEY',
323
- ...posthogImmediateResolveOptions,
324
- })
325
-
326
- expect(
327
- await posthog.getFeatureFlag('complex-flag', 'some-distinct-id', {
328
- personProperties: { region: 'USA', name: 'Aloha' },
329
- })
330
- ).toEqual(true)
331
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
332
-
333
- // # this distinctIDs hash is < rollout %
334
- expect(
335
- await posthog.getFeatureFlag('complex-flag', 'some-distinct-id_within_rollout?', {
336
- personProperties: { region: 'USA', email: 'a@b.com' },
337
- })
338
- ).toEqual(true)
339
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
340
-
341
- // # will fall back on `/flags`, as all properties present for second group, but that group resolves to false
342
- expect(
343
- await posthog.getFeatureFlag('complex-flag', 'some-distinct-id_outside_rollout?', {
344
- personProperties: { region: 'USA', email: 'a@b.com' },
345
- })
346
- ).toEqual('decide-fallback-value')
347
- expect(mockedFetch).toHaveBeenCalledWith(
348
- 'http://example.com/flags/?v=2',
349
- expect.objectContaining({
350
- body: JSON.stringify({
351
- token: 'TEST_API_KEY',
352
- distinct_id: 'some-distinct-id_outside_rollout?',
353
- groups: {},
354
- person_properties: {
355
- distinct_id: 'some-distinct-id_outside_rollout?',
356
- region: 'USA',
357
- email: 'a@b.com',
358
- },
359
- group_properties: {},
360
- geoip_disable: true,
361
- flag_keys_to_evaluate: ['complex-flag'],
362
- }),
363
- })
364
- )
365
- mockedFetch.mockClear()
366
-
367
- // # same as above
368
- expect(
369
- await posthog.getFeatureFlag('complex-flag', 'some-distinct-id', { personProperties: { doesnt_matter: '1' } })
370
- ).toEqual('decide-fallback-value')
371
- expect(mockedFetch).toHaveBeenCalledWith(
372
- 'http://example.com/flags/?v=2',
373
- expect.objectContaining({
374
- body: JSON.stringify({
375
- token: 'TEST_API_KEY',
376
- distinct_id: 'some-distinct-id',
377
- groups: {},
378
- person_properties: { distinct_id: 'some-distinct-id', doesnt_matter: '1' },
379
- group_properties: {},
380
- geoip_disable: true,
381
- flag_keys_to_evaluate: ['complex-flag'],
382
- }),
383
- })
384
- )
385
- mockedFetch.mockClear()
386
-
387
- expect(
388
- await posthog.getFeatureFlag('complex-flag', 'some-distinct-id', { personProperties: { region: 'USA' } })
389
- ).toEqual('decide-fallback-value')
390
- expect(mockedFetch).toHaveBeenCalledTimes(1) // TODO: Check this
391
- mockedFetch.mockClear()
392
-
393
- // # won't need to fallback when all values are present, and resolves to False
394
- expect(
395
- await posthog.getFeatureFlag('complex-flag', 'some-distinct-id_outside_rollout?', {
396
- personProperties: { region: 'USA', email: 'a@b.com', name: 'X', doesnt_matter: '1' },
397
- })
398
- ).toEqual(false)
399
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
400
- })
401
-
402
- it('falls back to decide', async () => {
403
- const flags = {
404
- flags: [
405
- {
406
- id: 1,
407
- name: 'Beta Feature',
408
- key: 'beta-feature',
409
- active: true,
410
- filters: {
411
- groups: [
412
- {
413
- properties: [{ key: 'id', value: 98, operator: undefined, type: 'cohort' }],
414
- rollout_percentage: 100,
415
- },
416
- ],
417
- },
418
- },
419
- {
420
- id: 2,
421
- name: 'Beta Feature',
422
- key: 'beta-feature2',
423
- active: true,
424
- filters: {
425
- groups: [
426
- {
427
- properties: [
428
- {
429
- key: 'region',
430
- operator: 'exact',
431
- value: ['USA'],
432
- type: 'person',
433
- },
434
- ],
435
- rollout_percentage: 100,
436
- },
437
- ],
438
- },
439
- },
440
- ],
441
- }
442
- mockedFetch.mockImplementation(
443
- apiImplementation({
444
- localFlags: flags,
445
- decideFlags: { 'beta-feature': 'alakazam', 'beta-feature2': 'alakazam2' },
446
- })
447
- )
448
-
449
- posthog = new PostHog('TEST_API_KEY', {
450
- host: 'http://example.com',
451
- personalApiKey: 'TEST_PERSONAL_API_KEY',
452
- ...posthogImmediateResolveOptions,
453
- })
454
-
455
- // # beta-feature fallbacks to decide because property type is unknown
456
- expect(await posthog.getFeatureFlag('beta-feature', 'some-distinct-id')).toEqual('alakazam')
457
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
458
- mockedFetch.mockClear()
459
-
460
- // # beta-feature2 fallbacks to decide because region property not given with call
461
- expect(await posthog.getFeatureFlag('beta-feature2', 'some-distinct-id')).toEqual('alakazam2')
462
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
463
- })
464
-
465
- it('dont fall back to decide when local evaluation is set', async () => {
466
- const flags = {
467
- flags: [
468
- {
469
- id: 1,
470
- name: 'Beta Feature',
471
- key: 'beta-feature',
472
- active: true,
473
- filters: {
474
- groups: [
475
- {
476
- properties: [{ key: 'id', value: 98, operator: undefined, type: 'cohort' }],
477
- rollout_percentage: 100,
478
- },
479
- ],
480
- },
481
- },
482
- {
483
- id: 2,
484
- name: 'Beta Feature',
485
- key: 'beta-feature2',
486
- active: true,
487
- filters: {
488
- groups: [
489
- {
490
- properties: [
491
- {
492
- key: 'region',
493
- operator: 'exact',
494
- value: ['USA'],
495
- type: 'person',
496
- },
497
- ],
498
- rollout_percentage: 100,
499
- },
500
- ],
501
- },
502
- },
503
- ],
504
- }
505
- mockedFetch.mockImplementation(
506
- apiImplementation({
507
- localFlags: flags,
508
- decideFlags: { 'beta-feature': 'alakazam', 'beta-feature2': 'alakazam2' },
509
- })
510
- )
511
-
512
- posthog = new PostHog('TEST_API_KEY', {
513
- host: 'http://example.com',
514
- personalApiKey: 'TEST_PERSONAL_API_KEY',
515
- ...posthogImmediateResolveOptions,
516
- })
517
-
518
- // # beta-feature should fallback to decide because property type is unknown
519
- // # but doesn't because only_evaluate_locally is true
520
- expect(await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', { onlyEvaluateLocally: true })).toEqual(
521
- undefined
522
- )
523
- expect(await posthog.isFeatureEnabled('beta-feature', 'some-distinct-id', { onlyEvaluateLocally: true })).toEqual(
524
- undefined
525
- )
526
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
527
-
528
- // # beta-feature2 should fallback to decide because region property not given with call
529
- // # but doesn't because only_evaluate_locally is true
530
- expect(await posthog.getFeatureFlag('beta-feature2', 'some-distinct-id', { onlyEvaluateLocally: true })).toEqual(
531
- undefined
532
- )
533
- expect(await posthog.isFeatureEnabled('beta-feature2', 'some-distinct-id', { onlyEvaluateLocally: true })).toEqual(
534
- undefined
535
- )
536
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
537
- })
538
-
539
- it("doesn't return undefined when flag is evaluated successfully", async () => {
540
- const flags = {
541
- flags: [
542
- {
543
- id: 1,
544
- name: 'Beta Feature',
545
- key: 'beta-feature',
546
- active: true,
547
- filters: {
548
- groups: [
549
- {
550
- properties: [],
551
- rollout_percentage: 0,
552
- },
553
- ],
554
- },
555
- },
556
- ],
557
- }
558
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags, decideFlags: {} }))
559
-
560
- posthog = new PostHog('TEST_API_KEY', {
561
- host: 'http://example.com',
562
- personalApiKey: 'TEST_PERSONAL_API_KEY',
563
- ...posthogImmediateResolveOptions,
564
- })
565
-
566
- // # beta-feature resolves to False
567
- expect(await posthog.getFeatureFlag('beta-feature', 'some-distinct-id')).toEqual(false)
568
- expect(await posthog.isFeatureEnabled('beta-feature', 'some-distinct-id')).toEqual(false)
569
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
570
-
571
- // # beta-feature2 falls back to decide, and whatever decide returns is the value
572
- expect(await posthog.getFeatureFlag('beta-feature2', 'some-distinct-id')).toEqual(undefined)
573
- expect(await posthog.isFeatureEnabled('beta-feature2', 'some-distinct-id')).toEqual(undefined)
574
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
575
- })
576
-
577
- it('experience continuity flags are not evaluated locally', async () => {
578
- const flags = {
579
- flags: [
580
- {
581
- id: 1,
582
- name: 'Beta Feature',
583
- key: 'beta-feature',
584
- active: true,
585
- ensure_experience_continuity: true,
586
- filters: {
587
- groups: [
588
- {
589
- properties: [],
590
- rollout_percentage: 0,
591
- },
592
- ],
593
- },
594
- },
595
- ],
596
- }
597
- mockedFetch.mockImplementation(
598
- apiImplementation({ localFlags: flags, decideFlags: { 'beta-feature': 'decide-fallback-value' } })
599
- )
600
-
601
- posthog = new PostHog('TEST_API_KEY', {
602
- host: 'http://example.com',
603
- personalApiKey: 'TEST_PERSONAL_API_KEY',
604
- ...posthogImmediateResolveOptions,
605
- })
606
-
607
- // # beta-feature2 falls back to decide, which on error returns default
608
- expect(await posthog.getFeatureFlag('beta-feature', 'some-distinct-id')).toEqual('decide-fallback-value')
609
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
610
- })
611
-
612
- it('get all flags with fallback', async () => {
613
- const flags = {
614
- flags: [
615
- {
616
- id: 1,
617
- name: 'Beta Feature',
618
- key: 'beta-feature',
619
- active: true,
620
- rollout_percentage: 100,
621
- filters: {
622
- groups: [
623
- {
624
- properties: [],
625
- rollout_percentage: 100,
626
- },
627
- ],
628
- },
629
- },
630
- {
631
- id: 2,
632
- name: 'Beta Feature',
633
- key: 'disabled-feature',
634
- active: true,
635
- filters: {
636
- groups: [
637
- {
638
- properties: [],
639
- rollout_percentage: 0,
640
- },
641
- ],
642
- },
643
- },
644
- {
645
- id: 3,
646
- name: 'Beta Feature',
647
- key: 'beta-feature2',
648
- active: true,
649
- filters: {
650
- groups: [
651
- {
652
- properties: [{ key: 'country', value: 'US' }],
653
- rollout_percentage: 0,
654
- },
655
- ],
656
- },
657
- },
658
- ],
659
- }
660
- mockedFetch.mockImplementation(
661
- apiImplementation({
662
- localFlags: flags,
663
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
664
- })
665
- )
666
-
667
- posthog = new PostHog('TEST_API_KEY', {
668
- host: 'http://example.com',
669
- personalApiKey: 'TEST_PERSONAL_API_KEY',
670
- ...posthogImmediateResolveOptions,
671
- })
672
-
673
- // # beta-feature value overridden by /flags
674
- expect(await posthog.getAllFlags('distinct-id')).toEqual({
675
- 'beta-feature': 'variant-1',
676
- 'beta-feature2': 'variant-2',
677
- 'disabled-feature': false,
678
- })
679
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
680
- mockedFetch.mockClear()
681
- })
682
-
683
- it('get all payloads with fallback', async () => {
684
- const flags = {
685
- flags: [
686
- {
687
- id: 1,
688
- name: 'Beta Feature',
689
- key: 'beta-feature',
690
- active: true,
691
- rollout_percentage: 100,
692
- filters: {
693
- groups: [
694
- {
695
- properties: [],
696
- rollout_percentage: 100,
697
- },
698
- ],
699
- payloads: {
700
- true: 'some-payload',
701
- },
702
- },
703
- },
704
- {
705
- id: 2,
706
- name: 'Beta Feature',
707
- key: 'disabled-feature',
708
- active: true,
709
- filters: {
710
- groups: [
711
- {
712
- properties: [],
713
- rollout_percentage: 0,
714
- },
715
- ],
716
- payloads: {
717
- true: 'another-payload',
718
- },
719
- },
720
- },
721
- {
722
- id: 3,
723
- name: 'Beta Feature',
724
- key: 'beta-feature2',
725
- active: true,
726
- filters: {
727
- groups: [
728
- {
729
- properties: [{ key: 'country', value: 'US' }],
730
- rollout_percentage: 0,
731
- },
732
- ],
733
- payloads: {
734
- true: 'payload-3',
735
- },
736
- },
737
- },
738
- ],
739
- }
740
- mockedFetch.mockImplementation(
741
- apiImplementation({
742
- localFlags: flags,
743
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
744
- decideFlagPayloads: { 'beta-feature': 100, 'beta-feature2': 300 },
745
- })
746
- )
747
-
748
- posthog = new PostHog('TEST_API_KEY', {
749
- host: 'http://example.com',
750
- personalApiKey: 'TEST_PERSONAL_API_KEY',
751
- ...posthogImmediateResolveOptions,
752
- })
753
-
754
- // # beta-feature value overridden by /flags
755
- expect((await posthog.getAllFlagsAndPayloads('distinct-id')).featureFlagPayloads).toEqual({
756
- 'beta-feature': 100,
757
- 'beta-feature2': 300,
758
- })
759
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
760
- mockedFetch.mockClear()
761
- })
762
-
763
- it('get all flags with fallback but only_locally_evaluated set', async () => {
764
- const flags = {
765
- flags: [
766
- {
767
- id: 1,
768
- name: 'Beta Feature',
769
- key: 'beta-feature',
770
- active: true,
771
- rollout_percentage: 100,
772
- filters: {
773
- groups: [
774
- {
775
- properties: [],
776
- rollout_percentage: 100,
777
- },
778
- ],
779
- },
780
- },
781
- {
782
- id: 2,
783
- name: 'Beta Feature',
784
- key: 'disabled-feature',
785
- active: true,
786
- filters: {
787
- groups: [
788
- {
789
- properties: [],
790
- rollout_percentage: 0,
791
- },
792
- ],
793
- },
794
- },
795
- {
796
- id: 3,
797
- name: 'Beta Feature',
798
- key: 'beta-feature2',
799
- active: true,
800
- filters: {
801
- groups: [
802
- {
803
- properties: [{ key: 'country', value: 'US' }],
804
- rollout_percentage: 0,
805
- },
806
- ],
807
- },
808
- },
809
- ],
810
- }
811
- mockedFetch.mockImplementation(
812
- apiImplementation({
813
- localFlags: flags,
814
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
815
- })
816
- )
817
-
818
- posthog = new PostHog('TEST_API_KEY', {
819
- host: 'http://example.com',
820
- personalApiKey: 'TEST_PERSONAL_API_KEY',
821
- ...posthogImmediateResolveOptions,
822
- })
823
-
824
- // # beta-feature2 has no value
825
- expect(await posthog.getAllFlags('distinct-id', { onlyEvaluateLocally: true })).toEqual({
826
- 'beta-feature': true,
827
- 'disabled-feature': false,
828
- })
829
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
830
- })
831
-
832
- it('get all payloads with fallback but only_evaluate_locally set', async () => {
833
- const flags = {
834
- flags: [
835
- {
836
- id: 1,
837
- name: 'Beta Feature',
838
- key: 'beta-feature',
839
- active: true,
840
- rollout_percentage: 100,
841
- filters: {
842
- groups: [
843
- {
844
- properties: [],
845
- rollout_percentage: 100,
846
- },
847
- ],
848
- payloads: {
849
- true: 'some-payload',
850
- },
851
- },
852
- },
853
- {
854
- id: 2,
855
- name: 'Beta Feature',
856
- key: 'disabled-feature',
857
- active: true,
858
- filters: {
859
- groups: [
860
- {
861
- properties: [],
862
- rollout_percentage: 0,
863
- },
864
- ],
865
- payloads: {
866
- true: 'another-payload',
867
- },
868
- },
869
- },
870
- {
871
- id: 3,
872
- name: 'Beta Feature',
873
- key: 'beta-feature2',
874
- active: true,
875
- filters: {
876
- groups: [
877
- {
878
- properties: [{ key: 'country', value: 'US' }],
879
- rollout_percentage: 0,
880
- },
881
- ],
882
- payloads: {
883
- true: 'payload-3',
884
- },
885
- },
886
- },
887
- ],
888
- }
889
- mockedFetch.mockImplementation(
890
- apiImplementation({
891
- localFlags: flags,
892
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
893
- decideFlagPayloads: { 'beta-feature': 100, 'beta-feature2': 300 },
894
- })
895
- )
896
-
897
- posthog = new PostHog('TEST_API_KEY', {
898
- host: 'http://example.com',
899
- personalApiKey: 'TEST_PERSONAL_API_KEY',
900
- ...posthogImmediateResolveOptions,
901
- })
902
-
903
- expect(
904
- (await posthog.getAllFlagsAndPayloads('distinct-id', { onlyEvaluateLocally: true })).featureFlagPayloads
905
- ).toEqual({
906
- 'beta-feature': 'some-payload',
907
- })
908
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
909
- })
910
-
911
- it('get all flags with fallback, with no local flags', async () => {
912
- const flags = {
913
- flags: [],
914
- }
915
- mockedFetch.mockImplementation(
916
- apiImplementation({
917
- localFlags: flags,
918
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
919
- })
920
- )
921
-
922
- posthog = new PostHog('TEST_API_KEY', {
923
- host: 'http://example.com',
924
- personalApiKey: 'TEST_PERSONAL_API_KEY',
925
- ...posthogImmediateResolveOptions,
926
- })
927
-
928
- expect(await posthog.getAllFlags('distinct-id')).toEqual({
929
- 'beta-feature': 'variant-1',
930
- 'beta-feature2': 'variant-2',
931
- })
932
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
933
- mockedFetch.mockClear()
934
- })
935
-
936
- it('get all payloads with fallback, with no local payloads', async () => {
937
- const flags = {
938
- flags: [],
939
- }
940
- mockedFetch.mockImplementation(
941
- apiImplementation({
942
- localFlags: flags,
943
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
944
- decideFlagPayloads: { 'beta-feature': 100, 'beta-feature2': 300 },
945
- })
946
- )
947
-
948
- posthog = new PostHog('TEST_API_KEY', {
949
- host: 'http://example.com',
950
- personalApiKey: 'TEST_PERSONAL_API_KEY',
951
- ...posthogImmediateResolveOptions,
952
- })
953
-
954
- expect((await posthog.getAllFlagsAndPayloads('distinct-id')).featureFlagPayloads).toEqual({
955
- 'beta-feature': 100,
956
- 'beta-feature2': 300,
957
- })
958
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
959
- mockedFetch.mockClear()
960
- })
961
-
962
- it('get all flags with no fallback', async () => {
963
- const flags = {
964
- flags: [
965
- {
966
- id: 1,
967
- name: 'Beta Feature',
968
- key: 'beta-feature',
969
- active: true,
970
- rollout_percentage: 100,
971
- filters: {
972
- groups: [
973
- {
974
- properties: [],
975
- rollout_percentage: 100,
976
- },
977
- ],
978
- },
979
- },
980
- {
981
- id: 2,
982
- name: 'Beta Feature',
983
- key: 'disabled-feature',
984
- active: true,
985
- filters: {
986
- groups: [
987
- {
988
- properties: [],
989
- rollout_percentage: 0,
990
- },
991
- ],
992
- },
993
- },
994
- ],
995
- }
996
- mockedFetch.mockImplementation(
997
- apiImplementation({
998
- localFlags: flags,
999
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
1000
- })
1001
- )
1002
-
1003
- posthog = new PostHog('TEST_API_KEY', {
1004
- host: 'http://example.com',
1005
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1006
- ...posthogImmediateResolveOptions,
1007
- })
1008
-
1009
- expect(await posthog.getAllFlags('distinct-id')).toEqual({ 'beta-feature': true, 'disabled-feature': false })
1010
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1011
- })
1012
-
1013
- it('get all payloads with no fallback', async () => {
1014
- const flags = {
1015
- flags: [
1016
- {
1017
- id: 1,
1018
- name: 'Beta Feature',
1019
- key: 'beta-feature',
1020
- active: true,
1021
- rollout_percentage: 100,
1022
- filters: {
1023
- groups: [
1024
- {
1025
- properties: [],
1026
- rollout_percentage: 100,
1027
- },
1028
- ],
1029
- payloads: {
1030
- true: 'new',
1031
- },
1032
- },
1033
- },
1034
- {
1035
- id: 2,
1036
- name: 'Beta Feature',
1037
- key: 'disabled-feature',
1038
- active: true,
1039
- filters: {
1040
- groups: [
1041
- {
1042
- properties: [],
1043
- rollout_percentage: 0,
1044
- },
1045
- ],
1046
- payloads: {
1047
- true: 'some-payload',
1048
- },
1049
- },
1050
- },
1051
- ],
1052
- }
1053
- mockedFetch.mockImplementation(
1054
- apiImplementation({
1055
- localFlags: flags,
1056
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
1057
- })
1058
- )
1059
-
1060
- posthog = new PostHog('TEST_API_KEY', {
1061
- host: 'http://example.com',
1062
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1063
- ...posthogImmediateResolveOptions,
1064
- })
1065
-
1066
- expect((await posthog.getAllFlagsAndPayloads('distinct-id')).featureFlagPayloads).toEqual({ 'beta-feature': 'new' })
1067
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1068
- })
1069
-
1070
- it('computes inactive flags locally as well', async () => {
1071
- const flags = {
1072
- flags: [
1073
- {
1074
- id: 1,
1075
- name: 'Beta Feature',
1076
- key: 'beta-feature',
1077
- active: true,
1078
- rollout_percentage: 100,
1079
- filters: {
1080
- groups: [
1081
- {
1082
- properties: [],
1083
- rollout_percentage: 100,
1084
- },
1085
- ],
1086
- },
1087
- },
1088
- {
1089
- id: 2,
1090
- name: 'Beta Feature',
1091
- key: 'disabled-feature',
1092
- active: true,
1093
- filters: {
1094
- groups: [
1095
- {
1096
- properties: [],
1097
- rollout_percentage: 0,
1098
- },
1099
- ],
1100
- },
1101
- },
1102
- ],
1103
- }
1104
- mockedFetch.mockImplementation(
1105
- apiImplementation({
1106
- localFlags: flags,
1107
- decideFlags: { 'beta-feature': 'variant-1', 'beta-feature2': 'variant-2' },
1108
- })
1109
- )
1110
-
1111
- posthog = new PostHog('TEST_API_KEY', {
1112
- host: 'http://example.com',
1113
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1114
- ...posthogImmediateResolveOptions,
1115
- })
1116
-
1117
- expect(await posthog.getAllFlags('distinct-id')).toEqual({ 'beta-feature': true, 'disabled-feature': false })
1118
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1119
-
1120
- // # Now, after a poll interval, flag 1 is inactive, and flag 2 rollout is set to 100%.
1121
- const flags2 = {
1122
- flags: [
1123
- {
1124
- id: 1,
1125
- name: 'Beta Feature',
1126
- key: 'beta-feature',
1127
- active: false,
1128
- rollout_percentage: 100,
1129
- filters: {
1130
- groups: [
1131
- {
1132
- properties: [],
1133
- rollout_percentage: 100,
1134
- },
1135
- ],
1136
- },
1137
- },
1138
- {
1139
- id: 2,
1140
- name: 'Beta Feature',
1141
- key: 'disabled-feature',
1142
- active: true,
1143
- filters: {
1144
- groups: [
1145
- {
1146
- properties: [],
1147
- rollout_percentage: 100,
1148
- },
1149
- ],
1150
- },
1151
- },
1152
- ],
1153
- }
1154
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags2 }))
1155
-
1156
- // # force reload to simulate poll interval
1157
- await posthog.reloadFeatureFlags()
1158
-
1159
- expect(await posthog.getAllFlags('distinct-id')).toEqual({ 'beta-feature': false, 'disabled-feature': true })
1160
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1161
- })
1162
-
1163
- it('computes complex cohorts locally', async () => {
1164
- const flags = {
1165
- flags: [
1166
- {
1167
- id: 1,
1168
- name: 'Beta Feature',
1169
- key: 'beta-feature',
1170
- active: true,
1171
- rollout_percentage: 100,
1172
- filters: {
1173
- groups: [
1174
- {
1175
- properties: [
1176
- {
1177
- key: 'region',
1178
- operator: 'exact',
1179
- value: ['USA'],
1180
- type: 'person',
1181
- },
1182
- { key: 'id', value: 98, type: 'cohort' },
1183
- ],
1184
- rollout_percentage: 100,
1185
- },
1186
- ],
1187
- },
1188
- },
1189
- ],
1190
- cohorts: {
1191
- '98': {
1192
- type: 'OR',
1193
- values: [
1194
- { key: 'id', value: 1, type: 'cohort' },
1195
- {
1196
- key: 'nation',
1197
- operator: 'exact',
1198
- value: ['UK'],
1199
- type: 'person',
1200
- },
1201
- ],
1202
- },
1203
- '1': {
1204
- type: 'AND',
1205
- values: [{ key: 'other', operator: 'exact', value: ['thing'], type: 'person' }],
1206
- },
1207
- },
1208
- }
1209
- mockedFetch.mockImplementation(
1210
- apiImplementation({
1211
- localFlags: flags,
1212
- decideFlags: {},
1213
- })
1214
- )
1215
-
1216
- posthog = new PostHog('TEST_API_KEY', {
1217
- host: 'http://example.com',
1218
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1219
- ...posthogImmediateResolveOptions,
1220
- })
1221
-
1222
- expect(
1223
- await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', { personProperties: { region: 'UK' } })
1224
- ).toEqual(false)
1225
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1226
-
1227
- // # even though 'other' property is not present, the cohort should still match since it's an OR condition
1228
- expect(
1229
- await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', {
1230
- personProperties: { region: 'USA', nation: 'UK' },
1231
- })
1232
- ).toEqual(true)
1233
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1234
-
1235
- // # even though 'other' property is not present, the cohort should still match since it's an OR condition
1236
- expect(
1237
- await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', {
1238
- personProperties: { region: 'USA', other: 'thing' },
1239
- })
1240
- ).toEqual(true)
1241
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1242
- })
1243
-
1244
- it('computes complex cohorts with negation locally', async () => {
1245
- const flags = {
1246
- flags: [
1247
- {
1248
- id: 1,
1249
- name: 'Beta Feature',
1250
- key: 'beta-feature',
1251
- active: true,
1252
- rollout_percentage: 100,
1253
- filters: {
1254
- groups: [
1255
- {
1256
- properties: [
1257
- {
1258
- key: 'region',
1259
- operator: 'exact',
1260
- value: ['USA'],
1261
- type: 'person',
1262
- },
1263
- { key: 'id', value: 98, type: 'cohort' },
1264
- ],
1265
- rollout_percentage: 100,
1266
- },
1267
- ],
1268
- },
1269
- },
1270
- ],
1271
- cohorts: {
1272
- '98': {
1273
- type: 'OR',
1274
- values: [
1275
- { key: 'id', value: 1, type: 'cohort' },
1276
- {
1277
- key: 'nation',
1278
- operator: 'exact',
1279
- value: ['UK'],
1280
- type: 'person',
1281
- },
1282
- ],
1283
- },
1284
- '1': {
1285
- type: 'AND',
1286
- values: [{ key: 'other', operator: 'exact', value: ['thing'], type: 'person', negation: true }],
1287
- },
1288
- },
1289
- }
1290
- mockedFetch.mockImplementation(
1291
- apiImplementation({
1292
- localFlags: flags,
1293
- decideFlags: {},
1294
- })
1295
- )
1296
-
1297
- posthog = new PostHog('TEST_API_KEY', {
1298
- host: 'http://example.com',
1299
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1300
- ...posthogImmediateResolveOptions,
1301
- })
1302
-
1303
- expect(
1304
- await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', { personProperties: { region: 'UK' } })
1305
- ).toEqual(false)
1306
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1307
-
1308
- // # even though 'other' property is not present, the cohort should still match since it's an OR condition
1309
- expect(
1310
- await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', {
1311
- personProperties: { region: 'USA', nation: 'UK' },
1312
- })
1313
- ).toEqual(true)
1314
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1315
-
1316
- // # since 'other' is negated, we return False. Since 'nation' is not present, we can't tell whether the flag should be true or false, so go to decide
1317
- expect(
1318
- await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', {
1319
- personProperties: { region: 'USA', other: 'thing' },
1320
- })
1321
- ).toEqual(undefined)
1322
- expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
1323
-
1324
- mockedFetch.mockClear()
1325
-
1326
- expect(
1327
- await posthog.getFeatureFlag('beta-feature', 'some-distinct-id', {
1328
- personProperties: { region: 'USA', other: 'thing2' },
1329
- })
1330
- ).toEqual(true)
1331
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1332
- })
1333
-
1334
- it('gets feature flag with variant overrides', async () => {
1335
- const flags = {
1336
- flags: [
1337
- {
1338
- id: 1,
1339
- name: 'Beta Feature',
1340
- key: 'beta-feature',
1341
- active: true,
1342
- filters: {
1343
- groups: [
1344
- {
1345
- properties: [
1346
- {
1347
- key: 'email',
1348
- operator: 'exact',
1349
- value: 'test@posthog.com',
1350
- type: 'person',
1351
- },
1352
- ],
1353
- rollout_percentage: 100,
1354
- variant: 'second-variant',
1355
- },
1356
- {
1357
- rollout_percentage: 50,
1358
- variant: 'first-variant',
1359
- },
1360
- ],
1361
- multivariate: {
1362
- variants: [
1363
- {
1364
- key: 'first-variant',
1365
- name: 'First Variant',
1366
- rollout_percentage: 50,
1367
- },
1368
- {
1369
- key: 'second-variant',
1370
- name: 'Second Variant',
1371
- rollout_percentage: 25,
1372
- },
1373
- {
1374
- key: 'third-variant',
1375
- name: 'Third Variant',
1376
- rollout_percentage: 25,
1377
- },
1378
- ],
1379
- },
1380
- },
1381
- },
1382
- ],
1383
- }
1384
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1385
-
1386
- posthog = new PostHog('TEST_API_KEY', {
1387
- host: 'http://example.com',
1388
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1389
- ...posthogImmediateResolveOptions,
1390
- })
1391
-
1392
- expect(
1393
- await posthog.getFeatureFlag('beta-feature', 'test_id', { personProperties: { email: 'test@posthog.com' } })
1394
- ).toEqual('second-variant')
1395
- expect(await posthog.getFeatureFlag('beta-feature', 'example_id')).toEqual('first-variant')
1396
-
1397
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1398
- // decide not called
1399
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1400
- })
1401
-
1402
- it('gets feature flag with clashing variant overrides', async () => {
1403
- const flags = {
1404
- flags: [
1405
- {
1406
- id: 1,
1407
- name: 'Beta Feature',
1408
- key: 'beta-feature',
1409
- active: true,
1410
- filters: {
1411
- groups: [
1412
- {
1413
- properties: [
1414
- {
1415
- key: 'email',
1416
- operator: 'exact',
1417
- value: 'test@posthog.com',
1418
- type: 'person',
1419
- },
1420
- ],
1421
- rollout_percentage: 100,
1422
- variant: 'second-variant',
1423
- },
1424
- // # since second-variant comes first in the list, it will be the one that gets picked
1425
- {
1426
- properties: [
1427
- {
1428
- key: 'email',
1429
- operator: 'exact',
1430
- value: 'test@posthog.com',
1431
- type: 'person',
1432
- },
1433
- ],
1434
- rollout_percentage: 100,
1435
- variant: 'first-variant',
1436
- },
1437
- {
1438
- rollout_percentage: 50,
1439
- variant: 'first-variant',
1440
- },
1441
- ],
1442
- multivariate: {
1443
- variants: [
1444
- {
1445
- key: 'first-variant',
1446
- name: 'First Variant',
1447
- rollout_percentage: 50,
1448
- },
1449
- {
1450
- key: 'second-variant',
1451
- name: 'Second Variant',
1452
- rollout_percentage: 25,
1453
- },
1454
- {
1455
- key: 'third-variant',
1456
- name: 'Third Variant',
1457
- rollout_percentage: 25,
1458
- },
1459
- ],
1460
- },
1461
- },
1462
- },
1463
- ],
1464
- }
1465
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1466
-
1467
- posthog = new PostHog('TEST_API_KEY', {
1468
- host: 'http://example.com',
1469
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1470
- ...posthogImmediateResolveOptions,
1471
- })
1472
-
1473
- expect(
1474
- await posthog.getFeatureFlag('beta-feature', 'test_id', { personProperties: { email: 'test@posthog.com' } })
1475
- ).toEqual('second-variant')
1476
- expect(
1477
- await posthog.getFeatureFlag('beta-feature', 'example_id', { personProperties: { email: 'test@posthog.com' } })
1478
- ).toEqual('second-variant')
1479
- expect(await posthog.getFeatureFlag('beta-feature', 'example_id')).toEqual('first-variant')
1480
-
1481
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1482
- // decide not called
1483
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1484
- })
1485
-
1486
- it('gets feature flag with invalid variant overrides', async () => {
1487
- const flags = {
1488
- flags: [
1489
- {
1490
- id: 1,
1491
- name: 'Beta Feature',
1492
- key: 'beta-feature',
1493
- active: true,
1494
- filters: {
1495
- groups: [
1496
- {
1497
- properties: [
1498
- {
1499
- key: 'email',
1500
- operator: 'exact',
1501
- value: 'test@posthog.com',
1502
- type: 'person',
1503
- },
1504
- ],
1505
- rollout_percentage: 100,
1506
- variant: 'second???',
1507
- },
1508
- {
1509
- rollout_percentage: 50,
1510
- variant: 'first???',
1511
- },
1512
- ],
1513
- multivariate: {
1514
- variants: [
1515
- {
1516
- key: 'first-variant',
1517
- name: 'First Variant',
1518
- rollout_percentage: 50,
1519
- },
1520
- {
1521
- key: 'second-variant',
1522
- name: 'Second Variant',
1523
- rollout_percentage: 25,
1524
- },
1525
- {
1526
- key: 'third-variant',
1527
- name: 'Third Variant',
1528
- rollout_percentage: 25,
1529
- },
1530
- ],
1531
- },
1532
- },
1533
- },
1534
- ],
1535
- }
1536
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1537
-
1538
- posthog = new PostHog('TEST_API_KEY', {
1539
- host: 'http://example.com',
1540
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1541
- ...posthogImmediateResolveOptions,
1542
- })
1543
-
1544
- expect(
1545
- await posthog.getFeatureFlag('beta-feature', 'test_id', { personProperties: { email: 'test@posthog.com' } })
1546
- ).toEqual('third-variant')
1547
- expect(await posthog.getFeatureFlag('beta-feature', 'example_id')).toEqual('second-variant')
1548
-
1549
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1550
- // decide not called
1551
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1552
- })
1553
-
1554
- it('gets feature flag with multiple variant overrides', async () => {
1555
- const flags = {
1556
- flags: [
1557
- {
1558
- id: 1,
1559
- name: 'Beta Feature',
1560
- key: 'beta-feature',
1561
- active: true,
1562
- filters: {
1563
- groups: [
1564
- {
1565
- rollout_percentage: 100,
1566
- // # The override applies even if the first condition matches all and gives everyone their default group
1567
- },
1568
- {
1569
- properties: [
1570
- {
1571
- key: 'email',
1572
- operator: 'exact',
1573
- value: 'test@posthog.com',
1574
- type: 'person',
1575
- },
1576
- ],
1577
- rollout_percentage: 100,
1578
- variant: 'second-variant',
1579
- },
1580
- {
1581
- rollout_percentage: 50,
1582
- variant: 'third-variant',
1583
- },
1584
- ],
1585
- multivariate: {
1586
- variants: [
1587
- {
1588
- key: 'first-variant',
1589
- name: 'First Variant',
1590
- rollout_percentage: 50,
1591
- },
1592
- {
1593
- key: 'second-variant',
1594
- name: 'Second Variant',
1595
- rollout_percentage: 25,
1596
- },
1597
- {
1598
- key: 'third-variant',
1599
- name: 'Third Variant',
1600
- rollout_percentage: 25,
1601
- },
1602
- ],
1603
- },
1604
- },
1605
- },
1606
- ],
1607
- }
1608
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1609
-
1610
- posthog = new PostHog('TEST_API_KEY', {
1611
- host: 'http://example.com',
1612
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1613
- ...posthogImmediateResolveOptions,
1614
- })
1615
-
1616
- expect(
1617
- await posthog.getFeatureFlag('beta-feature', 'test_id', { personProperties: { email: 'test@posthog.com' } })
1618
- ).toEqual('second-variant')
1619
- expect(await posthog.getFeatureFlag('beta-feature', 'example_id')).toEqual('third-variant')
1620
- expect(await posthog.getFeatureFlag('beta-feature', 'another_id')).toEqual('second-variant')
1621
-
1622
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1623
- // decide not called
1624
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1625
- })
1626
-
1627
- it('get feature flag payload based on boolean flag', async () => {
1628
- const flags = {
1629
- flags: [
1630
- {
1631
- id: 1,
1632
- name: 'Beta Feature',
1633
- key: 'person-flag',
1634
- active: true,
1635
- filters: {
1636
- groups: [
1637
- {
1638
- properties: [
1639
- {
1640
- key: 'region',
1641
- operator: 'exact',
1642
- value: ['USA'],
1643
- type: 'person',
1644
- },
1645
- ],
1646
- rollout_percentage: null,
1647
- },
1648
- ],
1649
- payloads: {
1650
- true: {
1651
- log: 'all',
1652
- },
1653
- },
1654
- },
1655
- },
1656
- ],
1657
- }
1658
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1659
-
1660
- posthog = new PostHog('TEST_API_KEY', {
1661
- host: 'http://example.com',
1662
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1663
- ...posthogImmediateResolveOptions,
1664
- })
1665
-
1666
- expect(
1667
- await posthog.getFeatureFlagPayload('person-flag', 'some-distinct-id', true, {
1668
- personProperties: { region: 'USA' },
1669
- })
1670
- ).toEqual({
1671
- log: 'all',
1672
- })
1673
- expect(
1674
- await posthog.getFeatureFlagPayload('person-flag', 'some-distinct-id', undefined, {
1675
- personProperties: { region: 'USA' },
1676
- })
1677
- ).toEqual({
1678
- log: 'all',
1679
- })
1680
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1681
- // decide not called
1682
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1683
- })
1684
-
1685
- it('get feature flag payload on a multivariate', async () => {
1686
- const flags = {
1687
- flags: [
1688
- {
1689
- id: 1,
1690
- name: 'Beta Feature',
1691
- key: 'beta-feature',
1692
- active: true,
1693
- filters: {
1694
- groups: [
1695
- {
1696
- properties: [
1697
- {
1698
- key: 'email',
1699
- operator: 'exact',
1700
- value: 'test@posthog.com',
1701
- type: 'person',
1702
- },
1703
- ],
1704
- rollout_percentage: 100,
1705
- variant: 'second-variant',
1706
- },
1707
- {
1708
- rollout_percentage: 50,
1709
- variant: 'first-variant',
1710
- },
1711
- ],
1712
- multivariate: {
1713
- variants: [
1714
- {
1715
- key: 'first-variant',
1716
- name: 'First Variant',
1717
- rollout_percentage: 50,
1718
- },
1719
- {
1720
- key: 'second-variant',
1721
- name: 'Second Variant',
1722
- rollout_percentage: 25,
1723
- },
1724
- {
1725
- key: 'third-variant',
1726
- name: 'Third Variant',
1727
- rollout_percentage: 25,
1728
- },
1729
- ],
1730
- },
1731
- payloads: {
1732
- 'second-variant': 2500,
1733
- },
1734
- },
1735
- },
1736
- ],
1737
- }
1738
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1739
-
1740
- posthog = new PostHog('TEST_API_KEY', {
1741
- host: 'http://example.com',
1742
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1743
- ...posthogImmediateResolveOptions,
1744
- })
1745
-
1746
- expect(
1747
- await posthog.getFeatureFlagPayload('beta-feature', 'test_id', 'second-variant', {
1748
- personProperties: { email: 'test@posthog.com' },
1749
- })
1750
- ).toEqual(2500)
1751
-
1752
- expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
1753
- // decide not called
1754
- expect(mockedFetch).not.toHaveBeenCalledWith(...anyDecideCall)
1755
- })
1756
-
1757
- describe('isLocalEvaluationReady', () => {
1758
- it('returns false when featureFlagsPoller is undefined', () => {
1759
- posthog = new PostHog('TEST_API_KEY', {
1760
- host: 'http://example.com',
1761
- ...posthogImmediateResolveOptions,
1762
- })
1763
- expect(posthog.isLocalEvaluationReady()).toBe(false)
1764
- })
1765
-
1766
- it('returns false when featureFlagsPoller has not loaded successfully', () => {
1767
- posthog = new PostHog('TEST_API_KEY', {
1768
- host: 'http://example.com',
1769
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1770
- ...posthogImmediateResolveOptions,
1771
- })
1772
- expect(posthog.isLocalEvaluationReady()).toBe(false)
1773
- })
1774
-
1775
- it('returns false when featureFlagsPoller has no flags', async () => {
1776
- const flags = { flags: [] }
1777
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1778
- posthog = new PostHog('TEST_API_KEY', {
1779
- host: 'http://example.com',
1780
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1781
- ...posthogImmediateResolveOptions,
1782
- })
1783
- await posthog.reloadFeatureFlags()
1784
- expect(posthog.isLocalEvaluationReady()).toBe(false)
1785
- })
1786
-
1787
- it('returns true when featureFlagsPoller has loaded flags successfully', async () => {
1788
- const flags = {
1789
- flags: [
1790
- {
1791
- id: 1,
1792
- name: 'Beta Feature',
1793
- key: 'beta-feature',
1794
- active: true,
1795
- filters: {
1796
- groups: [
1797
- {
1798
- properties: [],
1799
- rollout_percentage: 100,
1800
- },
1801
- ],
1802
- },
1803
- },
1804
- ],
1805
- }
1806
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1807
- posthog = new PostHog('TEST_API_KEY', {
1808
- host: 'http://example.com',
1809
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1810
- ...posthogImmediateResolveOptions,
1811
- })
1812
- await posthog.reloadFeatureFlags()
1813
- expect(posthog.isLocalEvaluationReady()).toBe(true)
1814
- })
1815
- })
1816
-
1817
- describe('waitForLocalEvaluationReady', () => {
1818
- it('returns true when local evaluation is ready', async () => {
1819
- const flags = {
1820
- flags: [
1821
- {
1822
- id: 1,
1823
- name: 'Beta Feature',
1824
- key: 'beta-feature',
1825
- active: true,
1826
- filters: {
1827
- groups: [{ properties: [], rollout_percentage: 100 }],
1828
- },
1829
- },
1830
- ],
1831
- }
1832
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1833
- posthog = new PostHog('TEST_API_KEY', {
1834
- host: 'http://example.com',
1835
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1836
- ...posthogImmediateResolveOptions,
1837
- })
1838
-
1839
- expect(await posthog.waitForLocalEvaluationReady()).toBe(true)
1840
- })
1841
-
1842
- it('returns false when local evaluation endpoint returns empty flags', async () => {
1843
- const flags = { flags: [] }
1844
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1845
- posthog = new PostHog('TEST_API_KEY', {
1846
- host: 'http://example.com',
1847
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1848
- ...posthogImmediateResolveOptions,
1849
- })
1850
- expect(await posthog.waitForLocalEvaluationReady()).toBe(false)
1851
- })
1852
-
1853
- it('returns false when local evaluation is not enabled', async () => {
1854
- const flags = { flags: [] }
1855
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1856
- posthog = new PostHog('TEST_API_KEY', {
1857
- host: 'http://example.com',
1858
- personalApiKey: undefined,
1859
- ...posthogImmediateResolveOptions,
1860
- })
1861
- expect(await posthog.waitForLocalEvaluationReady()).toBe(false)
1862
- })
1863
- })
1864
-
1865
- it('emits localEvaluationFlagsLoaded event when flags are loaded', async () => {
1866
- const flags = {
1867
- flags: [
1868
- {
1869
- id: 1,
1870
- name: 'Beta Feature',
1871
- key: 'beta-feature',
1872
- active: true,
1873
- filters: {
1874
- groups: [
1875
- {
1876
- properties: [],
1877
- rollout_percentage: 100,
1878
- },
1879
- ],
1880
- },
1881
- },
1882
- {
1883
- id: 2,
1884
- name: 'Alpha Feature',
1885
- key: 'alpha-feature',
1886
- active: true,
1887
- filters: {
1888
- groups: [
1889
- {
1890
- properties: [],
1891
- rollout_percentage: 50,
1892
- },
1893
- ],
1894
- },
1895
- },
1896
- ],
1897
- }
1898
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1899
-
1900
- posthog = new PostHog('TEST_API_KEY', {
1901
- host: 'http://example.com',
1902
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1903
- ...posthogImmediateResolveOptions,
1904
- })
1905
-
1906
- const eventHandler = jest.fn()
1907
- posthog.on('localEvaluationFlagsLoaded', eventHandler)
1908
-
1909
- // Wait for initial load
1910
- await waitForPromises()
1911
-
1912
- expect(eventHandler).toHaveBeenCalledWith(2) // Should be called with number of flags loaded
1913
- })
1914
-
1915
- it('does not emit localEvaluationFlagsLoaded event when loading fails', async () => {
1916
- mockedFetch.mockImplementation(() => {
1917
- throw new Error('Failed to load flags')
1918
- })
1919
-
1920
- posthog = new PostHog('TEST_API_KEY', {
1921
- host: 'http://example.com',
1922
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1923
- ...posthogImmediateResolveOptions,
1924
- })
1925
-
1926
- const eventHandler = jest.fn()
1927
- posthog.on('localEvaluationFlagsLoaded', eventHandler)
1928
-
1929
- // Wait for initial load
1930
- await waitForPromises()
1931
-
1932
- expect(eventHandler).not.toHaveBeenCalled()
1933
- })
1934
-
1935
- it('emits localEvaluationFlagsLoaded event on reload', async () => {
1936
- const flags = {
1937
- flags: [
1938
- {
1939
- id: 1,
1940
- name: 'Beta Feature',
1941
- key: 'beta-feature',
1942
- active: true,
1943
- filters: {
1944
- groups: [
1945
- {
1946
- properties: [],
1947
- rollout_percentage: 100,
1948
- },
1949
- ],
1950
- },
1951
- },
1952
- ],
1953
- }
1954
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1955
-
1956
- posthog = new PostHog('TEST_API_KEY', {
1957
- host: 'http://example.com',
1958
- personalApiKey: 'TEST_PERSONAL_API_KEY',
1959
- ...posthogImmediateResolveOptions,
1960
- })
1961
-
1962
- const eventHandler = jest.fn()
1963
- posthog.on('localEvaluationFlagsLoaded', eventHandler)
1964
-
1965
- // Wait for initial load
1966
- await waitForPromises()
1967
- eventHandler.mockClear() // Clear initial call
1968
-
1969
- // Reload flags
1970
- await posthog.reloadFeatureFlags()
1971
-
1972
- expect(eventHandler).toHaveBeenCalledWith(1) // Should be called with number of flags loaded
1973
- })
1974
- })
1975
-
1976
- describe('getFeatureFlag', () => {
1977
- it('should capture $feature_flag_called when called, but not add all cached flags', async () => {
1978
- const flags = {
1979
- flags: [
1980
- {
1981
- id: 1,
1982
- name: 'Beta Feature',
1983
- key: 'complex-flag',
1984
- active: true,
1985
- filters: {
1986
- groups: [
1987
- {
1988
- variant: null,
1989
- properties: [{ key: 'region', type: 'person', value: 'USA', operator: 'exact' }],
1990
- rollout_percentage: 100,
1991
- },
1992
- ],
1993
- },
1994
- },
1995
- {
1996
- id: 2,
1997
- name: 'Gamma Feature',
1998
- key: 'simple-flag',
1999
- active: true,
2000
- filters: {
2001
- groups: [
2002
- {
2003
- variant: null,
2004
- properties: [],
2005
- rollout_percentage: 100,
2006
- },
2007
- ],
2008
- },
2009
- },
2010
- ],
2011
- }
2012
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
2013
- const posthog = new PostHog('TEST_API_KEY', {
2014
- host: 'http://example.com',
2015
- personalApiKey: 'TEST_PERSONAL_API_KEY',
2016
- ...posthogImmediateResolveOptions,
2017
- })
2018
- let capturedMessage: any
2019
- posthog.on('capture', (message) => {
2020
- capturedMessage = message
2021
- })
2022
-
2023
- expect(
2024
- await posthog.getFeatureFlag('complex-flag', 'some-distinct-id', {
2025
- personProperties: {
2026
- region: 'USA',
2027
- } as unknown as Record<string, string>,
2028
- })
2029
- ).toEqual(true)
2030
-
2031
- await waitForPromises()
2032
-
2033
- expect(capturedMessage).toMatchObject({
2034
- distinct_id: 'some-distinct-id',
2035
- event: '$feature_flag_called',
2036
- library: posthog.getLibraryId(),
2037
- library_version: posthog.getLibraryVersion(),
2038
- properties: {
2039
- '$feature/complex-flag': true,
2040
- $feature_flag: 'complex-flag',
2041
- $feature_flag_response: true,
2042
- $groups: undefined,
2043
- $lib: posthog.getLibraryId(),
2044
- $lib_version: posthog.getLibraryVersion(),
2045
- locally_evaluated: true,
2046
- },
2047
- })
2048
-
2049
- expect(capturedMessage.properties).not.toHaveProperty('$active_feature_flags')
2050
- expect(capturedMessage.properties).not.toHaveProperty('$feature/simple-flag')
2051
- })
2052
- })
2053
-
2054
- describe('match properties', () => {
2055
- jest.useFakeTimers()
2056
-
2057
- it('with operator exact', () => {
2058
- const property_a = { key: 'key', value: 'value' }
2059
-
2060
- expect(matchProperty(property_a, { key: 'value' })).toBe(true)
2061
-
2062
- expect(matchProperty(property_a, { key: 'value2' })).toBe(false)
2063
- expect(matchProperty(property_a, { key: '' })).toBe(false)
2064
- expect(matchProperty(property_a, { key: undefined })).toBe(false)
2065
-
2066
- expect(() => matchProperty(property_a, { key2: 'value' })).toThrow(InconclusiveMatchError)
2067
- expect(() => matchProperty(property_a, {})).toThrow(InconclusiveMatchError)
2068
-
2069
- const property_b = { key: 'key', value: 'value', operator: 'exact' }
2070
-
2071
- expect(matchProperty(property_b, { key: 'value' })).toBe(true)
2072
- expect(matchProperty(property_b, { key: 'value2' })).toBe(false)
2073
-
2074
- const property_c = { key: 'key', value: ['value1', 'value2', 'value3'], operator: 'exact' }
2075
- expect(matchProperty(property_c, { key: 'value1' })).toBe(true)
2076
- expect(matchProperty(property_c, { key: 'value2' })).toBe(true)
2077
- expect(matchProperty(property_c, { key: 'value3' })).toBe(true)
2078
-
2079
- expect(matchProperty(property_c, { key: 'value4' })).toBe(false)
2080
-
2081
- expect(() => matchProperty(property_c, { key2: 'value' })).toThrow(InconclusiveMatchError)
2082
- })
2083
-
2084
- it('with operator is_not', () => {
2085
- const property_a = { key: 'key', value: 'value', operator: 'is_not' }
2086
-
2087
- expect(matchProperty(property_a, { key: 'value' })).toBe(false)
2088
- expect(matchProperty(property_a, { key: 'value2' })).toBe(true)
2089
- expect(matchProperty(property_a, { key: '' })).toBe(true)
2090
- expect(matchProperty(property_a, { key: undefined })).toBe(true)
2091
-
2092
- expect(() => matchProperty(property_a, { key2: 'value' })).toThrow(InconclusiveMatchError)
2093
- expect(() => matchProperty(property_a, {})).toThrow(InconclusiveMatchError)
2094
-
2095
- const property_c = { key: 'key', value: ['value1', 'value2', 'value3'], operator: 'is_not' }
2096
- expect(matchProperty(property_c, { key: 'value1' })).toBe(false)
2097
- expect(matchProperty(property_c, { key: 'value2' })).toBe(false)
2098
- expect(matchProperty(property_c, { key: 'value3' })).toBe(false)
2099
-
2100
- expect(matchProperty(property_c, { key: 'value4' })).toBe(true)
2101
- expect(matchProperty(property_c, { key: 'value5' })).toBe(true)
2102
- expect(matchProperty(property_c, { key: '' })).toBe(true)
2103
- expect(matchProperty(property_c, { key: undefined })).toBe(true)
2104
-
2105
- expect(() => matchProperty(property_c, { key2: 'value' })).toThrow(InconclusiveMatchError)
2106
- })
2107
-
2108
- it('with operator is_set', () => {
2109
- const property_a = { key: 'key', value: 'is_set', operator: 'is_set' }
2110
-
2111
- expect(matchProperty(property_a, { key: 'value' })).toBe(true)
2112
- expect(matchProperty(property_a, { key: 'value2' })).toBe(true)
2113
- expect(matchProperty(property_a, { key: '' })).toBe(true)
2114
- expect(matchProperty(property_a, { key: undefined })).toBe(false)
2115
-
2116
- expect(() => matchProperty(property_a, { key2: 'value' })).toThrow(InconclusiveMatchError)
2117
- expect(() => matchProperty(property_a, {})).toThrow(InconclusiveMatchError)
2118
- })
2119
-
2120
- it('with operator icontains', () => {
2121
- const property_a = { key: 'key', value: 'vaLuE', operator: 'icontains' }
2122
-
2123
- expect(matchProperty(property_a, { key: 'value' })).toBe(true)
2124
- expect(matchProperty(property_a, { key: 'value2' })).toBe(true)
2125
- expect(matchProperty(property_a, { key: 'vaLue3' })).toBe(true)
2126
- expect(matchProperty(property_a, { key: '343tfvalUe5' })).toBe(true)
2127
-
2128
- expect(matchProperty(property_a, { key: '' })).toBe(false)
2129
- expect(matchProperty(property_a, { key: undefined })).toBe(false)
2130
- expect(matchProperty(property_a, { key: 1234 })).toBe(false)
2131
- expect(matchProperty(property_a, { key: '1234' })).toBe(false)
2132
-
2133
- expect(() => matchProperty(property_a, { key2: 'value' })).toThrow(InconclusiveMatchError)
2134
- expect(() => matchProperty(property_a, {})).toThrow(InconclusiveMatchError)
2135
-
2136
- const property_b = { key: 'key', value: '3', operator: 'icontains' }
2137
-
2138
- expect(matchProperty(property_b, { key: '3' })).toBe(true)
2139
- expect(matchProperty(property_b, { key: 323 })).toBe(true)
2140
- expect(matchProperty(property_b, { key: 'val3' })).toBe(true)
2141
-
2142
- expect(matchProperty(property_b, { key: 'three' })).toBe(false)
2143
- })
2144
-
2145
- it('with operator regex', () => {
2146
- const property_a = { key: 'key', value: '\\.com$', operator: 'regex' }
2147
-
2148
- expect(matchProperty(property_a, { key: 'value.com' })).toBe(true)
2149
- expect(matchProperty(property_a, { key: 'value2.com' })).toBe(true)
2150
-
2151
- expect(matchProperty(property_a, { key: 'valuecom' })).toBe(false)
2152
- expect(matchProperty(property_a, { key: 'valuecom' })).toBe(false)
2153
- expect(matchProperty(property_a, { key: '.com343tfvalue5' })).toBe(false)
2154
- expect(matchProperty(property_a, { key: undefined })).toBe(false)
2155
- expect(matchProperty(property_a, { key: '' })).toBe(false)
2156
-
2157
- expect(() => matchProperty(property_a, { key2: 'value' })).toThrow(InconclusiveMatchError)
2158
- expect(() => matchProperty(property_a, {})).toThrow(InconclusiveMatchError)
2159
-
2160
- const property_b = { key: 'key', value: '3', operator: 'regex' }
2161
-
2162
- expect(matchProperty(property_b, { key: '3' })).toBe(true)
2163
- expect(matchProperty(property_b, { key: 323 })).toBe(true)
2164
- expect(matchProperty(property_b, { key: 'val3' })).toBe(true)
2165
-
2166
- expect(matchProperty(property_b, { key: 'three' })).toBe(false)
2167
-
2168
- // # invalid regex
2169
- const property_c = { key: 'key', value: '?*', operator: 'regex' }
2170
- expect(matchProperty(property_c, { key: 'value.com' })).toBe(false)
2171
- expect(matchProperty(property_c, { key: 'value2' })).toBe(false)
2172
-
2173
- // # non string value
2174
- const property_d = { key: 'key', value: 4, operator: 'regex' }
2175
- expect(matchProperty(property_d, { key: '4' })).toBe(true)
2176
- expect(matchProperty(property_d, { key: 4 })).toBe(true)
2177
-
2178
- expect(matchProperty(property_d, { key: 'value' })).toBe(false)
2179
-
2180
- // # non string value - not_regex
2181
- const property_e = { key: 'key', value: 4, operator: 'not_regex' }
2182
- expect(matchProperty(property_e, { key: '4' })).toBe(false)
2183
- expect(matchProperty(property_e, { key: 4 })).toBe(false)
2184
-
2185
- expect(matchProperty(property_e, { key: 'value' })).toBe(true)
2186
- })
2187
-
2188
- it('with math operators', () => {
2189
- const property_a = { key: 'key', value: 1, operator: 'gt' }
2190
-
2191
- expect(matchProperty(property_a, { key: 2 })).toBe(true)
2192
- expect(matchProperty(property_a, { key: 3 })).toBe(true)
2193
-
2194
- expect(matchProperty(property_a, { key: 0 })).toBe(false)
2195
- expect(matchProperty(property_a, { key: -1 })).toBe(false)
2196
- // # now we handle type mismatches so this should be true
2197
- expect(matchProperty(property_a, { key: '23' })).toBe(true)
2198
-
2199
- const property_b = { key: 'key', value: 1, operator: 'lt' }
2200
- expect(matchProperty(property_b, { key: 0 })).toBe(true)
2201
- expect(matchProperty(property_b, { key: -1 })).toBe(true)
2202
- expect(matchProperty(property_b, { key: -3 })).toBe(true)
2203
-
2204
- expect(matchProperty(property_b, { key: '3' })).toBe(false)
2205
- expect(matchProperty(property_b, { key: '1' })).toBe(false)
2206
- expect(matchProperty(property_b, { key: 1 })).toBe(false)
2207
-
2208
- const property_c = { key: 'key', value: 1, operator: 'gte' }
2209
- expect(matchProperty(property_c, { key: 2 })).toBe(true)
2210
- expect(matchProperty(property_c, { key: 1 })).toBe(true)
2211
-
2212
- expect(matchProperty(property_c, { key: 0 })).toBe(false)
2213
- expect(matchProperty(property_c, { key: -1 })).toBe(false)
2214
- expect(matchProperty(property_c, { key: -3 })).toBe(false)
2215
- // # now we handle type mismatches so this should be true
2216
- expect(matchProperty(property_c, { key: '3' })).toBe(true)
2217
-
2218
- const property_d = { key: 'key', value: '43', operator: 'lte' }
2219
- expect(matchProperty(property_d, { key: '43' })).toBe(true)
2220
- expect(matchProperty(property_d, { key: '42' })).toBe(true)
2221
-
2222
- expect(matchProperty(property_d, { key: '44' })).toBe(false)
2223
- expect(matchProperty(property_d, { key: 44 })).toBe(false)
2224
- expect(matchProperty(property_d, { key: 42 })).toBe(true)
2225
-
2226
- const property_e = { key: 'key', value: '30', operator: 'lt' }
2227
- expect(matchProperty(property_e, { key: '29' })).toBe(true)
2228
-
2229
- // # depending on the type of override, we adjust type comparison
2230
- expect(matchProperty(property_e, { key: '100' })).toBe(true)
2231
- expect(matchProperty(property_e, { key: 100 })).toBe(false)
2232
-
2233
- const property_f = { key: 'key', value: '123aloha', operator: 'gt' }
2234
- expect(matchProperty(property_f, { key: '123' })).toBe(false)
2235
- expect(matchProperty(property_f, { key: 122 })).toBe(false)
2236
-
2237
- // # this turns into a string comparison
2238
- expect(matchProperty(property_f, { key: 129 })).toBe(true)
2239
- })
2240
-
2241
- it('with date operators', () => {
2242
- // is date before
2243
- const property_a = { key: 'key', value: '2022-05-01', operator: 'is_date_before' }
2244
- expect(matchProperty(property_a, { key: '2022-03-01' })).toBe(true)
2245
- expect(matchProperty(property_a, { key: '2022-04-30' })).toBe(true)
2246
- expect(matchProperty(property_a, { key: new Date(2022, 3, 30) })).toBe(true)
2247
- expect(matchProperty(property_a, { key: new Date(2022, 3, 30, 1, 2, 3) })).toBe(true)
2248
- expect(matchProperty(property_a, { key: new Date('2022-04-30T00:00:00+02:00') })).toBe(true) // europe/madrid
2249
- expect(matchProperty(property_a, { key: new Date('2022-04-30') })).toBe(true)
2250
- expect(matchProperty(property_a, { key: '2022-05-30' })).toBe(false)
2251
-
2252
- // is date after
2253
- const property_b = { key: 'key', value: '2022-05-01', operator: 'is_date_after' }
2254
- expect(matchProperty(property_b, { key: '2022-05-02' })).toBe(true)
2255
- expect(matchProperty(property_b, { key: '2022-05-30' })).toBe(true)
2256
- expect(matchProperty(property_b, { key: new Date(2022, 4, 30) })).toBe(true)
2257
- expect(matchProperty(property_b, { key: new Date('2022-05-30') })).toBe(true)
2258
- expect(matchProperty(property_b, { key: '2022-04-30' })).toBe(false)
2259
-
2260
- // can't be an invalid number or invalid string
2261
- expect(() => matchProperty(property_a, { key: parseInt('62802180000012345') })).toThrow(InconclusiveMatchError)
2262
- expect(() => matchProperty(property_a, { key: 'abcdef' })).toThrow(InconclusiveMatchError)
2263
- // invalid flag property
2264
- const property_c = { key: 'key', value: 'abcd123', operator: 'is_date_before' }
2265
- expect(() => matchProperty(property_c, { key: '2022-05-30' })).toThrow(InconclusiveMatchError)
2266
-
2267
- // Timezone
2268
- const property_d = { key: 'key', value: '2022-04-05 12:34:12 +01:00', operator: 'is_date_before' }
2269
- expect(matchProperty(property_d, { key: '2022-05-30' })).toBe(false)
2270
-
2271
- expect(matchProperty(property_d, { key: '2022-03-30' })).toBe(true)
2272
- expect(matchProperty(property_d, { key: '2022-04-05 12:34:11+01:00' })).toBe(true)
2273
- expect(matchProperty(property_d, { key: '2022-04-05 11:34:11 +00:00' })).toBe(true)
2274
- expect(matchProperty(property_d, { key: '2022-04-05 11:34:13 +00:00' })).toBe(false)
2275
- })
2276
-
2277
- it.each([
2278
- ['is_date_before', '-6h', '2022-03-01', true],
2279
- ['is_date_before', '-6h', '2022-04-30', true],
2280
- // :TRICKY: MonthIndex is 0 indexed, so 3 is actually the 4th month, April.
2281
- ['is_date_before', '-6h', new Date(Date.UTC(2022, 3, 30, 1, 2, 3)), true],
2282
- // false because date comparison, instead of datetime, so reduces to same date
2283
- ['is_date_before', '-6h', new Date('2022-04-30T01:02:03+02:00'), true], // europe/madrid
2284
- ['is_date_before', '-6h', new Date('2022-04-30T20:02:03+02:00'), false], // europe/madrid
2285
- ['is_date_before', '-6h', new Date('2022-04-30T19:59:03+02:00'), true], // europe/madrid
2286
- ['is_date_before', '-6h', new Date('2022-04-30'), true],
2287
- ['is_date_before', '-6h', '2022-05-30', false],
2288
- // is date after
2289
- ['is_date_after', '1h', '2022-05-02', true],
2290
- ['is_date_after', '1h', '2022-05-30', true],
2291
- ['is_date_after', '1h', new Date(2022, 4, 30), true],
2292
- ['is_date_after', '1h', new Date('2022-05-30'), true],
2293
- ['is_date_after', '1h', '2022-04-30', false],
2294
- // # Try all possible relative dates
2295
- ['is_date_before', '1h', '2022-05-01 00:00:00 GMT', false],
2296
- ['is_date_before', '1h', '2022-04-30 22:00:00 GMT', true],
2297
- ['is_date_before', '-1d', '2022-04-29 23:59:00 GMT', true],
2298
- ['is_date_before', '-1d', '2022-04-30 00:00:01 GMT', false],
2299
- ['is_date_before', '1w', '2022-04-23 00:00:00 GMT', true],
2300
- ['is_date_before', '1w', '2022-04-24 00:00:00 GMT', false],
2301
- ['is_date_before', '1w', '2022-04-24 00:00:01 GMT', false],
2302
- ['is_date_before', '1m', '2022-03-01 00:00:00 GMT', true],
2303
- ['is_date_before', '1m', '2022-04-01 00:00:00 GMT', false],
2304
- ['is_date_before', '1m', '2022-04-05 00:00:01 GMT', false],
2305
-
2306
- ['is_date_before', '-1y', '2021-04-28 00:00:00 GMT', true],
2307
- ['is_date_before', '-1y', '2021-05-01 00:00:01 GMT', false],
2308
-
2309
- ['is_date_after', '122h', '2022-05-01 00:00:00 GMT', true],
2310
- ['is_date_after', '122h', '2022-04-23 01:00:00 GMT', false],
2311
-
2312
- ['is_date_after', '2d', '2022-05-01 00:00:00 GMT', true],
2313
- ['is_date_after', '2d', '2022-04-29 00:00:01 GMT', true],
2314
- ['is_date_after', '2d', '2022-04-29 00:00:00 GMT', false],
2315
-
2316
- ['is_date_after', '02w', '2022-05-01 00:00:00 GMT', true],
2317
- ['is_date_after', '02w', '2022-04-16 00:00:00 GMT', false],
2318
-
2319
- ['is_date_after', '-1m', '2022-04-01 00:00:01 GMT', true],
2320
- ['is_date_after', '-1m', '2022-04-01 00:00:00 GMT', false],
2321
-
2322
- ['is_date_after', '1y', '2022-05-01 00:00:00 GMT', true],
2323
- ['is_date_after', '1y', '2021-05-01 00:00:01 GMT', true],
2324
- ['is_date_after', '1y', '2021-05-01 00:00:00 GMT', false],
2325
- ['is_date_after', '1y', '2021-04-30 00:00:00 GMT', false],
2326
- ['is_date_after', '1y', '2021-03-01 12:13:00 GMT', false],
2327
- ])('with relative date operators: %s, %s, %s', (operator, value, date, expectation) => {
2328
- jest.setSystemTime(new Date('2022-05-01'))
2329
- expect(matchProperty({ key: 'key', value, operator }, { key: date })).toBe(expectation)
2330
-
2331
- return
2332
- })
2333
-
2334
- it('with relative date operators handles invalid keys', () => {
2335
- jest.setSystemTime(new Date('2022-05-01'))
2336
-
2337
- // # can't be an invalid string
2338
- expect(() => matchProperty({ key: 'key', value: '1d', operator: 'is_date_before' }, { key: 'abcdef' })).toThrow(
2339
- InconclusiveMatchError
2340
- )
2341
- // however js understands numbers as date offsets from utc epoch
2342
- expect(() => matchProperty({ key: 'key', value: '1d', operator: 'is_date_before' }, { key: 1 })).not.toThrow(
2343
- InconclusiveMatchError
2344
- )
2345
- })
2346
-
2347
- it('null or undefined property value', () => {
2348
- const property_a = { key: 'key', value: 'null', operator: 'is_not' }
2349
- expect(matchProperty(property_a, { key: null })).toBe(false)
2350
- expect(matchProperty(property_a, { key: undefined })).toBe(true)
2351
- expect(matchProperty(property_a, { key: 'null' })).toBe(false)
2352
- expect(matchProperty(property_a, { key: 'nul' })).toBe(true)
2353
-
2354
- const property_b = { key: 'key', value: 'null', operator: 'is_set' }
2355
- expect(matchProperty(property_b, { key: null })).toBe(false)
2356
- expect(matchProperty(property_b, { key: undefined })).toBe(false)
2357
- expect(matchProperty(property_b, { key: 'null' })).toBe(true)
2358
-
2359
- const property_c = { key: 'key', value: 'undefined', operator: 'icontains' }
2360
- expect(matchProperty(property_c, { key: null })).toBe(false)
2361
- expect(matchProperty(property_c, { key: undefined })).toBe(false)
2362
- expect(matchProperty(property_c, { key: 'lol' })).toBe(false)
2363
-
2364
- const property_d = { key: 'key', value: 'undefined', operator: 'regex' }
2365
- expect(matchProperty(property_d, { key: null })).toBe(false)
2366
- expect(matchProperty(property_d, { key: undefined })).toBe(false)
2367
-
2368
- const property_e = { key: 'key', value: 1, operator: 'gt' }
2369
- expect(matchProperty(property_e, { key: null })).toBe(false)
2370
- expect(matchProperty(property_e, { key: undefined })).toBe(false)
2371
-
2372
- const property_f = { key: 'key', value: 1, operator: 'lt' }
2373
- expect(matchProperty(property_f, { key: null })).toBe(false)
2374
- expect(matchProperty(property_f, { key: undefined })).toBe(false)
2375
-
2376
- const property_g = { key: 'key', value: 'xyz', operator: 'gte' }
2377
- expect(matchProperty(property_g, { key: null })).toBe(false)
2378
- expect(matchProperty(property_g, { key: undefined })).toBe(false)
2379
-
2380
- const property_h = { key: 'key', value: 'Oo', operator: 'lte' }
2381
- expect(matchProperty(property_h, { key: null })).toBe(false)
2382
- expect(matchProperty(property_h, { key: undefined })).toBe(false)
2383
-
2384
- const property_h_lower = { key: 'key', value: 'oo', operator: 'lte' }
2385
- expect(matchProperty(property_h_lower, { key: null })).toBe(false)
2386
- expect(matchProperty(property_h_lower, { key: undefined })).toBe(false)
2387
-
2388
- const property_i = { key: 'key', value: '2022-05-01', operator: 'is_date_before' }
2389
-
2390
- expect(matchProperty(property_i, { key: null })).toBe(false)
2391
- expect(matchProperty(property_i, { key: undefined })).toBe(false)
2392
-
2393
- const property_j = { key: 'key', value: '2022-05-01', operator: 'is_date_after' }
2394
- expect(matchProperty(property_j, { key: null })).toBe(false)
2395
-
2396
- const property_k = { key: 'key', value: '2022-05-01', operator: 'is_date_before' }
2397
- expect(matchProperty(property_k, { key: null })).toBe(false)
2398
- })
2399
-
2400
- it('null or undefined override value', () => {
2401
- const property_a = { key: 'key', value: 'ab', operator: 'is_not' }
2402
- expect(matchProperty(property_a, { key: null })).toBe(true)
2403
- expect(matchProperty(property_a, { key: undefined })).toBe(true)
2404
- expect(matchProperty(property_a, { key: 'null' })).toBe(true)
2405
- expect(matchProperty(property_a, { key: 'nul' })).toBe(true)
2406
-
2407
- const property_b = { key: 'key', value: 'null', operator: 'is_set' }
2408
- expect(matchProperty(property_b, { key: null })).toBe(false)
2409
- expect(matchProperty(property_b, { key: undefined })).toBe(false)
2410
- expect(matchProperty(property_b, { key: 'null' })).toBe(true)
2411
-
2412
- const property_c = { key: 'key', value: 'app.posthog.com', operator: 'icontains' }
2413
- expect(matchProperty(property_c, { key: null })).toBe(false)
2414
- expect(matchProperty(property_c, { key: undefined })).toBe(false)
2415
- expect(matchProperty(property_c, { key: 'lol' })).toBe(false)
2416
- expect(matchProperty(property_c, { key: 'https://app.posthog.com' })).toBe(true)
2417
-
2418
- const property_d = { key: 'key', value: '.+', operator: 'regex' }
2419
- expect(matchProperty(property_d, { key: null })).toBe(false)
2420
- expect(matchProperty(property_d, { key: undefined })).toBe(false)
2421
- expect(matchProperty(property_d, { key: 'i_am_a_value' })).toBe(true)
2422
-
2423
- const property_e = { key: 'key', value: 1, operator: 'gt' }
2424
- expect(matchProperty(property_e, { key: null })).toBe(false)
2425
- expect(matchProperty(property_e, { key: undefined })).toBe(false)
2426
- expect(matchProperty(property_e, { key: 1 })).toBe(false)
2427
- expect(matchProperty(property_e, { key: 2 })).toBe(true)
2428
-
2429
- const property_f = { key: 'key', value: 1, operator: 'lt' }
2430
- expect(matchProperty(property_f, { key: null })).toBe(false)
2431
- expect(matchProperty(property_f, { key: undefined })).toBe(false)
2432
- expect(matchProperty(property_f, { key: 0 })).toBe(true)
2433
-
2434
- const property_g = { key: 'key', value: 'xyz', operator: 'gte' }
2435
- expect(matchProperty(property_g, { key: null })).toBe(false)
2436
- expect(matchProperty(property_g, { key: undefined })).toBe(false)
2437
- expect(matchProperty(property_g, { key: 'xyz' })).toBe(true)
2438
-
2439
- const property_h = { key: 'key', value: 'Oo', operator: 'lte' }
2440
- expect(matchProperty(property_h, { key: null })).toBe(false)
2441
- expect(matchProperty(property_h, { key: undefined })).toBe(false)
2442
- expect(matchProperty(property_h, { key: 'Oo' })).toBe(true)
2443
-
2444
- const property_h_lower = { key: 'key', value: 'oo', operator: 'lte' }
2445
- expect(matchProperty(property_h_lower, { key: null })).toBe(false)
2446
- expect(matchProperty(property_h_lower, { key: undefined })).toBe(false)
2447
- expect(matchProperty(property_h_lower, { key: 'oo' })).toBe(true)
2448
-
2449
- const property_i = { key: 'key', value: '2022-05-01', operator: 'is_date_before' }
2450
- expect(matchProperty(property_i, { key: null })).toBe(false)
2451
- expect(matchProperty(property_i, { key: undefined })).toBe(false)
2452
-
2453
- const property_j = { key: 'key', value: '2022-05-01', operator: 'is_date_after' }
2454
- expect(matchProperty(property_j, { key: null })).toBe(false)
2455
-
2456
- const property_k = { key: 'key', value: '2022-05-01', operator: 'is_date_before' }
2457
- expect(matchProperty(property_k, { key: null })).toBe(false)
2458
- })
2459
-
2460
- it('with invalid operator', () => {
2461
- const property_a = { key: 'key', value: '2022-05-01', operator: 'is_unknown' }
2462
-
2463
- expect(() => matchProperty(property_a, { key: 'random' })).toThrow(
2464
- new InconclusiveMatchError('Unknown operator: is_unknown')
2465
- )
2466
- })
2467
- })
2468
-
2469
- describe('relative date parsing', () => {
2470
- jest.useFakeTimers()
2471
- beforeEach(() => {
2472
- jest.setSystemTime(new Date('2020-01-01T12:01:20.134Z'))
2473
- })
2474
-
2475
- it('invalid input', () => {
2476
- expect(relativeDateParseForFeatureFlagMatching('1')).toBe(null)
2477
- expect(relativeDateParseForFeatureFlagMatching('1x')).toBe(null)
2478
- expect(relativeDateParseForFeatureFlagMatching('1.2y')).toBe(null)
2479
- expect(relativeDateParseForFeatureFlagMatching('1z')).toBe(null)
2480
- expect(relativeDateParseForFeatureFlagMatching('1s')).toBe(null)
2481
- expect(relativeDateParseForFeatureFlagMatching('123344000.134m')).toBe(null)
2482
- expect(relativeDateParseForFeatureFlagMatching('bazinga')).toBe(null)
2483
- expect(relativeDateParseForFeatureFlagMatching('000bello')).toBe(null)
2484
- expect(relativeDateParseForFeatureFlagMatching('000hello')).toBe(null)
2485
-
2486
- expect(relativeDateParseForFeatureFlagMatching('000h')).not.toBe(null)
2487
- expect(relativeDateParseForFeatureFlagMatching('1000h')).not.toBe(null)
2488
- })
2489
-
2490
- it('overflow', () => {
2491
- expect(relativeDateParseForFeatureFlagMatching('1000000h')).toBe(null)
2492
- expect(relativeDateParseForFeatureFlagMatching('100000000000000000y')).toBe(null)
2493
- })
2494
-
2495
- it('hour parsing', () => {
2496
- expect(relativeDateParseForFeatureFlagMatching('1h')).toEqual(new Date('2020-01-01T11:01:20.134Z'))
2497
- expect(relativeDateParseForFeatureFlagMatching('2h')).toEqual(new Date('2020-01-01T10:01:20.134Z'))
2498
- expect(relativeDateParseForFeatureFlagMatching('24h')).toEqual(new Date('2019-12-31T12:01:20.134Z'))
2499
- expect(relativeDateParseForFeatureFlagMatching('30h')).toEqual(new Date('2019-12-31T06:01:20.134Z'))
2500
- expect(relativeDateParseForFeatureFlagMatching('48h')).toEqual(new Date('2019-12-30T12:01:20.134Z'))
2501
-
2502
- expect(relativeDateParseForFeatureFlagMatching('24h')).toEqual(relativeDateParseForFeatureFlagMatching('1d'))
2503
- expect(relativeDateParseForFeatureFlagMatching('48h')).toEqual(relativeDateParseForFeatureFlagMatching('2d'))
2504
- })
2505
-
2506
- it('day parsing', () => {
2507
- expect(relativeDateParseForFeatureFlagMatching('1d')).toEqual(new Date('2019-12-31T12:01:20.134Z'))
2508
- expect(relativeDateParseForFeatureFlagMatching('2d')).toEqual(new Date('2019-12-30T12:01:20.134Z'))
2509
- expect(relativeDateParseForFeatureFlagMatching('7d')).toEqual(new Date('2019-12-25T12:01:20.134Z'))
2510
- expect(relativeDateParseForFeatureFlagMatching('14d')).toEqual(new Date('2019-12-18T12:01:20.134Z'))
2511
- expect(relativeDateParseForFeatureFlagMatching('30d')).toEqual(new Date('2019-12-02T12:01:20.134Z'))
2512
-
2513
- expect(relativeDateParseForFeatureFlagMatching('7d')).toEqual(relativeDateParseForFeatureFlagMatching('1w'))
2514
- })
2515
-
2516
- it('week parsing', () => {
2517
- expect(relativeDateParseForFeatureFlagMatching('1w')).toEqual(new Date('2019-12-25T12:01:20.134Z'))
2518
- expect(relativeDateParseForFeatureFlagMatching('2w')).toEqual(new Date('2019-12-18T12:01:20.134Z'))
2519
- expect(relativeDateParseForFeatureFlagMatching('4w')).toEqual(new Date('2019-12-04T12:01:20.134Z'))
2520
- expect(relativeDateParseForFeatureFlagMatching('8w')).toEqual(new Date('2019-11-06T12:01:20.134Z'))
2521
-
2522
- expect(relativeDateParseForFeatureFlagMatching('1m')).toEqual(new Date('2019-12-01T12:01:20.134Z'))
2523
- expect(relativeDateParseForFeatureFlagMatching('4w')).not.toEqual(relativeDateParseForFeatureFlagMatching('1m'))
2524
- })
2525
-
2526
- it('month parsing', () => {
2527
- expect(relativeDateParseForFeatureFlagMatching('1m')).toEqual(new Date('2019-12-01T12:01:20.134Z'))
2528
- expect(relativeDateParseForFeatureFlagMatching('2m')).toEqual(new Date('2019-11-01T12:01:20.134Z'))
2529
-
2530
- expect(relativeDateParseForFeatureFlagMatching('4m')).toEqual(new Date('2019-09-01T12:01:20.134Z'))
2531
- expect(relativeDateParseForFeatureFlagMatching('5m')).toEqual(new Date('2019-08-01T12:01:20.134Z'))
2532
- expect(relativeDateParseForFeatureFlagMatching('6m')).toEqual(new Date('2019-07-01T12:01:20.134Z'))
2533
- expect(relativeDateParseForFeatureFlagMatching('8m')).toEqual(new Date('2019-05-01T12:01:20.134Z'))
2534
- expect(relativeDateParseForFeatureFlagMatching('10m')).toEqual(new Date('2019-03-01T12:01:20.134Z'))
2535
-
2536
- expect(relativeDateParseForFeatureFlagMatching('24m')).toEqual(new Date('2018-01-01T12:01:20.134Z'))
2537
-
2538
- expect(relativeDateParseForFeatureFlagMatching('1y')).toEqual(new Date('2019-01-01T12:01:20.134Z'))
2539
- expect(relativeDateParseForFeatureFlagMatching('12m')).toEqual(relativeDateParseForFeatureFlagMatching('1y'))
2540
-
2541
- jest.setSystemTime(new Date('2020-04-03T00:00:00Z'))
2542
- expect(relativeDateParseForFeatureFlagMatching('1m')).toEqual(new Date('2020-03-03T00:00:00Z'))
2543
- expect(relativeDateParseForFeatureFlagMatching('2m')).toEqual(new Date('2020-02-03T00:00:00Z'))
2544
- expect(relativeDateParseForFeatureFlagMatching('4m')).toEqual(new Date('2019-12-03T00:00:00Z'))
2545
- expect(relativeDateParseForFeatureFlagMatching('8m')).toEqual(new Date('2019-08-03T00:00:00Z'))
2546
-
2547
- expect(relativeDateParseForFeatureFlagMatching('1y')).toEqual(new Date('2019-04-03T00:00:00Z'))
2548
- expect(relativeDateParseForFeatureFlagMatching('12m')).toEqual(relativeDateParseForFeatureFlagMatching('1y'))
2549
- })
2550
-
2551
- it('year parsing', () => {
2552
- expect(relativeDateParseForFeatureFlagMatching('1y')).toEqual(new Date('2019-01-01T12:01:20.134Z'))
2553
- expect(relativeDateParseForFeatureFlagMatching('2y')).toEqual(new Date('2018-01-01T12:01:20.134Z'))
2554
- expect(relativeDateParseForFeatureFlagMatching('4y')).toEqual(new Date('2016-01-01T12:01:20.134Z'))
2555
- expect(relativeDateParseForFeatureFlagMatching('8y')).toEqual(new Date('2012-01-01T12:01:20.134Z'))
2556
-
2557
- expect(relativeDateParseForFeatureFlagMatching('1y')).toEqual(new Date('2019-01-01T12:01:20.134Z'))
2558
- expect(relativeDateParseForFeatureFlagMatching('12m')).toEqual(relativeDateParseForFeatureFlagMatching('1y'))
2559
- })
2560
- })
2561
-
2562
- describe('consistency tests', () => {
2563
- // # These tests are the same across all libraries
2564
- // # See https://github.com/PostHog/posthog/blob/master/posthog/test/test_feature_flag.py#L627
2565
- // # where this test has directly been copied from.
2566
- // # They ensure that the server and library hash calculations are in sync.
2567
-
2568
- let posthog: PostHog
2569
- jest.useFakeTimers()
2570
-
2571
- it('is consistent for simple flags', () => {
2572
- const flags = {
2573
- flags: [
2574
- {
2575
- id: 1,
2576
- name: '',
2577
- key: 'simple-flag',
2578
- active: true,
2579
- filters: {
2580
- groups: [{ properties: [], rollout_percentage: 45 }],
2581
- },
2582
- },
2583
- ],
2584
- }
2585
-
2586
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags, decideFlags: {}, decideStatus: 400 }))
2587
-
2588
- posthog = new PostHog('TEST_API_KEY', {
2589
- host: 'http://example.com',
2590
- personalApiKey: 'TEST_PERSONAL_API_KEY',
2591
- ...posthogImmediateResolveOptions,
2592
- })
2593
-
2594
- const results = [
2595
- false,
2596
- true,
2597
- true,
2598
- false,
2599
- true,
2600
- false,
2601
- false,
2602
- true,
2603
- false,
2604
- true,
2605
- false,
2606
- true,
2607
- true,
2608
- false,
2609
- true,
2610
- false,
2611
- false,
2612
- false,
2613
- true,
2614
- true,
2615
- false,
2616
- true,
2617
- false,
2618
- false,
2619
- true,
2620
- false,
2621
- true,
2622
- true,
2623
- false,
2624
- false,
2625
- false,
2626
- true,
2627
- true,
2628
- true,
2629
- true,
2630
- false,
2631
- false,
2632
- false,
2633
- false,
2634
- false,
2635
- false,
2636
- true,
2637
- true,
2638
- false,
2639
- true,
2640
- true,
2641
- false,
2642
- false,
2643
- false,
2644
- true,
2645
- true,
2646
- false,
2647
- false,
2648
- false,
2649
- false,
2650
- true,
2651
- false,
2652
- true,
2653
- false,
2654
- true,
2655
- false,
2656
- true,
2657
- true,
2658
- false,
2659
- true,
2660
- false,
2661
- true,
2662
- false,
2663
- true,
2664
- true,
2665
- false,
2666
- false,
2667
- true,
2668
- false,
2669
- false,
2670
- true,
2671
- false,
2672
- true,
2673
- false,
2674
- false,
2675
- true,
2676
- false,
2677
- false,
2678
- false,
2679
- true,
2680
- true,
2681
- false,
2682
- true,
2683
- true,
2684
- false,
2685
- true,
2686
- true,
2687
- true,
2688
- true,
2689
- true,
2690
- false,
2691
- true,
2692
- true,
2693
- false,
2694
- false,
2695
- true,
2696
- true,
2697
- true,
2698
- true,
2699
- false,
2700
- false,
2701
- true,
2702
- false,
2703
- true,
2704
- true,
2705
- true,
2706
- false,
2707
- false,
2708
- false,
2709
- false,
2710
- false,
2711
- true,
2712
- false,
2713
- false,
2714
- true,
2715
- true,
2716
- true,
2717
- false,
2718
- false,
2719
- true,
2720
- false,
2721
- true,
2722
- false,
2723
- false,
2724
- true,
2725
- false,
2726
- false,
2727
- false,
2728
- false,
2729
- false,
2730
- false,
2731
- false,
2732
- false,
2733
- true,
2734
- true,
2735
- false,
2736
- false,
2737
- true,
2738
- false,
2739
- false,
2740
- true,
2741
- true,
2742
- false,
2743
- false,
2744
- true,
2745
- false,
2746
- true,
2747
- false,
2748
- true,
2749
- true,
2750
- true,
2751
- false,
2752
- false,
2753
- false,
2754
- true,
2755
- false,
2756
- false,
2757
- false,
2758
- false,
2759
- true,
2760
- true,
2761
- false,
2762
- true,
2763
- true,
2764
- false,
2765
- true,
2766
- false,
2767
- true,
2768
- true,
2769
- false,
2770
- true,
2771
- false,
2772
- true,
2773
- true,
2774
- true,
2775
- false,
2776
- true,
2777
- false,
2778
- false,
2779
- true,
2780
- true,
2781
- false,
2782
- true,
2783
- false,
2784
- true,
2785
- true,
2786
- false,
2787
- false,
2788
- true,
2789
- true,
2790
- true,
2791
- true,
2792
- false,
2793
- true,
2794
- true,
2795
- false,
2796
- false,
2797
- true,
2798
- false,
2799
- true,
2800
- false,
2801
- false,
2802
- true,
2803
- true,
2804
- false,
2805
- true,
2806
- false,
2807
- true,
2808
- false,
2809
- false,
2810
- false,
2811
- false,
2812
- false,
2813
- false,
2814
- false,
2815
- true,
2816
- false,
2817
- true,
2818
- true,
2819
- false,
2820
- false,
2821
- true,
2822
- false,
2823
- true,
2824
- false,
2825
- false,
2826
- false,
2827
- true,
2828
- false,
2829
- true,
2830
- false,
2831
- false,
2832
- false,
2833
- true,
2834
- false,
2835
- false,
2836
- true,
2837
- false,
2838
- true,
2839
- true,
2840
- false,
2841
- false,
2842
- false,
2843
- false,
2844
- true,
2845
- false,
2846
- false,
2847
- false,
2848
- false,
2849
- false,
2850
- false,
2851
- false,
2852
- false,
2853
- false,
2854
- false,
2855
- false,
2856
- false,
2857
- false,
2858
- true,
2859
- true,
2860
- false,
2861
- true,
2862
- false,
2863
- true,
2864
- true,
2865
- false,
2866
- true,
2867
- false,
2868
- true,
2869
- false,
2870
- false,
2871
- false,
2872
- true,
2873
- true,
2874
- true,
2875
- true,
2876
- false,
2877
- false,
2878
- false,
2879
- false,
2880
- false,
2881
- true,
2882
- true,
2883
- true,
2884
- false,
2885
- false,
2886
- true,
2887
- true,
2888
- false,
2889
- false,
2890
- false,
2891
- false,
2892
- false,
2893
- true,
2894
- false,
2895
- true,
2896
- true,
2897
- true,
2898
- true,
2899
- false,
2900
- true,
2901
- true,
2902
- true,
2903
- false,
2904
- false,
2905
- true,
2906
- false,
2907
- true,
2908
- false,
2909
- false,
2910
- true,
2911
- true,
2912
- true,
2913
- false,
2914
- true,
2915
- false,
2916
- false,
2917
- false,
2918
- true,
2919
- true,
2920
- false,
2921
- true,
2922
- false,
2923
- true,
2924
- false,
2925
- true,
2926
- true,
2927
- true,
2928
- true,
2929
- true,
2930
- false,
2931
- false,
2932
- true,
2933
- false,
2934
- true,
2935
- false,
2936
- true,
2937
- true,
2938
- true,
2939
- false,
2940
- true,
2941
- false,
2942
- true,
2943
- true,
2944
- false,
2945
- true,
2946
- true,
2947
- true,
2948
- true,
2949
- true,
2950
- false,
2951
- false,
2952
- false,
2953
- false,
2954
- false,
2955
- true,
2956
- false,
2957
- true,
2958
- false,
2959
- false,
2960
- true,
2961
- true,
2962
- false,
2963
- false,
2964
- false,
2965
- true,
2966
- false,
2967
- true,
2968
- true,
2969
- true,
2970
- true,
2971
- false,
2972
- false,
2973
- false,
2974
- false,
2975
- true,
2976
- true,
2977
- false,
2978
- false,
2979
- true,
2980
- true,
2981
- false,
2982
- true,
2983
- true,
2984
- true,
2985
- true,
2986
- false,
2987
- true,
2988
- true,
2989
- true,
2990
- false,
2991
- false,
2992
- true,
2993
- true,
2994
- false,
2995
- false,
2996
- true,
2997
- false,
2998
- false,
2999
- true,
3000
- false,
3001
- false,
3002
- false,
3003
- false,
3004
- false,
3005
- false,
3006
- false,
3007
- false,
3008
- false,
3009
- false,
3010
- true,
3011
- true,
3012
- false,
3013
- false,
3014
- true,
3015
- false,
3016
- false,
3017
- true,
3018
- false,
3019
- true,
3020
- false,
3021
- false,
3022
- true,
3023
- false,
3024
- false,
3025
- false,
3026
- false,
3027
- false,
3028
- false,
3029
- true,
3030
- false,
3031
- false,
3032
- false,
3033
- false,
3034
- false,
3035
- false,
3036
- false,
3037
- false,
3038
- false,
3039
- true,
3040
- true,
3041
- true,
3042
- false,
3043
- false,
3044
- false,
3045
- true,
3046
- false,
3047
- true,
3048
- false,
3049
- false,
3050
- false,
3051
- true,
3052
- false,
3053
- false,
3054
- false,
3055
- false,
3056
- false,
3057
- false,
3058
- false,
3059
- true,
3060
- false,
3061
- false,
3062
- false,
3063
- false,
3064
- false,
3065
- false,
3066
- false,
3067
- false,
3068
- true,
3069
- false,
3070
- true,
3071
- false,
3072
- true,
3073
- true,
3074
- true,
3075
- false,
3076
- false,
3077
- false,
3078
- true,
3079
- true,
3080
- true,
3081
- false,
3082
- true,
3083
- false,
3084
- true,
3085
- true,
3086
- false,
3087
- false,
3088
- false,
3089
- true,
3090
- false,
3091
- false,
3092
- false,
3093
- false,
3094
- true,
3095
- false,
3096
- true,
3097
- false,
3098
- true,
3099
- true,
3100
- false,
3101
- true,
3102
- false,
3103
- false,
3104
- false,
3105
- true,
3106
- false,
3107
- false,
3108
- true,
3109
- true,
3110
- false,
3111
- true,
3112
- false,
3113
- false,
3114
- false,
3115
- false,
3116
- false,
3117
- false,
3118
- true,
3119
- true,
3120
- false,
3121
- false,
3122
- true,
3123
- false,
3124
- false,
3125
- true,
3126
- true,
3127
- true,
3128
- false,
3129
- false,
3130
- false,
3131
- true,
3132
- false,
3133
- false,
3134
- false,
3135
- false,
3136
- true,
3137
- false,
3138
- true,
3139
- false,
3140
- false,
3141
- false,
3142
- true,
3143
- false,
3144
- true,
3145
- true,
3146
- false,
3147
- true,
3148
- false,
3149
- true,
3150
- false,
3151
- true,
3152
- false,
3153
- false,
3154
- true,
3155
- false,
3156
- false,
3157
- true,
3158
- false,
3159
- true,
3160
- false,
3161
- true,
3162
- false,
3163
- true,
3164
- false,
3165
- false,
3166
- true,
3167
- true,
3168
- true,
3169
- true,
3170
- false,
3171
- true,
3172
- false,
3173
- false,
3174
- false,
3175
- false,
3176
- false,
3177
- true,
3178
- false,
3179
- false,
3180
- true,
3181
- false,
3182
- false,
3183
- true,
3184
- true,
3185
- false,
3186
- false,
3187
- false,
3188
- false,
3189
- true,
3190
- true,
3191
- true,
3192
- false,
3193
- false,
3194
- true,
3195
- false,
3196
- false,
3197
- true,
3198
- true,
3199
- true,
3200
- true,
3201
- false,
3202
- false,
3203
- false,
3204
- true,
3205
- false,
3206
- false,
3207
- false,
3208
- true,
3209
- false,
3210
- false,
3211
- true,
3212
- true,
3213
- true,
3214
- true,
3215
- false,
3216
- false,
3217
- true,
3218
- true,
3219
- false,
3220
- true,
3221
- false,
3222
- true,
3223
- false,
3224
- false,
3225
- true,
3226
- true,
3227
- false,
3228
- true,
3229
- true,
3230
- true,
3231
- true,
3232
- false,
3233
- false,
3234
- true,
3235
- false,
3236
- false,
3237
- true,
3238
- true,
3239
- false,
3240
- true,
3241
- false,
3242
- true,
3243
- false,
3244
- false,
3245
- true,
3246
- false,
3247
- false,
3248
- false,
3249
- false,
3250
- true,
3251
- true,
3252
- true,
3253
- false,
3254
- true,
3255
- false,
3256
- false,
3257
- true,
3258
- false,
3259
- false,
3260
- true,
3261
- false,
3262
- false,
3263
- false,
3264
- false,
3265
- true,
3266
- false,
3267
- true,
3268
- false,
3269
- true,
3270
- true,
3271
- false,
3272
- false,
3273
- true,
3274
- false,
3275
- true,
3276
- true,
3277
- true,
3278
- false,
3279
- false,
3280
- false,
3281
- false,
3282
- true,
3283
- true,
3284
- false,
3285
- true,
3286
- false,
3287
- false,
3288
- false,
3289
- true,
3290
- false,
3291
- false,
3292
- false,
3293
- false,
3294
- true,
3295
- true,
3296
- true,
3297
- false,
3298
- false,
3299
- false,
3300
- true,
3301
- true,
3302
- true,
3303
- true,
3304
- false,
3305
- true,
3306
- true,
3307
- false,
3308
- true,
3309
- true,
3310
- true,
3311
- false,
3312
- true,
3313
- false,
3314
- false,
3315
- true,
3316
- false,
3317
- true,
3318
- true,
3319
- true,
3320
- true,
3321
- false,
3322
- true,
3323
- false,
3324
- true,
3325
- false,
3326
- true,
3327
- false,
3328
- false,
3329
- true,
3330
- true,
3331
- false,
3332
- false,
3333
- true,
3334
- false,
3335
- true,
3336
- false,
3337
- false,
3338
- false,
3339
- false,
3340
- true,
3341
- false,
3342
- true,
3343
- false,
3344
- false,
3345
- false,
3346
- true,
3347
- true,
3348
- true,
3349
- false,
3350
- false,
3351
- false,
3352
- true,
3353
- false,
3354
- true,
3355
- true,
3356
- false,
3357
- false,
3358
- false,
3359
- false,
3360
- false,
3361
- true,
3362
- false,
3363
- true,
3364
- false,
3365
- false,
3366
- true,
3367
- true,
3368
- false,
3369
- true,
3370
- true,
3371
- true,
3372
- true,
3373
- false,
3374
- false,
3375
- true,
3376
- false,
3377
- false,
3378
- true,
3379
- false,
3380
- true,
3381
- false,
3382
- true,
3383
- true,
3384
- false,
3385
- false,
3386
- false,
3387
- true,
3388
- false,
3389
- true,
3390
- true,
3391
- false,
3392
- false,
3393
- false,
3394
- true,
3395
- false,
3396
- true,
3397
- false,
3398
- true,
3399
- true,
3400
- false,
3401
- true,
3402
- false,
3403
- false,
3404
- true,
3405
- false,
3406
- false,
3407
- false,
3408
- true,
3409
- true,
3410
- true,
3411
- false,
3412
- false,
3413
- false,
3414
- false,
3415
- false,
3416
- true,
3417
- false,
3418
- false,
3419
- true,
3420
- true,
3421
- true,
3422
- true,
3423
- true,
3424
- false,
3425
- false,
3426
- false,
3427
- false,
3428
- false,
3429
- false,
3430
- false,
3431
- false,
3432
- true,
3433
- true,
3434
- true,
3435
- false,
3436
- false,
3437
- true,
3438
- true,
3439
- false,
3440
- true,
3441
- true,
3442
- false,
3443
- true,
3444
- false,
3445
- true,
3446
- false,
3447
- false,
3448
- false,
3449
- true,
3450
- false,
3451
- false,
3452
- true,
3453
- false,
3454
- false,
3455
- true,
3456
- true,
3457
- true,
3458
- true,
3459
- false,
3460
- false,
3461
- true,
3462
- false,
3463
- true,
3464
- true,
3465
- false,
3466
- false,
3467
- true,
3468
- false,
3469
- false,
3470
- true,
3471
- true,
3472
- false,
3473
- true,
3474
- false,
3475
- false,
3476
- true,
3477
- true,
3478
- true,
3479
- false,
3480
- false,
3481
- false,
3482
- false,
3483
- false,
3484
- true,
3485
- false,
3486
- true,
3487
- false,
3488
- false,
3489
- false,
3490
- false,
3491
- false,
3492
- true,
3493
- true,
3494
- false,
3495
- true,
3496
- true,
3497
- true,
3498
- false,
3499
- false,
3500
- false,
3501
- false,
3502
- true,
3503
- true,
3504
- true,
3505
- true,
3506
- false,
3507
- true,
3508
- true,
3509
- false,
3510
- true,
3511
- false,
3512
- true,
3513
- false,
3514
- true,
3515
- false,
3516
- false,
3517
- false,
3518
- false,
3519
- true,
3520
- true,
3521
- true,
3522
- true,
3523
- false,
3524
- false,
3525
- true,
3526
- false,
3527
- true,
3528
- true,
3529
- false,
3530
- false,
3531
- false,
3532
- false,
3533
- false,
3534
- false,
3535
- true,
3536
- false,
3537
- true,
3538
- false,
3539
- true,
3540
- true,
3541
- false,
3542
- false,
3543
- true,
3544
- true,
3545
- true,
3546
- true,
3547
- false,
3548
- false,
3549
- true,
3550
- false,
3551
- true,
3552
- true,
3553
- false,
3554
- false,
3555
- true,
3556
- true,
3557
- true,
3558
- false,
3559
- true,
3560
- false,
3561
- false,
3562
- true,
3563
- true,
3564
- false,
3565
- false,
3566
- false,
3567
- true,
3568
- false,
3569
- false,
3570
- true,
3571
- false,
3572
- false,
3573
- false,
3574
- true,
3575
- true,
3576
- true,
3577
- true,
3578
- false,
3579
- true,
3580
- false,
3581
- true,
3582
- false,
3583
- true,
3584
- false,
3585
- true,
3586
- false,
3587
- false,
3588
- true,
3589
- false,
3590
- false,
3591
- true,
3592
- false,
3593
- true,
3594
- true,
3595
- ]
3596
-
3597
- results.forEach(async (result, index) => {
3598
- const distinctId = `distinct_id_${index}`
3599
- const value = await posthog.isFeatureEnabled('simple-flag', distinctId)
3600
- expect(value).toBe(result)
3601
- })
3602
- })
3603
-
3604
- it('is consistent for multivariate flags', async () => {
3605
- const flags = {
3606
- flags: [
3607
- {
3608
- id: 1,
3609
- name: 'Beta Feature',
3610
- key: 'multivariate-flag',
3611
- active: true,
3612
- filters: {
3613
- groups: [{ properties: [], rollout_percentage: 55 }],
3614
- multivariate: {
3615
- variants: [
3616
- { key: 'first-variant', name: 'First Variant', rollout_percentage: 50 },
3617
- { key: 'second-variant', name: 'Second Variant', rollout_percentage: 20 },
3618
- { key: 'third-variant', name: 'Third Variant', rollout_percentage: 20 },
3619
- { key: 'fourth-variant', name: 'Fourth Variant', rollout_percentage: 5 },
3620
- { key: 'fifth-variant', name: 'Fifth Variant', rollout_percentage: 5 },
3621
- ],
3622
- },
3623
- },
3624
- },
3625
- ],
3626
- }
3627
-
3628
- mockedFetch.mockImplementation(apiImplementation({ localFlags: flags, decideFlags: {}, decideStatus: 400 }))
3629
-
3630
- posthog = new PostHog('TEST_API_KEY', {
3631
- host: 'http://example.com',
3632
- personalApiKey: 'TEST_PERSONAL_API_KEY',
3633
- ...posthogImmediateResolveOptions,
3634
- })
3635
-
3636
- const results = [
3637
- 'second-variant',
3638
- 'second-variant',
3639
- 'first-variant',
3640
- false,
3641
- false,
3642
- 'second-variant',
3643
- 'first-variant',
3644
- false,
3645
- false,
3646
- false,
3647
- 'first-variant',
3648
- 'third-variant',
3649
- false,
3650
- 'first-variant',
3651
- 'second-variant',
3652
- 'first-variant',
3653
- false,
3654
- false,
3655
- 'fourth-variant',
3656
- 'first-variant',
3657
- false,
3658
- 'third-variant',
3659
- false,
3660
- false,
3661
- false,
3662
- 'first-variant',
3663
- 'first-variant',
3664
- 'first-variant',
3665
- 'first-variant',
3666
- 'first-variant',
3667
- 'first-variant',
3668
- 'third-variant',
3669
- false,
3670
- 'third-variant',
3671
- 'second-variant',
3672
- 'first-variant',
3673
- false,
3674
- 'third-variant',
3675
- false,
3676
- false,
3677
- 'first-variant',
3678
- 'second-variant',
3679
- false,
3680
- 'first-variant',
3681
- 'first-variant',
3682
- 'second-variant',
3683
- false,
3684
- 'first-variant',
3685
- false,
3686
- false,
3687
- 'first-variant',
3688
- 'first-variant',
3689
- 'first-variant',
3690
- 'second-variant',
3691
- 'first-variant',
3692
- false,
3693
- 'second-variant',
3694
- 'second-variant',
3695
- 'third-variant',
3696
- 'second-variant',
3697
- 'first-variant',
3698
- false,
3699
- 'first-variant',
3700
- 'second-variant',
3701
- 'fourth-variant',
3702
- false,
3703
- 'first-variant',
3704
- 'first-variant',
3705
- 'first-variant',
3706
- false,
3707
- 'first-variant',
3708
- 'second-variant',
3709
- false,
3710
- 'third-variant',
3711
- false,
3712
- false,
3713
- false,
3714
- false,
3715
- false,
3716
- false,
3717
- 'first-variant',
3718
- 'fifth-variant',
3719
- false,
3720
- 'second-variant',
3721
- 'first-variant',
3722
- 'second-variant',
3723
- false,
3724
- 'third-variant',
3725
- 'third-variant',
3726
- false,
3727
- false,
3728
- false,
3729
- false,
3730
- 'third-variant',
3731
- false,
3732
- false,
3733
- 'first-variant',
3734
- 'first-variant',
3735
- false,
3736
- 'third-variant',
3737
- 'third-variant',
3738
- false,
3739
- 'third-variant',
3740
- 'second-variant',
3741
- 'third-variant',
3742
- false,
3743
- false,
3744
- 'second-variant',
3745
- 'first-variant',
3746
- false,
3747
- false,
3748
- 'first-variant',
3749
- false,
3750
- false,
3751
- false,
3752
- false,
3753
- 'first-variant',
3754
- 'first-variant',
3755
- 'first-variant',
3756
- false,
3757
- false,
3758
- false,
3759
- 'first-variant',
3760
- 'first-variant',
3761
- false,
3762
- 'first-variant',
3763
- 'first-variant',
3764
- false,
3765
- false,
3766
- false,
3767
- false,
3768
- false,
3769
- false,
3770
- false,
3771
- false,
3772
- false,
3773
- 'first-variant',
3774
- 'first-variant',
3775
- 'first-variant',
3776
- 'first-variant',
3777
- 'second-variant',
3778
- 'first-variant',
3779
- 'first-variant',
3780
- 'first-variant',
3781
- 'second-variant',
3782
- false,
3783
- 'second-variant',
3784
- 'first-variant',
3785
- 'second-variant',
3786
- 'first-variant',
3787
- false,
3788
- 'second-variant',
3789
- 'second-variant',
3790
- false,
3791
- 'first-variant',
3792
- false,
3793
- false,
3794
- false,
3795
- 'third-variant',
3796
- 'first-variant',
3797
- false,
3798
- false,
3799
- 'first-variant',
3800
- false,
3801
- false,
3802
- false,
3803
- false,
3804
- 'first-variant',
3805
- false,
3806
- false,
3807
- false,
3808
- false,
3809
- false,
3810
- false,
3811
- false,
3812
- 'first-variant',
3813
- 'first-variant',
3814
- 'third-variant',
3815
- 'first-variant',
3816
- 'first-variant',
3817
- false,
3818
- false,
3819
- 'first-variant',
3820
- false,
3821
- false,
3822
- 'fifth-variant',
3823
- 'second-variant',
3824
- false,
3825
- 'second-variant',
3826
- false,
3827
- 'first-variant',
3828
- 'third-variant',
3829
- 'first-variant',
3830
- 'fifth-variant',
3831
- 'third-variant',
3832
- false,
3833
- false,
3834
- 'fourth-variant',
3835
- false,
3836
- false,
3837
- false,
3838
- false,
3839
- 'third-variant',
3840
- false,
3841
- false,
3842
- 'third-variant',
3843
- false,
3844
- 'first-variant',
3845
- 'second-variant',
3846
- 'second-variant',
3847
- 'second-variant',
3848
- false,
3849
- 'first-variant',
3850
- 'third-variant',
3851
- 'first-variant',
3852
- 'first-variant',
3853
- false,
3854
- false,
3855
- false,
3856
- false,
3857
- false,
3858
- 'first-variant',
3859
- 'first-variant',
3860
- 'first-variant',
3861
- 'second-variant',
3862
- false,
3863
- false,
3864
- false,
3865
- 'second-variant',
3866
- false,
3867
- false,
3868
- 'first-variant',
3869
- false,
3870
- 'first-variant',
3871
- false,
3872
- false,
3873
- 'first-variant',
3874
- 'first-variant',
3875
- 'first-variant',
3876
- 'first-variant',
3877
- 'third-variant',
3878
- 'first-variant',
3879
- 'third-variant',
3880
- 'first-variant',
3881
- 'first-variant',
3882
- 'second-variant',
3883
- 'third-variant',
3884
- 'third-variant',
3885
- false,
3886
- 'second-variant',
3887
- 'first-variant',
3888
- false,
3889
- 'second-variant',
3890
- 'first-variant',
3891
- false,
3892
- 'first-variant',
3893
- false,
3894
- false,
3895
- 'first-variant',
3896
- 'fifth-variant',
3897
- 'first-variant',
3898
- false,
3899
- false,
3900
- false,
3901
- false,
3902
- 'first-variant',
3903
- 'first-variant',
3904
- 'second-variant',
3905
- false,
3906
- 'second-variant',
3907
- 'third-variant',
3908
- 'third-variant',
3909
- false,
3910
- 'first-variant',
3911
- 'third-variant',
3912
- false,
3913
- false,
3914
- 'first-variant',
3915
- false,
3916
- 'third-variant',
3917
- 'first-variant',
3918
- false,
3919
- 'third-variant',
3920
- 'first-variant',
3921
- 'first-variant',
3922
- false,
3923
- 'first-variant',
3924
- 'second-variant',
3925
- 'second-variant',
3926
- 'first-variant',
3927
- false,
3928
- false,
3929
- false,
3930
- 'second-variant',
3931
- false,
3932
- false,
3933
- 'first-variant',
3934
- 'first-variant',
3935
- false,
3936
- 'third-variant',
3937
- false,
3938
- 'first-variant',
3939
- false,
3940
- 'third-variant',
3941
- false,
3942
- 'third-variant',
3943
- 'second-variant',
3944
- 'first-variant',
3945
- false,
3946
- false,
3947
- 'first-variant',
3948
- 'third-variant',
3949
- 'first-variant',
3950
- 'second-variant',
3951
- 'fifth-variant',
3952
- false,
3953
- false,
3954
- 'first-variant',
3955
- false,
3956
- false,
3957
- false,
3958
- 'third-variant',
3959
- false,
3960
- 'second-variant',
3961
- 'first-variant',
3962
- false,
3963
- false,
3964
- false,
3965
- false,
3966
- 'third-variant',
3967
- false,
3968
- false,
3969
- 'third-variant',
3970
- false,
3971
- false,
3972
- 'first-variant',
3973
- 'third-variant',
3974
- false,
3975
- false,
3976
- 'first-variant',
3977
- false,
3978
- false,
3979
- 'fourth-variant',
3980
- 'fourth-variant',
3981
- 'third-variant',
3982
- 'second-variant',
3983
- 'first-variant',
3984
- 'third-variant',
3985
- 'fifth-variant',
3986
- false,
3987
- 'first-variant',
3988
- 'fifth-variant',
3989
- false,
3990
- 'first-variant',
3991
- 'first-variant',
3992
- 'first-variant',
3993
- false,
3994
- false,
3995
- false,
3996
- 'second-variant',
3997
- 'fifth-variant',
3998
- 'second-variant',
3999
- 'first-variant',
4000
- 'first-variant',
4001
- 'second-variant',
4002
- false,
4003
- false,
4004
- 'third-variant',
4005
- false,
4006
- 'second-variant',
4007
- 'fifth-variant',
4008
- false,
4009
- 'third-variant',
4010
- 'first-variant',
4011
- false,
4012
- false,
4013
- 'fourth-variant',
4014
- false,
4015
- false,
4016
- 'second-variant',
4017
- false,
4018
- false,
4019
- 'first-variant',
4020
- 'fourth-variant',
4021
- 'first-variant',
4022
- 'second-variant',
4023
- false,
4024
- false,
4025
- false,
4026
- 'first-variant',
4027
- 'third-variant',
4028
- 'third-variant',
4029
- false,
4030
- 'first-variant',
4031
- 'first-variant',
4032
- 'first-variant',
4033
- false,
4034
- 'first-variant',
4035
- false,
4036
- 'first-variant',
4037
- 'third-variant',
4038
- 'third-variant',
4039
- false,
4040
- false,
4041
- 'first-variant',
4042
- false,
4043
- false,
4044
- 'second-variant',
4045
- 'second-variant',
4046
- 'first-variant',
4047
- 'first-variant',
4048
- 'first-variant',
4049
- false,
4050
- 'fifth-variant',
4051
- 'first-variant',
4052
- false,
4053
- false,
4054
- false,
4055
- 'second-variant',
4056
- 'third-variant',
4057
- 'first-variant',
4058
- 'fourth-variant',
4059
- 'first-variant',
4060
- 'third-variant',
4061
- false,
4062
- 'first-variant',
4063
- 'first-variant',
4064
- false,
4065
- 'third-variant',
4066
- 'first-variant',
4067
- 'first-variant',
4068
- 'third-variant',
4069
- false,
4070
- 'fourth-variant',
4071
- 'fifth-variant',
4072
- 'first-variant',
4073
- 'first-variant',
4074
- false,
4075
- false,
4076
- false,
4077
- 'first-variant',
4078
- 'first-variant',
4079
- 'first-variant',
4080
- false,
4081
- 'first-variant',
4082
- 'first-variant',
4083
- 'second-variant',
4084
- 'first-variant',
4085
- false,
4086
- 'first-variant',
4087
- 'second-variant',
4088
- 'first-variant',
4089
- false,
4090
- 'first-variant',
4091
- 'second-variant',
4092
- false,
4093
- 'first-variant',
4094
- 'first-variant',
4095
- false,
4096
- 'first-variant',
4097
- false,
4098
- 'first-variant',
4099
- false,
4100
- 'first-variant',
4101
- false,
4102
- false,
4103
- false,
4104
- 'third-variant',
4105
- 'third-variant',
4106
- 'first-variant',
4107
- false,
4108
- false,
4109
- 'second-variant',
4110
- 'third-variant',
4111
- 'first-variant',
4112
- 'first-variant',
4113
- false,
4114
- false,
4115
- false,
4116
- 'second-variant',
4117
- 'first-variant',
4118
- false,
4119
- 'first-variant',
4120
- 'third-variant',
4121
- false,
4122
- 'first-variant',
4123
- false,
4124
- false,
4125
- false,
4126
- 'first-variant',
4127
- 'third-variant',
4128
- 'third-variant',
4129
- false,
4130
- false,
4131
- false,
4132
- false,
4133
- 'third-variant',
4134
- 'fourth-variant',
4135
- 'fourth-variant',
4136
- 'first-variant',
4137
- 'second-variant',
4138
- false,
4139
- 'first-variant',
4140
- false,
4141
- 'second-variant',
4142
- 'first-variant',
4143
- 'third-variant',
4144
- false,
4145
- 'third-variant',
4146
- false,
4147
- 'first-variant',
4148
- 'first-variant',
4149
- 'third-variant',
4150
- false,
4151
- false,
4152
- false,
4153
- 'fourth-variant',
4154
- 'second-variant',
4155
- 'first-variant',
4156
- false,
4157
- false,
4158
- 'first-variant',
4159
- 'fourth-variant',
4160
- false,
4161
- 'first-variant',
4162
- 'third-variant',
4163
- 'first-variant',
4164
- false,
4165
- false,
4166
- 'third-variant',
4167
- false,
4168
- 'first-variant',
4169
- false,
4170
- 'first-variant',
4171
- 'first-variant',
4172
- 'third-variant',
4173
- 'second-variant',
4174
- 'fourth-variant',
4175
- false,
4176
- 'first-variant',
4177
- false,
4178
- false,
4179
- false,
4180
- false,
4181
- 'second-variant',
4182
- 'first-variant',
4183
- 'second-variant',
4184
- false,
4185
- 'first-variant',
4186
- false,
4187
- 'first-variant',
4188
- 'first-variant',
4189
- false,
4190
- 'first-variant',
4191
- 'first-variant',
4192
- 'second-variant',
4193
- 'third-variant',
4194
- 'first-variant',
4195
- 'first-variant',
4196
- 'first-variant',
4197
- false,
4198
- false,
4199
- false,
4200
- 'third-variant',
4201
- false,
4202
- 'first-variant',
4203
- 'first-variant',
4204
- 'first-variant',
4205
- 'third-variant',
4206
- 'first-variant',
4207
- 'first-variant',
4208
- 'second-variant',
4209
- 'first-variant',
4210
- 'fifth-variant',
4211
- 'fourth-variant',
4212
- 'first-variant',
4213
- 'second-variant',
4214
- false,
4215
- 'fourth-variant',
4216
- false,
4217
- false,
4218
- false,
4219
- 'fourth-variant',
4220
- false,
4221
- false,
4222
- 'third-variant',
4223
- false,
4224
- false,
4225
- false,
4226
- 'first-variant',
4227
- 'third-variant',
4228
- 'third-variant',
4229
- 'second-variant',
4230
- 'first-variant',
4231
- 'second-variant',
4232
- 'first-variant',
4233
- false,
4234
- 'first-variant',
4235
- false,
4236
- false,
4237
- false,
4238
- false,
4239
- false,
4240
- 'first-variant',
4241
- 'first-variant',
4242
- false,
4243
- 'second-variant',
4244
- false,
4245
- false,
4246
- 'first-variant',
4247
- false,
4248
- 'second-variant',
4249
- 'first-variant',
4250
- 'first-variant',
4251
- 'first-variant',
4252
- 'third-variant',
4253
- 'second-variant',
4254
- false,
4255
- false,
4256
- 'fifth-variant',
4257
- 'third-variant',
4258
- false,
4259
- false,
4260
- 'first-variant',
4261
- false,
4262
- false,
4263
- false,
4264
- 'first-variant',
4265
- 'second-variant',
4266
- 'third-variant',
4267
- 'third-variant',
4268
- false,
4269
- false,
4270
- 'first-variant',
4271
- false,
4272
- 'third-variant',
4273
- 'first-variant',
4274
- false,
4275
- false,
4276
- false,
4277
- false,
4278
- 'fourth-variant',
4279
- 'first-variant',
4280
- false,
4281
- false,
4282
- false,
4283
- 'third-variant',
4284
- false,
4285
- false,
4286
- 'second-variant',
4287
- 'first-variant',
4288
- false,
4289
- false,
4290
- 'second-variant',
4291
- 'third-variant',
4292
- 'first-variant',
4293
- 'first-variant',
4294
- false,
4295
- 'first-variant',
4296
- 'first-variant',
4297
- false,
4298
- false,
4299
- 'second-variant',
4300
- 'third-variant',
4301
- 'second-variant',
4302
- 'third-variant',
4303
- false,
4304
- false,
4305
- 'first-variant',
4306
- false,
4307
- false,
4308
- 'first-variant',
4309
- false,
4310
- 'second-variant',
4311
- false,
4312
- false,
4313
- false,
4314
- false,
4315
- 'first-variant',
4316
- false,
4317
- 'third-variant',
4318
- false,
4319
- 'first-variant',
4320
- false,
4321
- false,
4322
- 'second-variant',
4323
- 'third-variant',
4324
- 'second-variant',
4325
- 'fourth-variant',
4326
- 'first-variant',
4327
- 'first-variant',
4328
- 'first-variant',
4329
- false,
4330
- 'first-variant',
4331
- false,
4332
- 'second-variant',
4333
- false,
4334
- false,
4335
- false,
4336
- false,
4337
- false,
4338
- 'first-variant',
4339
- false,
4340
- false,
4341
- false,
4342
- false,
4343
- false,
4344
- 'first-variant',
4345
- false,
4346
- 'second-variant',
4347
- false,
4348
- false,
4349
- false,
4350
- false,
4351
- 'second-variant',
4352
- false,
4353
- 'first-variant',
4354
- false,
4355
- 'third-variant',
4356
- false,
4357
- false,
4358
- 'first-variant',
4359
- 'third-variant',
4360
- false,
4361
- 'third-variant',
4362
- false,
4363
- false,
4364
- 'second-variant',
4365
- false,
4366
- 'first-variant',
4367
- 'second-variant',
4368
- 'first-variant',
4369
- false,
4370
- false,
4371
- false,
4372
- false,
4373
- false,
4374
- 'second-variant',
4375
- false,
4376
- false,
4377
- 'first-variant',
4378
- 'third-variant',
4379
- false,
4380
- 'first-variant',
4381
- false,
4382
- false,
4383
- false,
4384
- false,
4385
- false,
4386
- 'first-variant',
4387
- 'second-variant',
4388
- false,
4389
- false,
4390
- false,
4391
- 'first-variant',
4392
- 'first-variant',
4393
- 'fifth-variant',
4394
- false,
4395
- false,
4396
- false,
4397
- 'first-variant',
4398
- false,
4399
- 'third-variant',
4400
- false,
4401
- false,
4402
- 'second-variant',
4403
- false,
4404
- false,
4405
- false,
4406
- false,
4407
- false,
4408
- 'fourth-variant',
4409
- 'second-variant',
4410
- 'first-variant',
4411
- 'second-variant',
4412
- false,
4413
- 'second-variant',
4414
- false,
4415
- 'second-variant',
4416
- false,
4417
- 'first-variant',
4418
- false,
4419
- 'first-variant',
4420
- 'first-variant',
4421
- false,
4422
- 'second-variant',
4423
- false,
4424
- 'first-variant',
4425
- false,
4426
- 'fifth-variant',
4427
- false,
4428
- 'first-variant',
4429
- 'first-variant',
4430
- false,
4431
- false,
4432
- false,
4433
- 'first-variant',
4434
- false,
4435
- 'first-variant',
4436
- 'third-variant',
4437
- false,
4438
- false,
4439
- 'first-variant',
4440
- 'first-variant',
4441
- false,
4442
- false,
4443
- 'fifth-variant',
4444
- false,
4445
- false,
4446
- 'third-variant',
4447
- false,
4448
- 'third-variant',
4449
- 'first-variant',
4450
- 'first-variant',
4451
- 'third-variant',
4452
- 'third-variant',
4453
- false,
4454
- 'first-variant',
4455
- false,
4456
- false,
4457
- false,
4458
- false,
4459
- false,
4460
- 'first-variant',
4461
- false,
4462
- false,
4463
- false,
4464
- false,
4465
- 'second-variant',
4466
- 'first-variant',
4467
- 'second-variant',
4468
- 'first-variant',
4469
- false,
4470
- 'fifth-variant',
4471
- 'first-variant',
4472
- false,
4473
- false,
4474
- 'fourth-variant',
4475
- 'first-variant',
4476
- 'first-variant',
4477
- false,
4478
- false,
4479
- 'fourth-variant',
4480
- 'first-variant',
4481
- false,
4482
- 'second-variant',
4483
- 'third-variant',
4484
- 'third-variant',
4485
- 'first-variant',
4486
- 'first-variant',
4487
- false,
4488
- false,
4489
- false,
4490
- 'first-variant',
4491
- 'first-variant',
4492
- 'first-variant',
4493
- false,
4494
- 'third-variant',
4495
- 'third-variant',
4496
- 'third-variant',
4497
- false,
4498
- false,
4499
- 'first-variant',
4500
- 'first-variant',
4501
- false,
4502
- 'second-variant',
4503
- false,
4504
- false,
4505
- 'second-variant',
4506
- false,
4507
- 'third-variant',
4508
- 'first-variant',
4509
- 'second-variant',
4510
- 'fifth-variant',
4511
- 'first-variant',
4512
- 'first-variant',
4513
- false,
4514
- 'first-variant',
4515
- 'fifth-variant',
4516
- false,
4517
- false,
4518
- false,
4519
- 'third-variant',
4520
- 'first-variant',
4521
- 'first-variant',
4522
- 'second-variant',
4523
- 'fourth-variant',
4524
- 'first-variant',
4525
- 'second-variant',
4526
- 'first-variant',
4527
- false,
4528
- false,
4529
- false,
4530
- 'second-variant',
4531
- 'third-variant',
4532
- false,
4533
- false,
4534
- 'first-variant',
4535
- false,
4536
- false,
4537
- false,
4538
- false,
4539
- false,
4540
- false,
4541
- 'first-variant',
4542
- 'first-variant',
4543
- false,
4544
- 'third-variant',
4545
- false,
4546
- 'first-variant',
4547
- false,
4548
- 'third-variant',
4549
- 'third-variant',
4550
- 'first-variant',
4551
- 'first-variant',
4552
- false,
4553
- 'second-variant',
4554
- false,
4555
- 'second-variant',
4556
- 'first-variant',
4557
- false,
4558
- false,
4559
- false,
4560
- 'second-variant',
4561
- false,
4562
- 'third-variant',
4563
- false,
4564
- 'first-variant',
4565
- 'fifth-variant',
4566
- 'first-variant',
4567
- 'first-variant',
4568
- false,
4569
- false,
4570
- 'first-variant',
4571
- false,
4572
- false,
4573
- false,
4574
- 'first-variant',
4575
- 'fourth-variant',
4576
- 'first-variant',
4577
- 'first-variant',
4578
- 'first-variant',
4579
- 'fifth-variant',
4580
- false,
4581
- false,
4582
- false,
4583
- 'second-variant',
4584
- false,
4585
- false,
4586
- false,
4587
- 'first-variant',
4588
- 'first-variant',
4589
- false,
4590
- false,
4591
- 'first-variant',
4592
- 'first-variant',
4593
- 'second-variant',
4594
- 'first-variant',
4595
- 'first-variant',
4596
- 'first-variant',
4597
- 'first-variant',
4598
- 'first-variant',
4599
- 'third-variant',
4600
- 'first-variant',
4601
- false,
4602
- 'second-variant',
4603
- false,
4604
- false,
4605
- 'third-variant',
4606
- 'second-variant',
4607
- 'third-variant',
4608
- false,
4609
- 'first-variant',
4610
- 'third-variant',
4611
- 'second-variant',
4612
- 'first-variant',
4613
- 'third-variant',
4614
- false,
4615
- false,
4616
- 'first-variant',
4617
- 'first-variant',
4618
- false,
4619
- false,
4620
- false,
4621
- 'first-variant',
4622
- 'third-variant',
4623
- 'second-variant',
4624
- 'first-variant',
4625
- 'first-variant',
4626
- 'first-variant',
4627
- false,
4628
- 'third-variant',
4629
- 'second-variant',
4630
- 'third-variant',
4631
- false,
4632
- false,
4633
- 'third-variant',
4634
- 'first-variant',
4635
- false,
4636
- 'first-variant',
4637
- ]
4638
-
4639
- results.forEach(async (result, index) => {
4640
- const distinctId = `distinct_id_${index}`
4641
- const value = await posthog.getFeatureFlag('multivariate-flag', distinctId)
4642
- expect(value).toBe(result)
4643
- })
4644
- })
4645
- })
4646
-
4647
- describe('quota limiting', () => {
4648
- it('should clear local flags when quota limited', async () => {
4649
- const consoleSpy = jest.spyOn(console, 'warn')
4650
-
4651
- mockedFetch.mockImplementation(
4652
- apiImplementation({
4653
- localFlagsStatus: 402,
4654
- })
4655
- )
4656
-
4657
- const posthog = new PostHog('TEST_API_KEY', {
4658
- host: 'http://example.com',
4659
- personalApiKey: 'TEST_PERSONAL_API_KEY',
4660
- ...posthogImmediateResolveOptions,
4661
- })
4662
-
4663
- // Enable debug mode to see the messages
4664
- posthog.debug(true)
4665
-
4666
- // Force a reload and wait for it to complete
4667
- await posthog.reloadFeatureFlags()
4668
-
4669
- // locally evaluate the flags
4670
- const res = await posthog.getAllFlagsAndPayloads('distinct-id', { onlyEvaluateLocally: true })
4671
-
4672
- // expect the flags to be cleared and for the debug message to be logged
4673
- expect(res.featureFlags).toEqual({})
4674
- expect(res.featureFlagPayloads).toEqual({})
4675
- expect(consoleSpy).toHaveBeenCalledWith(
4676
- '[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all local flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts'
4677
- )
4678
-
4679
- consoleSpy.mockRestore()
4680
- })
4681
- })