posthog-node 4.10.1 → 4.11.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.
@@ -0,0 +1,293 @@
1
+ import { PostHog as PostHog, PostHogOptions } from '../src/posthog-node'
2
+ import fetch from '../src/fetch'
3
+ import { apiImplementation, apiImplementationV4 } from './test-utils'
4
+ import { waitForPromises } from 'posthog-core/test/test-utils/test-utils'
5
+ import { PostHogV4DecideResponse } from 'posthog-core/src/types'
6
+ jest.mock('../src/fetch')
7
+
8
+ jest.spyOn(console, 'debug').mockImplementation()
9
+
10
+ const mockedFetch = jest.mocked(fetch, true)
11
+
12
+ const posthogImmediateResolveOptions: PostHogOptions = {
13
+ fetchRetryCount: 0,
14
+ }
15
+
16
+ describe('decide v4', () => {
17
+ describe('getFeatureFlag v4', () => {
18
+ it('returns false if the flag is not found', async () => {
19
+ const decideResponse: PostHogV4DecideResponse = {
20
+ flags: {},
21
+ errorsWhileComputingFlags: false,
22
+ requestId: '0152a345-295f-4fba-adac-2e6ea9c91082',
23
+ }
24
+ mockedFetch.mockImplementation(apiImplementationV4(decideResponse))
25
+
26
+ const posthog = new PostHog('TEST_API_KEY', {
27
+ host: 'http://example.com',
28
+ ...posthogImmediateResolveOptions,
29
+ })
30
+ let capturedMessage: any
31
+ posthog.on('capture', (message) => {
32
+ capturedMessage = message
33
+ })
34
+
35
+ const result = await posthog.getFeatureFlag('non-existent-flag', 'some-distinct-id')
36
+
37
+ expect(result).toBe(false)
38
+ expect(mockedFetch).toHaveBeenCalledWith('http://example.com/decide/?v=4', expect.any(Object))
39
+
40
+ await waitForPromises()
41
+ expect(capturedMessage).toMatchObject({
42
+ distinct_id: 'some-distinct-id',
43
+ event: '$feature_flag_called',
44
+ library: posthog.getLibraryId(),
45
+ library_version: posthog.getLibraryVersion(),
46
+ properties: {
47
+ '$feature/non-existent-flag': false,
48
+ $feature_flag: 'non-existent-flag',
49
+ $feature_flag_response: false,
50
+ $feature_flag_request_id: '0152a345-295f-4fba-adac-2e6ea9c91082',
51
+ $groups: undefined,
52
+ $lib: posthog.getLibraryId(),
53
+ $lib_version: posthog.getLibraryVersion(),
54
+ locally_evaluated: false,
55
+ },
56
+ })
57
+ })
58
+
59
+ it.each([
60
+ {
61
+ key: 'variant-flag',
62
+ expectedResponse: 'variant-value',
63
+ expectedReason: 'Matched condition set 3',
64
+ expectedId: 2,
65
+ expectedVersion: 23,
66
+ },
67
+ {
68
+ key: 'boolean-flag',
69
+ expectedResponse: true,
70
+ expectedReason: 'Matched condition set 1',
71
+ expectedId: 1,
72
+ expectedVersion: 12,
73
+ },
74
+ {
75
+ key: 'non-matching-flag',
76
+ expectedResponse: false,
77
+ expectedReason: 'Did not match any condition',
78
+ expectedId: 3,
79
+ expectedVersion: 2,
80
+ },
81
+ ])(
82
+ 'captures a feature flag called event with extra metadata when the flag is found',
83
+ async ({ key, expectedResponse, expectedReason, expectedId, expectedVersion }) => {
84
+ const decideResponse: PostHogV4DecideResponse = {
85
+ flags: {
86
+ 'variant-flag': {
87
+ key: 'variant-flag',
88
+ enabled: true,
89
+ variant: 'variant-value',
90
+ reason: {
91
+ code: 'variant',
92
+ condition_index: 2,
93
+ description: 'Matched condition set 3',
94
+ },
95
+ metadata: {
96
+ id: 2,
97
+ version: 23,
98
+ payload: '{"key": "value"}',
99
+ description: 'description',
100
+ },
101
+ },
102
+ 'boolean-flag': {
103
+ key: 'boolean-flag',
104
+ enabled: true,
105
+ variant: undefined,
106
+ reason: {
107
+ code: 'boolean',
108
+ condition_index: 1,
109
+ description: 'Matched condition set 1',
110
+ },
111
+ metadata: {
112
+ id: 1,
113
+ version: 12,
114
+ payload: undefined,
115
+ description: 'description',
116
+ },
117
+ },
118
+ 'non-matching-flag': {
119
+ key: 'non-matching-flag',
120
+ enabled: false,
121
+ variant: undefined,
122
+ reason: {
123
+ code: 'boolean',
124
+ condition_index: 1,
125
+ description: 'Did not match any condition',
126
+ },
127
+ metadata: {
128
+ id: 3,
129
+ version: 2,
130
+ payload: undefined,
131
+ description: 'description',
132
+ },
133
+ },
134
+ },
135
+ errorsWhileComputingFlags: false,
136
+ requestId: '0152a345-295f-4fba-adac-2e6ea9c91082',
137
+ }
138
+ mockedFetch.mockImplementation(apiImplementationV4(decideResponse))
139
+
140
+ const posthog = new PostHog('TEST_API_KEY', {
141
+ host: 'http://example.com',
142
+ ...posthogImmediateResolveOptions,
143
+ })
144
+ let capturedMessage: any
145
+ posthog.on('capture', (message) => {
146
+ capturedMessage = message
147
+ })
148
+
149
+ const result = await posthog.getFeatureFlag(key, 'some-distinct-id')
150
+
151
+ expect(result).toBe(expectedResponse)
152
+ expect(mockedFetch).toHaveBeenCalledWith('http://example.com/decide/?v=4', expect.any(Object))
153
+
154
+ await waitForPromises()
155
+ expect(capturedMessage).toMatchObject({
156
+ distinct_id: 'some-distinct-id',
157
+ event: '$feature_flag_called',
158
+ library: posthog.getLibraryId(),
159
+ library_version: posthog.getLibraryVersion(),
160
+ properties: {
161
+ [`$feature/${key}`]: expectedResponse,
162
+ $feature_flag: key,
163
+ $feature_flag_response: expectedResponse,
164
+ $feature_flag_id: expectedId,
165
+ $feature_flag_version: expectedVersion,
166
+ $feature_flag_reason: expectedReason,
167
+ $feature_flag_request_id: '0152a345-295f-4fba-adac-2e6ea9c91082',
168
+ $groups: undefined,
169
+ $lib: posthog.getLibraryId(),
170
+ $lib_version: posthog.getLibraryVersion(),
171
+ locally_evaluated: false,
172
+ },
173
+ })
174
+ }
175
+ )
176
+
177
+ describe('getFeatureFlagPayload v4', () => {
178
+ it('returns payload', async () => {
179
+ mockedFetch.mockImplementation(
180
+ apiImplementationV4({
181
+ flags: {
182
+ 'flag-with-payload': {
183
+ key: 'flag-with-payload',
184
+ enabled: true,
185
+ variant: undefined,
186
+ reason: {
187
+ code: 'boolean',
188
+ condition_index: 1,
189
+ description: 'Matched condition set 2',
190
+ },
191
+ metadata: {
192
+ id: 1,
193
+ version: 12,
194
+ payload: '[0, 1, 2]',
195
+ description: 'description',
196
+ },
197
+ },
198
+ },
199
+ errorsWhileComputingFlags: false,
200
+ })
201
+ )
202
+
203
+ const posthog = new PostHog('TEST_API_KEY', {
204
+ host: 'http://example.com',
205
+ ...posthogImmediateResolveOptions,
206
+ })
207
+ let capturedMessage: any
208
+ posthog.on('capture', (message) => {
209
+ capturedMessage = message
210
+ })
211
+
212
+ const result = await posthog.getFeatureFlagPayload('flag-with-payload', 'some-distinct-id')
213
+
214
+ expect(result).toEqual([0, 1, 2])
215
+ expect(mockedFetch).toHaveBeenCalledWith('http://example.com/decide/?v=4', expect.any(Object))
216
+
217
+ await waitForPromises()
218
+ expect(capturedMessage).toBeUndefined()
219
+ })
220
+ })
221
+ })
222
+ })
223
+
224
+ describe('decide v3', () => {
225
+ describe('getFeatureFlag v3', () => {
226
+ it('returns false if the flag is not found', async () => {
227
+ mockedFetch.mockImplementation(apiImplementation({ decideFlags: {} }))
228
+
229
+ const posthog = new PostHog('TEST_API_KEY', {
230
+ host: 'http://example.com',
231
+ ...posthogImmediateResolveOptions,
232
+ })
233
+ let capturedMessage: any
234
+ posthog.on('capture', (message) => {
235
+ capturedMessage = message
236
+ })
237
+
238
+ const result = await posthog.getFeatureFlag('non-existent-flag', 'some-distinct-id')
239
+
240
+ expect(result).toBe(false)
241
+ expect(mockedFetch).toHaveBeenCalledWith('http://example.com/decide/?v=4', expect.any(Object))
242
+
243
+ await waitForPromises()
244
+ expect(capturedMessage).toMatchObject({
245
+ distinct_id: 'some-distinct-id',
246
+ event: '$feature_flag_called',
247
+ library: posthog.getLibraryId(),
248
+ library_version: posthog.getLibraryVersion(),
249
+ properties: {
250
+ '$feature/non-existent-flag': false,
251
+ $feature_flag: 'non-existent-flag',
252
+ $feature_flag_response: false,
253
+ $groups: undefined,
254
+ $lib: posthog.getLibraryId(),
255
+ $lib_version: posthog.getLibraryVersion(),
256
+ locally_evaluated: false,
257
+ },
258
+ })
259
+ })
260
+
261
+ describe('getFeatureFlagPayload v3', () => {
262
+ it('returns payload', async () => {
263
+ mockedFetch.mockImplementation(
264
+ apiImplementation({
265
+ decideFlags: {
266
+ 'flag-with-payload': true,
267
+ },
268
+ decideFlagPayloads: {
269
+ 'flag-with-payload': [0, 1, 2],
270
+ },
271
+ })
272
+ )
273
+
274
+ const posthog = new PostHog('TEST_API_KEY', {
275
+ host: 'http://example.com',
276
+ ...posthogImmediateResolveOptions,
277
+ })
278
+ let capturedMessage: any = undefined
279
+ posthog.on('capture', (message) => {
280
+ capturedMessage = message
281
+ })
282
+
283
+ const result = await posthog.getFeatureFlagPayload('flag-with-payload', 'some-distinct-id')
284
+
285
+ expect(result).toEqual([0, 1, 2])
286
+ expect(mockedFetch).toHaveBeenCalledWith('http://example.com/decide/?v=4', expect.any(Object))
287
+
288
+ await waitForPromises()
289
+ expect(capturedMessage).toBeUndefined()
290
+ })
291
+ })
292
+ })
293
+ })
@@ -347,7 +347,7 @@ describe('local evaluation', () => {
347
347
  })
348
348
  ).toEqual('decide-fallback-value')
349
349
  expect(mockedFetch).toHaveBeenCalledWith(
350
- 'http://example.com/decide/?v=3',
350
+ 'http://example.com/decide/?v=4',
351
351
  expect.objectContaining({
352
352
  body: JSON.stringify({
353
353
  token: 'TEST_API_KEY',
@@ -371,7 +371,7 @@ describe('local evaluation', () => {
371
371
  await posthog.getFeatureFlag('complex-flag', 'some-distinct-id', { personProperties: { doesnt_matter: '1' } })
372
372
  ).toEqual('decide-fallback-value')
373
373
  expect(mockedFetch).toHaveBeenCalledWith(
374
- 'http://example.com/decide/?v=3',
374
+ 'http://example.com/decide/?v=4',
375
375
  expect.objectContaining({
376
376
  body: JSON.stringify({
377
377
  token: 'TEST_API_KEY',
@@ -0,0 +1,71 @@
1
+ import { Lazy } from '../src/lazy'
2
+
3
+ describe('Lazy', () => {
4
+ it('should only call the factory once', async (): Promise<void> => {
5
+ let callCount = 0
6
+ const factory = async (): Promise<string> => {
7
+ callCount++
8
+ return 'value'
9
+ }
10
+
11
+ const lazy = new Lazy(factory)
12
+ expect(callCount).toBe(0)
13
+
14
+ const value1 = await lazy.getValue()
15
+ expect(value1).toBe('value')
16
+ expect(callCount).toBe(1)
17
+
18
+ const value2 = await lazy.getValue()
19
+ expect(value2).toBe('value')
20
+ expect(callCount).toBe(1)
21
+ })
22
+
23
+ it('should handle errors in the factory', async (): Promise<void> => {
24
+ const factory = async (): Promise<string> => {
25
+ throw new Error('Factory error')
26
+ }
27
+
28
+ const lazy = new Lazy(factory)
29
+ await expect(lazy.getValue()).rejects.toThrow('Factory error')
30
+ })
31
+
32
+ it('should handle undefined values', async (): Promise<void> => {
33
+ const factory = async (): Promise<undefined> => {
34
+ return undefined
35
+ }
36
+
37
+ const lazy = new Lazy(factory)
38
+ const value = await lazy.getValue()
39
+ expect(value).toBeUndefined()
40
+ })
41
+
42
+ it('should handle complex types', async (): Promise<void> => {
43
+ interface ComplexType {
44
+ id: number
45
+ name: string
46
+ }
47
+
48
+ const factory = async (): Promise<ComplexType> => {
49
+ return { id: 1, name: 'test' }
50
+ }
51
+
52
+ const lazy = new Lazy<ComplexType>(factory)
53
+ const value = await lazy.getValue()
54
+ expect(value).toEqual({ id: 1, name: 'test' })
55
+ })
56
+
57
+ it('should handle concurrent calls', async (): Promise<void> => {
58
+ let callCount = 0
59
+ const factory = async (): Promise<string> => {
60
+ callCount++
61
+ return 'value'
62
+ }
63
+
64
+ const lazy = new Lazy(factory)
65
+ const [value1, value2] = await Promise.all([lazy.getValue(), lazy.getValue()])
66
+
67
+ expect(value1).toBe('value')
68
+ expect(value2).toBe('value')
69
+ expect(callCount).toBe(1)
70
+ })
71
+ })
@@ -328,6 +328,17 @@ describe('PostHog Node.js', () => {
328
328
 
329
329
  await client.shutdown()
330
330
  })
331
+
332
+ it('should warn if capture is called with a string', () => {
333
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
334
+ posthog.debug(true)
335
+ // @ts-expect-error - Testing the warning when passing a string instead of an object
336
+ posthog.capture('test-event')
337
+ expect(warnSpy).toHaveBeenCalledWith(
338
+ 'Called capture() with a string as the first argument when an object was expected.'
339
+ )
340
+ warnSpy.mockRestore()
341
+ })
331
342
  })
332
343
 
333
344
  describe('shutdown', () => {
@@ -603,7 +614,7 @@ describe('PostHog Node.js', () => {
603
614
  )
604
615
  expect(mockedFetch).toHaveBeenCalledTimes(1)
605
616
  expect(mockedFetch).toHaveBeenCalledWith(
606
- 'http://example.com/decide/?v=3',
617
+ 'http://example.com/decide/?v=4',
607
618
  expect.objectContaining({ method: 'POST', body: expect.stringContaining('"geoip_disable":true') })
608
619
  )
609
620
  })
@@ -636,7 +647,7 @@ describe('PostHog Node.js', () => {
636
647
  await waitForPromises()
637
648
 
638
649
  expect(mockedFetch).toHaveBeenCalledWith(
639
- 'http://example.com/decide/?v=3',
650
+ 'http://example.com/decide/?v=4',
640
651
  expect.objectContaining({ method: 'POST' })
641
652
  )
642
653
 
@@ -645,7 +656,7 @@ describe('PostHog Node.js', () => {
645
656
  distinct_id: 'distinct_id',
646
657
  event: 'node test event',
647
658
  properties: expect.objectContaining({
648
- $active_feature_flags: ['feature-1', 'feature-2', 'feature-variant', 'feature-array'],
659
+ $active_feature_flags: ['feature-1', 'feature-2', 'feature-array', 'feature-variant'],
649
660
  '$feature/feature-1': true,
650
661
  '$feature/feature-2': true,
651
662
  '$feature/feature-array': true,
@@ -661,7 +672,7 @@ describe('PostHog Node.js', () => {
661
672
 
662
673
  expect(mockedFetch).not.toHaveBeenCalledWith(...anyLocalEvalCall)
663
674
  expect(mockedFetch).toHaveBeenCalledWith(
664
- 'http://example.com/decide/?v=3',
675
+ 'http://example.com/decide/?v=4',
665
676
  expect.objectContaining({ method: 'POST', body: expect.stringContaining('"geoip_disable":true') })
666
677
  )
667
678
  })
@@ -721,7 +732,7 @@ describe('PostHog Node.js', () => {
721
732
  expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
722
733
  // no decide call
723
734
  expect(mockedFetch).not.toHaveBeenCalledWith(
724
- 'http://example.com/decide/?v=3',
735
+ 'http://example.com/decide/?v=4',
725
736
  expect.objectContaining({ method: 'POST' })
726
737
  )
727
738
 
@@ -779,7 +790,7 @@ describe('PostHog Node.js', () => {
779
790
  expect(mockedFetch).toHaveBeenCalledWith(...anyLocalEvalCall)
780
791
  // no decide call
781
792
  expect(mockedFetch).not.toHaveBeenCalledWith(
782
- 'http://example.com/decide/?v=3',
793
+ 'http://example.com/decide/?v=4',
783
794
  expect.objectContaining({ method: 'POST' })
784
795
  )
785
796
 
@@ -827,12 +838,12 @@ describe('PostHog Node.js', () => {
827
838
  await waitForFlushTimer()
828
839
 
829
840
  expect(mockedFetch).toHaveBeenCalledWith(
830
- 'http://example.com/decide/?v=3',
841
+ 'http://example.com/decide/?v=4',
831
842
  expect.objectContaining({ method: 'POST', body: expect.not.stringContaining('geoip_disable') })
832
843
  )
833
844
 
834
845
  expect(getLastBatchEvents()?.[0].properties).toEqual({
835
- $active_feature_flags: ['feature-1', 'feature-2', 'feature-variant', 'feature-array'],
846
+ $active_feature_flags: ['feature-1', 'feature-2', 'feature-array', 'feature-variant'],
836
847
  '$feature/feature-1': true,
837
848
  '$feature/feature-2': true,
838
849
  '$feature/feature-array': true,
@@ -1080,7 +1091,7 @@ describe('PostHog Node.js', () => {
1080
1091
  ).resolves.toEqual(2)
1081
1092
  expect(mockedFetch).toHaveBeenCalledTimes(1)
1082
1093
  expect(mockedFetch).toHaveBeenCalledWith(
1083
- 'http://example.com/decide/?v=3',
1094
+ 'http://example.com/decide/?v=4',
1084
1095
  expect.objectContaining({ method: 'POST', body: expect.stringContaining('"geoip_disable":true') })
1085
1096
  )
1086
1097
  })
@@ -1122,7 +1133,7 @@ describe('PostHog Node.js', () => {
1122
1133
  ).resolves.toEqual([1])
1123
1134
  expect(mockedFetch).toHaveBeenCalledTimes(1)
1124
1135
  expect(mockedFetch).toHaveBeenCalledWith(
1125
- 'http://example.com/decide/?v=3',
1136
+ 'http://example.com/decide/?v=4',
1126
1137
  expect.objectContaining({ method: 'POST', body: expect.stringContaining('"geoip_disable":true') })
1127
1138
  )
1128
1139
  })
@@ -1142,7 +1153,7 @@ describe('PostHog Node.js', () => {
1142
1153
  ).resolves.toEqual(2)
1143
1154
  expect(mockedFetch).toHaveBeenCalledTimes(1)
1144
1155
  expect(mockedFetch).toHaveBeenCalledWith(
1145
- 'http://example.com/decide/?v=3',
1156
+ 'http://example.com/decide/?v=4',
1146
1157
  expect.objectContaining({ method: 'POST', body: expect.stringContaining('"geoip_disable":true') })
1147
1158
  )
1148
1159
 
@@ -1151,7 +1162,7 @@ describe('PostHog Node.js', () => {
1151
1162
  await expect(posthog.isFeatureEnabled('feature-variant', '123', { disableGeoip: false })).resolves.toEqual(true)
1152
1163
  expect(mockedFetch).toHaveBeenCalledTimes(1)
1153
1164
  expect(mockedFetch).toHaveBeenCalledWith(
1154
- 'http://example.com/decide/?v=3',
1165
+ 'http://example.com/decide/?v=4',
1155
1166
  expect.objectContaining({ method: 'POST', body: expect.not.stringContaining('geoip_disable') })
1156
1167
  )
1157
1168
  })
@@ -1165,7 +1176,7 @@ describe('PostHog Node.js', () => {
1165
1176
  jest.runOnlyPendingTimers()
1166
1177
 
1167
1178
  expect(mockedFetch).toHaveBeenCalledWith(
1168
- 'http://example.com/decide/?v=3',
1179
+ 'http://example.com/decide/?v=4',
1169
1180
  expect.objectContaining({
1170
1181
  body: JSON.stringify({
1171
1182
  token: 'TEST_API_KEY',
@@ -1195,7 +1206,7 @@ describe('PostHog Node.js', () => {
1195
1206
  jest.runOnlyPendingTimers()
1196
1207
 
1197
1208
  expect(mockedFetch).toHaveBeenCalledWith(
1198
- 'http://example.com/decide/?v=3',
1209
+ 'http://example.com/decide/?v=4',
1199
1210
  expect.objectContaining({
1200
1211
  body: JSON.stringify({
1201
1212
  token: 'TEST_API_KEY',
@@ -1226,7 +1237,7 @@ describe('PostHog Node.js', () => {
1226
1237
  jest.runOnlyPendingTimers()
1227
1238
 
1228
1239
  expect(mockedFetch).toHaveBeenCalledWith(
1229
- 'http://example.com/decide/?v=3',
1240
+ 'http://example.com/decide/?v=4',
1230
1241
  expect.objectContaining({
1231
1242
  body: JSON.stringify({
1232
1243
  token: 'TEST_API_KEY',
@@ -1250,7 +1261,7 @@ describe('PostHog Node.js', () => {
1250
1261
  jest.runOnlyPendingTimers()
1251
1262
 
1252
1263
  expect(mockedFetch).toHaveBeenCalledWith(
1253
- 'http://example.com/decide/?v=3',
1264
+ 'http://example.com/decide/?v=4',
1254
1265
  expect.objectContaining({
1255
1266
  body: JSON.stringify({
1256
1267
  token: 'TEST_API_KEY',
@@ -1270,7 +1281,7 @@ describe('PostHog Node.js', () => {
1270
1281
  jest.runOnlyPendingTimers()
1271
1282
 
1272
1283
  expect(mockedFetch).toHaveBeenCalledWith(
1273
- 'http://example.com/decide/?v=3',
1284
+ 'http://example.com/decide/?v=4',
1274
1285
  expect.objectContaining({
1275
1286
  body: JSON.stringify({
1276
1287
  token: 'TEST_API_KEY',
@@ -1292,7 +1303,7 @@ describe('PostHog Node.js', () => {
1292
1303
  jest.runOnlyPendingTimers()
1293
1304
 
1294
1305
  expect(mockedFetch).toHaveBeenCalledWith(
1295
- 'http://example.com/decide/?v=3',
1306
+ 'http://example.com/decide/?v=4',
1296
1307
  expect.objectContaining({
1297
1308
  body: JSON.stringify({
1298
1309
  token: 'TEST_API_KEY',
@@ -1308,5 +1319,25 @@ describe('PostHog Node.js', () => {
1308
1319
  })
1309
1320
  )
1310
1321
  })
1322
+
1323
+ it('should log error when decide response has errors', async () => {
1324
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
1325
+
1326
+ mockedFetch.mockImplementation(
1327
+ apiImplementation({
1328
+ decideFlags: { 'feature-1': true },
1329
+ decideFlagPayloads: {},
1330
+ errorsWhileComputingFlags: true,
1331
+ })
1332
+ )
1333
+
1334
+ await posthog.getFeatureFlag('feature-1', '123')
1335
+
1336
+ expect(errorSpy).toHaveBeenCalledWith(
1337
+ '[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices'
1338
+ )
1339
+
1340
+ errorSpy.mockRestore()
1341
+ })
1311
1342
  })
1312
1343
  })
@@ -1,15 +1,40 @@
1
+ import { PostHogV4DecideResponse } from 'posthog-core/src/types'
2
+
3
+ export const apiImplementationV4 = (decideResponse?: PostHogV4DecideResponse) => {
4
+ return (url: any): Promise<any> => {
5
+ if ((url as any).includes('/decide/?v=4')) {
6
+ return Promise.resolve({
7
+ status: 200,
8
+ text: () => Promise.resolve('ok'),
9
+ json: () => Promise.resolve(decideResponse),
10
+ }) as any
11
+ }
12
+
13
+ return Promise.resolve({
14
+ status: 400,
15
+ text: () => Promise.resolve('ok'),
16
+ json: () =>
17
+ Promise.resolve({
18
+ status: 'ok',
19
+ }),
20
+ }) as any
21
+ }
22
+ }
23
+
1
24
  export const apiImplementation = ({
2
25
  localFlags,
3
26
  decideFlags,
4
27
  decideFlagPayloads,
5
28
  decideStatus = 200,
6
29
  localFlagsStatus = 200,
30
+ errorsWhileComputingFlags = false,
7
31
  }: {
8
32
  localFlags?: any
9
33
  decideFlags?: any
10
34
  decideFlagPayloads?: any
11
35
  decideStatus?: number
12
36
  localFlagsStatus?: number
37
+ errorsWhileComputingFlags?: boolean
13
38
  }) => {
14
39
  return (url: any): Promise<any> => {
15
40
  if ((url as any).includes('/decide/')) {
@@ -25,6 +50,7 @@ export const apiImplementation = ({
25
50
  featureFlagPayloads: Object.fromEntries(
26
51
  Object.entries(decideFlagPayloads || {}).map(([k, v]) => [k, JSON.stringify(v)])
27
52
  ),
53
+ errorsWhileComputingFlags,
28
54
  })
29
55
  }
30
56
  },
@@ -65,4 +91,4 @@ export const anyLocalEvalCall = [
65
91
  'http://example.com/api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts',
66
92
  expect.any(Object),
67
93
  ]
68
- export const anyDecideCall = ['http://example.com/decide/?v=3', expect.any(Object)]
94
+ export const anyDecideCall = ['http://example.com/decide/?v=4', expect.any(Object)]