posthog-node 3.6.3 → 4.0.0-beta.2

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.
@@ -4,6 +4,7 @@ import { PostHog as PostHog, PostHogOptions } from '../src/posthog-node'
4
4
  import { matchProperty, InconclusiveMatchError, relativeDateParseForFeatureFlagMatching } from '../src/feature-flags'
5
5
  jest.mock('../src/fetch')
6
6
  import fetch from '../src/fetch'
7
+ import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './test-utils'
7
8
 
8
9
  jest.spyOn(console, 'debug').mockImplementation()
9
10
 
@@ -13,71 +14,6 @@ const posthogImmediateResolveOptions: PostHogOptions = {
13
14
  fetchRetryCount: 0,
14
15
  }
15
16
 
16
- export const apiImplementation = ({
17
- localFlags,
18
- decideFlags,
19
- decideFlagPayloads,
20
- decideStatus = 200,
21
- }: {
22
- localFlags?: any
23
- decideFlags?: any
24
- decideFlagPayloads?: any
25
- decideStatus?: number
26
- }) => {
27
- return (url: any): Promise<any> => {
28
- if ((url as any).includes('/decide/')) {
29
- return Promise.resolve({
30
- status: decideStatus,
31
- text: () => Promise.resolve('ok'),
32
- json: () => {
33
- if (decideStatus !== 200) {
34
- return Promise.resolve(decideFlags)
35
- } else {
36
- return Promise.resolve({
37
- featureFlags: decideFlags,
38
- featureFlagPayloads: decideFlagPayloads,
39
- })
40
- }
41
- },
42
- }) as any
43
- }
44
-
45
- if ((url as any).includes('api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts')) {
46
- return Promise.resolve({
47
- status: 200,
48
- text: () => Promise.resolve('ok'),
49
- json: () => Promise.resolve(localFlags),
50
- }) as any
51
- }
52
-
53
- if ((url as any).includes('batch/')) {
54
- return Promise.resolve({
55
- status: 200,
56
- text: () => Promise.resolve('ok'),
57
- json: () =>
58
- Promise.resolve({
59
- status: 'ok',
60
- }),
61
- }) as any
62
- }
63
-
64
- return Promise.resolve({
65
- status: 400,
66
- text: () => Promise.resolve('ok'),
67
- json: () =>
68
- Promise.resolve({
69
- status: 'ok',
70
- }),
71
- }) as any
72
- }
73
- }
74
-
75
- export const anyLocalEvalCall = [
76
- 'http://example.com/api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts',
77
- expect.any(Object),
78
- ]
79
- export const anyDecideCall = ['http://example.com/decide/?v=3', expect.any(Object)]
80
-
81
17
  describe('local evaluation', () => {
82
18
  let posthog: PostHog
83
19
 
@@ -85,7 +21,7 @@ describe('local evaluation', () => {
85
21
 
86
22
  afterEach(async () => {
87
23
  // ensure clean shutdown & no test interdependencies
88
- await posthog.shutdownAsync()
24
+ await posthog.shutdown()
89
25
  })
90
26
 
91
27
  it('evaluates person properties', async () => {
@@ -614,7 +550,7 @@ describe('local evaluation', () => {
614
550
  expect(await posthog.getFeatureFlag('beta-feature2', 'some-distinct-id')).toEqual(undefined)
615
551
  expect(await posthog.isFeatureEnabled('beta-feature2', 'some-distinct-id')).toEqual(undefined)
616
552
  expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
617
- await posthog.shutdownAsync()
553
+ await posthog.shutdown()
618
554
  expect(err).toHaveProperty('name', 'PostHogFetchHttpError')
619
555
  })
620
556
 
@@ -2051,80 +1987,74 @@ describe('match properties', () => {
2051
1987
  expect(matchProperty(property_d, { key: '2022-04-05 11:34:13 +00:00' })).toBe(false)
2052
1988
  })
2053
1989
 
2054
- it('with relative date operators', () => {
1990
+ it.each([
1991
+ ['is_date_before', '-6h', '2022-03-01', true],
1992
+ ['is_date_before', '-6h', '2022-04-30', true],
1993
+ // :TRICKY: MonthIndex is 0 indexed, so 3 is actually the 4th month, April.
1994
+ ['is_date_before', '-6h', new Date(Date.UTC(2022, 3, 30, 1, 2, 3)), true],
1995
+ // false because date comparison, instead of datetime, so reduces to same date
1996
+ ['is_date_before', '-6h', new Date('2022-04-30T01:02:03+02:00'), true], // europe/madrid
1997
+ ['is_date_before', '-6h', new Date('2022-04-30T20:02:03+02:00'), false], // europe/madrid
1998
+ ['is_date_before', '-6h', new Date('2022-04-30T19:59:03+02:00'), true], // europe/madrid
1999
+ ['is_date_before', '-6h', new Date('2022-04-30'), true],
2000
+ ['is_date_before', '-6h', '2022-05-30', false],
2001
+ // is date after
2002
+ ['is_date_after', '1h', '2022-05-02', true],
2003
+ ['is_date_after', '1h', '2022-05-30', true],
2004
+ ['is_date_after', '1h', new Date(2022, 4, 30), true],
2005
+ ['is_date_after', '1h', new Date('2022-05-30'), true],
2006
+ ['is_date_after', '1h', '2022-04-30', false],
2007
+ // # Try all possible relative dates
2008
+ ['is_date_before', '1h', '2022-05-01 00:00:00', false],
2009
+ ['is_date_before', '1h', '2022-04-30 22:00:00', true],
2010
+ ['is_date_before', '-1d', '2022-04-29 23:59:00 GMT', true],
2011
+ ['is_date_before', '-1d', '2022-04-30 00:00:01 GMT', false],
2012
+ ['is_date_before', '1w', '2022-04-23 00:00:00 GMT', true],
2013
+ ['is_date_before', '1w', '2022-04-24 00:00:00 GMT', false],
2014
+ ['is_date_before', '1w', '2022-04-24 00:00:01 GMT', false],
2015
+ ['is_date_before', '1m', '2022-03-01 00:00:00 GMT', true],
2016
+ ['is_date_before', '1m', '2022-04-01 00:00:00 GMT', false],
2017
+ ['is_date_before', '1m', '2022-04-05 00:00:01 GMT', false],
2018
+
2019
+ ['is_date_before', '-1y', '2021-04-28 00:00:00 GMT', true],
2020
+ ['is_date_before', '-1y', '2021-05-01 00:00:01 GMT', false],
2021
+
2022
+ ['is_date_after', '122h', '2022-05-01 00:00:00 GMT', true],
2023
+ ['is_date_after', '122h', '2022-04-23 01:00:00 GMT', false],
2024
+
2025
+ ['is_date_after', '2d', '2022-05-01 00:00:00 GMT', true],
2026
+ ['is_date_after', '2d', '2022-04-29 00:00:01 GMT', true],
2027
+ ['is_date_after', '2d', '2022-04-29 00:00:00 GMT', false],
2028
+
2029
+ ['is_date_after', '02w', '2022-05-01 00:00:00 GMT', true],
2030
+ ['is_date_after', '02w', '2022-04-16 00:00:00 GMT', false],
2031
+
2032
+ ['is_date_after', '-1m', '2022-04-01 00:00:01 GMT', true],
2033
+ ['is_date_after', '-1m', '2022-04-01 00:00:00 GMT', false],
2034
+
2035
+ ['is_date_after', '1y', '2022-05-01 00:00:00 GMT', true],
2036
+ ['is_date_after', '1y', '2021-05-01 00:00:01 GMT', true],
2037
+ ['is_date_after', '1y', '2021-05-01 00:00:00 GMT', false],
2038
+ ['is_date_after', '1y', '2021-04-30 00:00:00 GMT', false],
2039
+ ['is_date_after', '1y', '2021-03-01 12:13:00 GMT', false],
2040
+ ])('with relative date operators: %s, %s, %s', (operator, value, date, expectation) => {
2055
2041
  jest.setSystemTime(new Date('2022-05-01'))
2042
+ expect(matchProperty({ key: 'key', value, operator }, { key: date })).toBe(expectation)
2056
2043
 
2057
- const property_a = { key: 'key', value: '-6h', operator: 'is_date_before' }
2058
- expect(matchProperty(property_a, { key: '2022-03-01' })).toBe(true)
2059
- expect(matchProperty(property_a, { key: '2022-04-30' })).toBe(true)
2044
+ return
2045
+ })
2060
2046
 
2061
- // :TRICKY: MonthIndex is 0 indexed, so 3 is actually the 4th month, April.
2062
- expect(matchProperty(property_a, { key: new Date(Date.UTC(2022, 3, 30, 1, 2, 3)) })).toBe(true)
2063
- // false because date comparison, instead of datetime, so reduces to same date
2064
- expect(matchProperty(property_a, { key: new Date(2022, 3, 30, 19, 2, 3) })).toBe(false)
2065
- expect(matchProperty(property_a, { key: new Date('2022-04-30T01:02:03+02:00') })).toBe(true) // europe/madrid
2066
- expect(matchProperty(property_a, { key: new Date('2022-04-30T20:02:03+02:00') })).toBe(false) // europe/madrid
2067
- expect(matchProperty(property_a, { key: new Date('2022-04-30T19:59:03+02:00') })).toBe(true) // europe/madrid
2068
- expect(matchProperty(property_a, { key: new Date('2022-04-30') })).toBe(true)
2069
- expect(matchProperty(property_a, { key: '2022-05-30' })).toBe(false)
2047
+ it('with relative date operators handles invalid keys', () => {
2048
+ jest.setSystemTime(new Date('2022-05-01'))
2070
2049
 
2071
2050
  // # can't be an invalid string
2072
- expect(() => matchProperty(property_a, { key: 'abcdef' })).toThrow(InconclusiveMatchError)
2051
+ expect(() => matchProperty({ key: 'key', value: '1d', operator: 'is_date_before' }, { key: 'abcdef' })).toThrow(
2052
+ InconclusiveMatchError
2053
+ )
2073
2054
  // however js understands numbers as date offsets from utc epoch
2074
- expect(() => matchProperty(property_a, { key: 1 })).not.toThrow(InconclusiveMatchError)
2075
-
2076
- const property_b = { key: 'key', value: '1h', operator: 'is_date_after' }
2077
- expect(matchProperty(property_b, { key: '2022-05-02' })).toBe(true)
2078
- expect(matchProperty(property_b, { key: '2022-05-30' })).toBe(true)
2079
- expect(matchProperty(property_b, { key: new Date(2022, 4, 30) })).toBe(true)
2080
- expect(matchProperty(property_b, { key: new Date('2022-05-30') })).toBe(true)
2081
- expect(matchProperty(property_b, { key: '2022-04-30' })).toBe(false)
2082
-
2083
- // # Try all possible relative dates
2084
- const property_e = { key: 'key', value: '1h', operator: 'is_date_before' }
2085
- expect(matchProperty(property_e, { key: '2022-05-01 00:00:00' })).toBe(false)
2086
- expect(matchProperty(property_e, { key: '2022-04-30 22:00:00' })).toBe(true)
2087
-
2088
- const property_f = { key: 'key', value: '-1d', operator: 'is_date_before' }
2089
- expect(matchProperty(property_f, { key: '2022-04-29 23:59:00 GMT' })).toBe(true)
2090
- expect(matchProperty(property_f, { key: '2022-04-30 00:00:01 GMT' })).toBe(false)
2091
-
2092
- const property_g = { key: 'key', value: '1w', operator: 'is_date_before' }
2093
- expect(matchProperty(property_g, { key: '2022-04-23 00:00:00 GMT' })).toBe(true)
2094
- expect(matchProperty(property_g, { key: '2022-04-24 00:00:00 GMT' })).toBe(false)
2095
- expect(matchProperty(property_g, { key: '2022-04-24 00:00:01 GMT' })).toBe(false)
2096
-
2097
- const property_h = { key: 'key', value: '1m', operator: 'is_date_before' }
2098
- expect(matchProperty(property_h, { key: '2022-03-01 00:00:00 GMT' })).toBe(true)
2099
- expect(matchProperty(property_h, { key: '2022-04-05 00:00:00 GMT' })).toBe(false)
2100
-
2101
- const property_i = { key: 'key', value: '-1y', operator: 'is_date_before' }
2102
- expect(matchProperty(property_i, { key: '2021-04-28 00:00:00 GMT' })).toBe(true)
2103
- expect(matchProperty(property_i, { key: '2021-05-01 00:00:01 GMT' })).toBe(false)
2104
-
2105
- const property_j = { key: 'key', value: '122h', operator: 'is_date_after' }
2106
- expect(matchProperty(property_j, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
2107
- expect(matchProperty(property_j, { key: '2022-04-23 01:00:00 GMT' })).toBe(false)
2108
-
2109
- const property_k = { key: 'key', value: '2d', operator: 'is_date_after' }
2110
- expect(matchProperty(property_k, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
2111
- expect(matchProperty(property_k, { key: '2022-04-29 00:00:01 GMT' })).toBe(true)
2112
- expect(matchProperty(property_k, { key: '2022-04-29 00:00:00 GMT' })).toBe(false)
2113
-
2114
- const property_l = { key: 'key', value: '02w', operator: 'is_date_after' }
2115
- expect(matchProperty(property_l, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
2116
- expect(matchProperty(property_l, { key: '2022-04-16 00:00:00 GMT' })).toBe(false)
2117
-
2118
- const property_m = { key: 'key', value: '-1m', operator: 'is_date_after' }
2119
- expect(matchProperty(property_m, { key: '2022-04-01 00:00:01 GMT' })).toBe(true)
2120
- expect(matchProperty(property_m, { key: '2022-04-01 00:00:00 GMT' })).toBe(false)
2121
-
2122
- const property_n = { key: 'key', value: '1y', operator: 'is_date_after' }
2123
- expect(matchProperty(property_n, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
2124
- expect(matchProperty(property_n, { key: '2021-05-01 00:00:01 GMT' })).toBe(true)
2125
- expect(matchProperty(property_n, { key: '2021-05-01 00:00:00 GMT' })).toBe(false)
2126
- expect(matchProperty(property_n, { key: '2021-04-30 00:00:00 GMT' })).toBe(false)
2127
- expect(matchProperty(property_n, { key: '2021-03-01 12:13:00 GMT' })).toBe(false)
2055
+ expect(() => matchProperty({ key: 'key', value: '1d', operator: 'is_date_before' }, { key: 1 })).not.toThrow(
2056
+ InconclusiveMatchError
2057
+ )
2128
2058
  })
2129
2059
 
2130
2060
  it('null or undefined property value', () => {
@@ -1,8 +1,7 @@
1
- // import { PostHog } from '../'
2
1
  import { PostHog as PostHog } from '../src/posthog-node'
3
2
  jest.mock('../src/fetch')
4
3
  import fetch from '../src/fetch'
5
- import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './feature-flags.spec'
4
+ import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './test-utils'
6
5
  import { waitForPromises, wait } from '../../posthog-core/test/test-utils/test-utils'
7
6
  import { randomUUID } from 'crypto'
8
7
 
@@ -10,6 +9,14 @@ jest.mock('../package.json', () => ({ version: '1.2.3' }))
10
9
 
11
10
  const mockedFetch = jest.mocked(fetch, true)
12
11
 
12
+ const waitForFlushTimer = async (): Promise<void> => {
13
+ await waitForPromises()
14
+ // To trigger the flush via the timer
15
+ jest.runOnlyPendingTimers()
16
+ // Then wait for the flush promise
17
+ await waitForPromises()
18
+ }
19
+
13
20
  const getLastBatchEvents = (): any[] | undefined => {
14
21
  expect(mockedFetch).toHaveBeenCalledWith('http://example.com/batch/', expect.objectContaining({ method: 'POST' }))
15
22
 
@@ -43,8 +50,17 @@ describe('PostHog Node.js', () => {
43
50
  })
44
51
 
45
52
  afterEach(async () => {
53
+ mockedFetch.mockResolvedValue({
54
+ status: 200,
55
+ text: () => Promise.resolve('ok'),
56
+ json: () =>
57
+ Promise.resolve({
58
+ status: 'ok',
59
+ }),
60
+ } as any)
61
+
46
62
  // ensure clean shutdown & no test interdependencies
47
- await posthog.shutdownAsync()
63
+ await posthog.shutdown()
48
64
  })
49
65
 
50
66
  describe('core methods', () => {
@@ -52,8 +68,7 @@ describe('PostHog Node.js', () => {
52
68
  expect(mockedFetch).toHaveBeenCalledTimes(0)
53
69
  posthog.capture({ distinctId: '123', event: 'test-event', properties: { foo: 'bar' }, groups: { org: 123 } })
54
70
 
55
- await waitForPromises()
56
- jest.runOnlyPendingTimers()
71
+ await waitForFlushTimer()
57
72
 
58
73
  const batchEvents = getLastBatchEvents()
59
74
  expect(batchEvents).toEqual([
@@ -80,8 +95,7 @@ describe('PostHog Node.js', () => {
80
95
  expect(mockedFetch).toHaveBeenCalledTimes(0)
81
96
  posthog.capture({ distinctId: '123', event: 'test-event', properties: { foo: 'bar' }, groups: { org: 123 } })
82
97
 
83
- await waitForPromises()
84
- jest.runOnlyPendingTimers()
98
+ await waitForFlushTimer()
85
99
  expect(getLastBatchEvents()?.[0]).toEqual(
86
100
  expect.objectContaining({
87
101
  distinct_id: '123',
@@ -103,8 +117,7 @@ describe('PostHog Node.js', () => {
103
117
  groups: { other_group: 'x' },
104
118
  })
105
119
 
106
- await waitForPromises()
107
- jest.runOnlyPendingTimers()
120
+ await waitForFlushTimer()
108
121
  expect(getLastBatchEvents()?.[0]).toEqual(
109
122
  expect.objectContaining({
110
123
  distinct_id: '123',
@@ -124,6 +137,8 @@ describe('PostHog Node.js', () => {
124
137
  expect(mockedFetch).toHaveBeenCalledTimes(0)
125
138
  posthog.identify({ distinctId: '123', properties: { foo: 'bar' } })
126
139
  jest.runOnlyPendingTimers()
140
+ await waitForPromises()
141
+
127
142
  const batchEvents = getLastBatchEvents()
128
143
  expect(batchEvents).toMatchObject([
129
144
  {
@@ -143,6 +158,7 @@ describe('PostHog Node.js', () => {
143
158
  expect(mockedFetch).toHaveBeenCalledTimes(0)
144
159
  posthog.identify({ distinctId: '123', properties: { foo: 'bar', $set: { foo: 'other' } } })
145
160
  jest.runOnlyPendingTimers()
161
+ await waitForPromises()
146
162
  const batchEvents = getLastBatchEvents()
147
163
  expect(batchEvents).toMatchObject([
148
164
  {
@@ -162,6 +178,7 @@ describe('PostHog Node.js', () => {
162
178
  expect(mockedFetch).toHaveBeenCalledTimes(0)
163
179
  posthog.alias({ distinctId: '123', alias: '1234' })
164
180
  jest.runOnlyPendingTimers()
181
+ await waitForPromises()
165
182
  const batchEvents = getLastBatchEvents()
166
183
  expect(batchEvents).toMatchObject([
167
184
  {
@@ -179,8 +196,7 @@ describe('PostHog Node.js', () => {
179
196
  it('should allow overriding timestamp', async () => {
180
197
  expect(mockedFetch).toHaveBeenCalledTimes(0)
181
198
  posthog.capture({ event: 'custom-time', distinctId: '123', timestamp: new Date('2021-02-03') })
182
- await waitForPromises()
183
- jest.runOnlyPendingTimers()
199
+ await waitForFlushTimer()
184
200
  const batchEvents = getLastBatchEvents()
185
201
  expect(batchEvents).toMatchObject([
186
202
  {
@@ -196,8 +212,7 @@ describe('PostHog Node.js', () => {
196
212
  expect(mockedFetch).toHaveBeenCalledTimes(0)
197
213
  const uuid = randomUUID()
198
214
  posthog.capture({ event: 'custom-time', distinctId: '123', uuid })
199
- await waitForPromises()
200
- jest.runOnlyPendingTimers()
215
+ await waitForFlushTimer()
201
216
  const batchEvents = getLastBatchEvents()
202
217
  expect(batchEvents).toMatchObject([
203
218
  {
@@ -219,8 +234,7 @@ describe('PostHog Node.js', () => {
219
234
  disableGeoip: false,
220
235
  })
221
236
 
222
- await waitForPromises()
223
- jest.runOnlyPendingTimers()
237
+ await waitForFlushTimer()
224
238
  const batchEvents = getLastBatchEvents()
225
239
  expect(batchEvents?.[0].properties).toEqual({
226
240
  $groups: { org: 123 },
@@ -238,8 +252,7 @@ describe('PostHog Node.js', () => {
238
252
  })
239
253
  client.capture({ distinctId: '123', event: 'test-event', properties: { foo: 'bar' }, groups: { org: 123 } })
240
254
 
241
- await waitForPromises()
242
- jest.runOnlyPendingTimers()
255
+ await waitForFlushTimer()
243
256
 
244
257
  let batchEvents = getLastBatchEvents()
245
258
  expect(batchEvents?.[0].properties).toEqual({
@@ -257,10 +270,9 @@ describe('PostHog Node.js', () => {
257
270
  disableGeoip: true,
258
271
  })
259
272
 
260
- await waitForPromises()
261
- jest.runOnlyPendingTimers()
273
+ await waitForFlushTimer()
274
+
262
275
  batchEvents = getLastBatchEvents()
263
- console.warn(batchEvents)
264
276
  expect(batchEvents?.[0].properties).toEqual({
265
277
  $groups: { org: 123 },
266
278
  foo: 'bar',
@@ -277,8 +289,9 @@ describe('PostHog Node.js', () => {
277
289
  disableGeoip: false,
278
290
  })
279
291
 
292
+ await waitForFlushTimer()
280
293
  await waitForPromises()
281
- jest.runOnlyPendingTimers()
294
+
282
295
  batchEvents = getLastBatchEvents()
283
296
  expect(batchEvents?.[0].properties).toEqual({
284
297
  $groups: { org: 123 },
@@ -287,16 +300,19 @@ describe('PostHog Node.js', () => {
287
300
  $lib_version: '1.2.3',
288
301
  })
289
302
 
290
- await client.shutdownAsync()
303
+ await client.shutdown()
291
304
  })
292
305
  })
293
306
 
294
307
  describe('shutdown', () => {
308
+ let warnSpy: jest.SpyInstance, logSpy: jest.SpyInstance
295
309
  beforeEach(() => {
296
- // a serverless posthog configuration
297
- posthog = new PostHog('TEST_API_KEY', {
298
- host: 'http://example.com',
299
- fetchRetryCount: 0,
310
+ const actualLog = console.log
311
+ warnSpy = jest.spyOn(console, 'warn').mockImplementation((...args) => {
312
+ actualLog('spied warn:', ...args)
313
+ })
314
+ logSpy = jest.spyOn(console, 'log').mockImplementation((...args) => {
315
+ actualLog('spied log:', ...args)
300
316
  })
301
317
 
302
318
  mockedFetch.mockImplementation(async () => {
@@ -312,66 +328,69 @@ describe('PostHog Node.js', () => {
312
328
  }),
313
329
  } as any)
314
330
  })
331
+
332
+ jest.useRealTimers()
315
333
  })
316
334
 
317
335
  afterEach(() => {
318
- posthog.debug(false)
319
336
  jest.useFakeTimers()
320
337
  })
321
338
 
322
339
  it('should shutdown cleanly', async () => {
323
- posthog = new PostHog('TEST_API_KEY', {
340
+ const ph = new PostHog('TEST_API_KEY', {
324
341
  host: 'http://example.com',
325
342
  fetchRetryCount: 0,
326
343
  flushAt: 1,
327
344
  })
345
+ ph.debug(true)
328
346
 
329
- const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
330
- jest.useRealTimers()
331
347
  // using debug mode to check console.log output
332
348
  // which tells us when the flush is complete
333
- posthog.debug(true)
334
- for (let i = 0; i < 10; i++) {
335
- posthog.capture({ event: 'test-event', distinctId: '123' })
336
- // requests come 100ms apart
337
- await wait(100)
338
- }
339
349
 
340
- // 10 capture calls to debug log
341
- // 6 flush calls to debug log
342
- expect(logSpy).toHaveBeenCalledTimes(16)
343
- expect(10).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('capture')).length)
344
- expect(6).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('flush')).length)
350
+ ph.capture({ event: 'test-event', distinctId: '123' })
351
+ await wait(100)
352
+ expect(logSpy).toHaveBeenCalledTimes(1)
353
+
354
+ ph.capture({ event: 'test-event', distinctId: '123' })
355
+ ph.capture({ event: 'test-event', distinctId: '123' })
356
+ await wait(100)
357
+ expect(logSpy).toHaveBeenCalledTimes(3)
358
+ await wait(400) // The flush will resolve in this time
359
+ ph.capture({ event: 'test-event', distinctId: '123' })
360
+ ph.capture({ event: 'test-event', distinctId: '123' })
361
+ await wait(100)
362
+ expect(logSpy).toHaveBeenCalledTimes(6) // 5 captures and 1 flush
363
+ expect(5).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('capture')).length)
364
+ expect(1).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('flush')).length)
345
365
 
346
366
  logSpy.mockClear()
367
+ expect(logSpy).toHaveBeenCalledTimes(0)
347
368
 
348
- await posthog.shutdownAsync()
349
- // remaining 4 flush calls to debug log
350
- // happen during shutdown
351
- expect(4).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('flush')).length)
352
- jest.useFakeTimers()
369
+ console.warn('YOO!!')
370
+
371
+ await ph.shutdown()
372
+ // 1 final flush for the events that were queued during shutdown
373
+ expect(1).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('flush')).length)
353
374
  logSpy.mockRestore()
375
+ warnSpy.mockRestore()
354
376
  })
355
377
 
356
378
  it('should shutdown cleanly with pending capture flag promises', async () => {
357
- posthog = new PostHog('TEST_API_KEY', {
379
+ const ph = new PostHog('TEST_API_KEY', {
358
380
  host: 'http://example.com',
359
381
  fetchRetryCount: 0,
360
382
  flushAt: 4,
361
383
  })
362
-
363
- const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
364
- jest.useRealTimers()
365
- posthog.debug(true)
384
+ ph.debug(true)
366
385
 
367
386
  for (let i = 0; i < 10; i++) {
368
- posthog.capture({ event: 'test-event', distinctId: `${i}`, sendFeatureFlags: true })
387
+ ph.capture({ event: 'test-event', distinctId: `${i}`, sendFeatureFlags: true })
369
388
  }
370
389
 
371
- await posthog.shutdownAsync()
390
+ await ph.shutdown()
372
391
  // all capture calls happen during shutdown
373
392
  const batchEvents = getLastBatchEvents()
374
- expect(batchEvents?.length).toEqual(2)
393
+ expect(batchEvents?.length).toEqual(6)
375
394
  expect(batchEvents?.[batchEvents?.length - 1]).toMatchObject({
376
395
  // last event in batch
377
396
  distinct_id: '9',
@@ -387,8 +406,8 @@ describe('PostHog Node.js', () => {
387
406
  type: 'capture',
388
407
  })
389
408
  expect(10).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('capture')).length)
390
- expect(3).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('flush')).length)
391
- jest.useFakeTimers()
409
+ // 1 for the captured events, 1 for the final flush of feature flag called events
410
+ expect(2).toEqual(logSpy.mock.calls.filter((call) => call[1].includes('flush')).length)
392
411
  logSpy.mockRestore()
393
412
  })
394
413
  })
@@ -397,7 +416,7 @@ describe('PostHog Node.js', () => {
397
416
  it('should identify group with unique id', async () => {
398
417
  posthog.groupIdentify({ groupType: 'posthog', groupKey: 'team-1', properties: { analytics: true } })
399
418
  jest.runOnlyPendingTimers()
400
- await posthog.flushAsync()
419
+ await posthog.flush()
401
420
  const batchEvents = getLastBatchEvents()
402
421
  expect(batchEvents).toMatchObject([
403
422
  {
@@ -422,7 +441,7 @@ describe('PostHog Node.js', () => {
422
441
  distinctId: '123',
423
442
  })
424
443
  jest.runOnlyPendingTimers()
425
- await posthog.flushAsync()
444
+ await posthog.flush()
426
445
  const batchEvents = getLastBatchEvents()
427
446
  expect(batchEvents).toMatchObject([
428
447
  {
@@ -656,7 +675,7 @@ describe('PostHog Node.js', () => {
656
675
  false
657
676
  )
658
677
 
659
- await posthog.shutdownAsync()
678
+ await posthog.shutdown()
660
679
  })
661
680
 
662
681
  it('doesnt add flag properties when locally evaluated flags are empty', async () => {
@@ -729,8 +748,7 @@ describe('PostHog Node.js', () => {
729
748
  disableGeoip: false,
730
749
  })
731
750
 
732
- await waitForPromises()
733
- jest.runOnlyPendingTimers()
751
+ await waitForFlushTimer()
734
752
 
735
753
  expect(mockedFetch).toHaveBeenCalledWith(
736
754
  'http://example.com/decide/?v=3',
@@ -858,7 +876,7 @@ describe('PostHog Node.js', () => {
858
876
  // TRICKY: There's now an extra step before events are queued, so need to wait for that to resolve
859
877
  jest.runOnlyPendingTimers()
860
878
  await waitForPromises()
861
- await posthog.flushAsync()
879
+ await posthog.flush()
862
880
 
863
881
  expect(mockedFetch).toHaveBeenCalledWith('http://example.com/batch/', expect.any(Object))
864
882
 
@@ -887,7 +905,7 @@ describe('PostHog Node.js', () => {
887
905
  ).toEqual(true)
888
906
  jest.runOnlyPendingTimers()
889
907
  await waitForPromises()
890
- await posthog.flushAsync()
908
+ await posthog.flush()
891
909
 
892
910
  expect(mockedFetch).not.toHaveBeenCalledWith('http://example.com/batch/', expect.any(Object))
893
911
 
@@ -901,7 +919,7 @@ describe('PostHog Node.js', () => {
901
919
  ).toEqual(true)
902
920
  jest.runOnlyPendingTimers()
903
921
  await waitForPromises()
904
- await posthog.flushAsync()
922
+ await posthog.flush()
905
923
  expect(mockedFetch).toHaveBeenCalledWith('http://example.com/batch/', expect.any(Object))
906
924
 
907
925
  expect(getLastBatchEvents()?.[0]).toEqual(
@@ -930,7 +948,7 @@ describe('PostHog Node.js', () => {
930
948
  ).toEqual(true)
931
949
  jest.runOnlyPendingTimers()
932
950
  await waitForPromises()
933
- await posthog.flushAsync()
951
+ await posthog.flush()
934
952
  expect(mockedFetch).not.toHaveBeenCalledWith('http://example.com/batch/', expect.any(Object))
935
953
 
936
954
  // # called for different flag, falls back to decide, should call capture again
@@ -942,7 +960,7 @@ describe('PostHog Node.js', () => {
942
960
  ).toEqual('decide-value')
943
961
  jest.runOnlyPendingTimers()
944
962
  await waitForPromises()
945
- await posthog.flushAsync()
963
+ await posthog.flush()
946
964
  // one to decide, one to batch
947
965
  expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
948
966
  expect(mockedFetch).toHaveBeenCalledWith('http://example.com/batch/', expect.any(Object))
@@ -972,7 +990,7 @@ describe('PostHog Node.js', () => {
972
990
  ).toEqual(true)
973
991
  jest.runOnlyPendingTimers()
974
992
  await waitForPromises()
975
- await posthog.flushAsync()
993
+ await posthog.flush()
976
994
  // call decide, but not batch
977
995
  expect(mockedFetch).toHaveBeenCalledWith(...anyDecideCall)
978
996
  expect(mockedFetch).not.toHaveBeenCalledWith('http://example.com/batch/', expect.any(Object))