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.
- package/CHANGELOG.md +13 -0
- package/lib/index.cjs.js +1079 -2209
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +58 -28
- package/lib/index.esm.js +1079 -2209
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +54 -33
- package/lib/posthog-core/src/types.d.ts +21 -3
- package/lib/posthog-core/src/utils.d.ts +5 -4
- package/lib/posthog-node/src/posthog-node.d.ts +6 -8
- package/lib/posthog-node/src/types.d.ts +3 -1
- package/lib/posthog-node/test/test-utils.d.ts +8 -0
- package/package.json +1 -1
- package/src/posthog-node.ts +20 -16
- package/src/types.ts +3 -1
- package/test/extensions/sentry-integration.spec.ts +4 -3
- package/test/feature-flags.spec.ts +65 -135
- package/test/posthog-node.spec.ts +85 -67
- package/test/test-utils.ts +64 -0
|
@@ -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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
2058
|
-
|
|
2059
|
-
expect(matchProperty(property_a, { key: '2022-04-30' })).toBe(true)
|
|
2044
|
+
return
|
|
2045
|
+
})
|
|
2060
2046
|
|
|
2061
|
-
|
|
2062
|
-
|
|
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(
|
|
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(
|
|
2075
|
-
|
|
2076
|
-
|
|
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 './
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
261
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
expect(logSpy).toHaveBeenCalledTimes(
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
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
|
-
|
|
387
|
+
ph.capture({ event: 'test-event', distinctId: `${i}`, sendFeatureFlags: true })
|
|
369
388
|
}
|
|
370
389
|
|
|
371
|
-
await
|
|
390
|
+
await ph.shutdown()
|
|
372
391
|
// all capture calls happen during shutdown
|
|
373
392
|
const batchEvents = getLastBatchEvents()
|
|
374
|
-
expect(batchEvents?.length).toEqual(
|
|
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
|
-
|
|
391
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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))
|