posthog-node 3.6.2 → 4.0.0-beta.1

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
 
@@ -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', () => {
@@ -2,7 +2,7 @@
2
2
  import { PostHog as PostHog } from '../src/posthog-node'
3
3
  jest.mock('../src/fetch')
4
4
  import fetch from '../src/fetch'
5
- import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './feature-flags.spec'
5
+ import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './test-utils'
6
6
  import { waitForPromises, wait } from '../../posthog-core/test/test-utils/test-utils'
7
7
  import { randomUUID } from 'crypto'
8
8
 
@@ -260,7 +260,6 @@ describe('PostHog Node.js', () => {
260
260
  await waitForPromises()
261
261
  jest.runOnlyPendingTimers()
262
262
  batchEvents = getLastBatchEvents()
263
- console.warn(batchEvents)
264
263
  expect(batchEvents?.[0].properties).toEqual({
265
264
  $groups: { org: 123 },
266
265
  foo: 'bar',
@@ -0,0 +1,64 @@
1
+ export const apiImplementation = ({
2
+ localFlags,
3
+ decideFlags,
4
+ decideFlagPayloads,
5
+ decideStatus = 200,
6
+ }: {
7
+ localFlags?: any
8
+ decideFlags?: any
9
+ decideFlagPayloads?: any
10
+ decideStatus?: number
11
+ }) => {
12
+ return (url: any): Promise<any> => {
13
+ if ((url as any).includes('/decide/')) {
14
+ return Promise.resolve({
15
+ status: decideStatus,
16
+ text: () => Promise.resolve('ok'),
17
+ json: () => {
18
+ if (decideStatus !== 200) {
19
+ return Promise.resolve(decideFlags)
20
+ } else {
21
+ return Promise.resolve({
22
+ featureFlags: decideFlags,
23
+ featureFlagPayloads: decideFlagPayloads,
24
+ })
25
+ }
26
+ },
27
+ }) as any
28
+ }
29
+
30
+ if ((url as any).includes('api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts')) {
31
+ return Promise.resolve({
32
+ status: 200,
33
+ text: () => Promise.resolve('ok'),
34
+ json: () => Promise.resolve(localFlags),
35
+ }) as any
36
+ }
37
+
38
+ if ((url as any).includes('batch/')) {
39
+ return Promise.resolve({
40
+ status: 200,
41
+ text: () => Promise.resolve('ok'),
42
+ json: () =>
43
+ Promise.resolve({
44
+ status: 'ok',
45
+ }),
46
+ }) as any
47
+ }
48
+
49
+ return Promise.resolve({
50
+ status: 400,
51
+ text: () => Promise.resolve('ok'),
52
+ json: () =>
53
+ Promise.resolve({
54
+ status: 'ok',
55
+ }),
56
+ }) as any
57
+ }
58
+ }
59
+
60
+ export const anyLocalEvalCall = [
61
+ 'http://example.com/api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts',
62
+ expect.any(Object),
63
+ ]
64
+ export const anyDecideCall = ['http://example.com/decide/?v=3', expect.any(Object)]