@segment/analytics-browser-actions-facebook-conversions-api-web 1.11.1-staging-49cc16573.0 → 1.12.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.
- package/dist/cjs/send/functions.d.ts +5 -2
- package/dist/cjs/send/functions.js +56 -20
- package/dist/cjs/send/functions.js.map +1 -1
- package/dist/cjs/types.d.ts +1 -0
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/send/functions.d.ts +5 -2
- package/dist/esm/send/functions.js +54 -21
- package/dist/esm/send/functions.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/send/__tests__/formatFBEvent.test.ts +282 -0
- package/src/send/__tests__/formatUserData.test.ts +391 -0
- package/src/send/__tests__/functions.test.ts +113 -509
- package/src/send/__tests__/hashing.test.ts +123 -0
- package/src/send/__tests__/init.test.ts +578 -0
- package/src/send/functions.ts +71 -24
- package/src/types.ts +1 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { formatFBEvent } from '../functions'
|
|
2
|
+
import { Payload } from '../generated-types'
|
|
3
|
+
|
|
4
|
+
describe('formatFBEvent', () => {
|
|
5
|
+
it('should format a complete Purchase event with all fields', () => {
|
|
6
|
+
const payload: Partial<Payload> = {
|
|
7
|
+
event_config: {
|
|
8
|
+
event_name: 'Purchase',
|
|
9
|
+
show_fields: true
|
|
10
|
+
},
|
|
11
|
+
content_ids: ['product-123', 'product-456'],
|
|
12
|
+
content_name: 'Test Product',
|
|
13
|
+
content_category: 'Electronics',
|
|
14
|
+
content_type: 'product',
|
|
15
|
+
contents: [
|
|
16
|
+
{ id: 'product-123', quantity: 2, item_price: 49.99 },
|
|
17
|
+
{ id: 'product-456', quantity: 1, item_price: 99.99 }
|
|
18
|
+
],
|
|
19
|
+
currency: 'USD',
|
|
20
|
+
delivery_category: 'home_delivery',
|
|
21
|
+
num_items: 3,
|
|
22
|
+
value: 199.97,
|
|
23
|
+
predicted_ltv: 500.0,
|
|
24
|
+
net_revenue: 180.0,
|
|
25
|
+
custom_data: {
|
|
26
|
+
order_id: 'order-123',
|
|
27
|
+
campaign_id: 'summer-sale'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = formatFBEvent(payload as Payload)
|
|
32
|
+
|
|
33
|
+
expect(result).toEqual({
|
|
34
|
+
partner_agent: 'segment',
|
|
35
|
+
content_ids: ['product-123', 'product-456'],
|
|
36
|
+
content_name: 'Test Product',
|
|
37
|
+
content_category: 'Electronics',
|
|
38
|
+
content_type: 'product',
|
|
39
|
+
contents: [
|
|
40
|
+
{ id: 'product-123', quantity: 2, item_price: 49.99 },
|
|
41
|
+
{ id: 'product-456', quantity: 1, item_price: 99.99 }
|
|
42
|
+
],
|
|
43
|
+
currency: 'USD',
|
|
44
|
+
delivery_category: 'home_delivery',
|
|
45
|
+
num_items: 3,
|
|
46
|
+
value: 199.97,
|
|
47
|
+
predicted_ltv: 500.0,
|
|
48
|
+
net_revenue: 180.0,
|
|
49
|
+
custom_data: {
|
|
50
|
+
order_id: 'order-123',
|
|
51
|
+
campaign_id: 'summer-sale'
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should format minimal PageView event', () => {
|
|
57
|
+
const payload: Partial<Payload> = {
|
|
58
|
+
event_config: {
|
|
59
|
+
event_name: 'PageView',
|
|
60
|
+
show_fields: false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = formatFBEvent(payload as Payload)
|
|
65
|
+
|
|
66
|
+
expect(result).toEqual({
|
|
67
|
+
partner_agent: 'segment'
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should include only provided fields', () => {
|
|
72
|
+
const payload: Partial<Payload> = {
|
|
73
|
+
event_config: {
|
|
74
|
+
event_name: 'ViewContent',
|
|
75
|
+
show_fields: true
|
|
76
|
+
},
|
|
77
|
+
content_ids: ['product-789'],
|
|
78
|
+
value: 149.99,
|
|
79
|
+
currency: 'USD'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result = formatFBEvent(payload as Payload)
|
|
83
|
+
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
partner_agent: 'segment',
|
|
86
|
+
content_ids: ['product-789'],
|
|
87
|
+
value: 149.99,
|
|
88
|
+
currency: 'USD'
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should handle zero values for numeric fields', () => {
|
|
93
|
+
const payload: Partial<Payload> = {
|
|
94
|
+
event_config: {
|
|
95
|
+
event_name: 'Purchase',
|
|
96
|
+
show_fields: true
|
|
97
|
+
},
|
|
98
|
+
content_ids: ['product-123'],
|
|
99
|
+
value: 0,
|
|
100
|
+
num_items: 0,
|
|
101
|
+
predicted_ltv: 0,
|
|
102
|
+
net_revenue: 0
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result = formatFBEvent(payload as Payload)
|
|
106
|
+
|
|
107
|
+
expect(result).toEqual({
|
|
108
|
+
partner_agent: 'segment',
|
|
109
|
+
content_ids: ['product-123'],
|
|
110
|
+
value: 0,
|
|
111
|
+
num_items: 0,
|
|
112
|
+
predicted_ltv: 0,
|
|
113
|
+
net_revenue: 0
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should not include empty arrays', () => {
|
|
118
|
+
const payload: Partial<Payload> = {
|
|
119
|
+
event_config: {
|
|
120
|
+
event_name: 'AddToCart',
|
|
121
|
+
show_fields: true
|
|
122
|
+
},
|
|
123
|
+
content_ids: [],
|
|
124
|
+
contents: [],
|
|
125
|
+
value: 99.99
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = formatFBEvent(payload as Payload)
|
|
129
|
+
|
|
130
|
+
expect(result).toEqual({
|
|
131
|
+
partner_agent: 'segment',
|
|
132
|
+
value: 99.99
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('should not include empty custom_data object', () => {
|
|
137
|
+
const payload: Partial<Payload> = {
|
|
138
|
+
event_config: {
|
|
139
|
+
event_name: 'Purchase',
|
|
140
|
+
show_fields: true
|
|
141
|
+
},
|
|
142
|
+
content_ids: ['product-123'],
|
|
143
|
+
value: 99.99,
|
|
144
|
+
custom_data: {}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const result = formatFBEvent(payload as Payload)
|
|
148
|
+
|
|
149
|
+
expect(result).toEqual({
|
|
150
|
+
partner_agent: 'segment',
|
|
151
|
+
content_ids: ['product-123'],
|
|
152
|
+
value: 99.99
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should include contents array with all item properties', () => {
|
|
157
|
+
const payload: Partial<Payload> = {
|
|
158
|
+
event_config: {
|
|
159
|
+
event_name: 'Purchase',
|
|
160
|
+
show_fields: true
|
|
161
|
+
},
|
|
162
|
+
contents: [
|
|
163
|
+
{ id: 'product-1', quantity: 2, item_price: 25.50 },
|
|
164
|
+
{ id: 'product-2', quantity: 1, item_price: 100.00 },
|
|
165
|
+
{ id: 'product-3', quantity: 3 }
|
|
166
|
+
],
|
|
167
|
+
value: 151.00
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result = formatFBEvent(payload as Payload)
|
|
171
|
+
|
|
172
|
+
expect(result).toEqual({
|
|
173
|
+
partner_agent: 'segment',
|
|
174
|
+
contents: [
|
|
175
|
+
{ id: 'product-1', quantity: 2, item_price: 25.50 },
|
|
176
|
+
{ id: 'product-2', quantity: 1, item_price: 100.00 },
|
|
177
|
+
{ id: 'product-3', quantity: 3 }
|
|
178
|
+
],
|
|
179
|
+
value: 151.00
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should include all standard event fields', () => {
|
|
184
|
+
const payload: Partial<Payload> = {
|
|
185
|
+
event_config: {
|
|
186
|
+
event_name: 'InitiateCheckout',
|
|
187
|
+
show_fields: true
|
|
188
|
+
},
|
|
189
|
+
content_category: 'Apparel',
|
|
190
|
+
content_ids: ['shirt-123'],
|
|
191
|
+
content_name: 'Blue Shirt',
|
|
192
|
+
content_type: 'product',
|
|
193
|
+
currency: 'EUR',
|
|
194
|
+
num_items: 2,
|
|
195
|
+
value: 59.98
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = formatFBEvent(payload as Payload)
|
|
199
|
+
|
|
200
|
+
expect(result).toEqual({
|
|
201
|
+
partner_agent: 'segment',
|
|
202
|
+
content_category: 'Apparel',
|
|
203
|
+
content_ids: ['shirt-123'],
|
|
204
|
+
content_name: 'Blue Shirt',
|
|
205
|
+
content_type: 'product',
|
|
206
|
+
currency: 'EUR',
|
|
207
|
+
num_items: 2,
|
|
208
|
+
value: 59.98
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should format Subscribe event with predicted_ltv', () => {
|
|
213
|
+
const payload: Partial<Payload> = {
|
|
214
|
+
event_config: {
|
|
215
|
+
event_name: 'Subscribe',
|
|
216
|
+
show_fields: true
|
|
217
|
+
},
|
|
218
|
+
value: 9.99,
|
|
219
|
+
currency: 'USD',
|
|
220
|
+
predicted_ltv: 119.88
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const result = formatFBEvent(payload as Payload)
|
|
224
|
+
|
|
225
|
+
expect(result).toEqual({
|
|
226
|
+
partner_agent: 'segment',
|
|
227
|
+
value: 9.99,
|
|
228
|
+
currency: 'USD',
|
|
229
|
+
predicted_ltv: 119.88
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('should format Purchase event with net_revenue', () => {
|
|
234
|
+
const payload: Partial<Payload> = {
|
|
235
|
+
event_config: {
|
|
236
|
+
event_name: 'Purchase',
|
|
237
|
+
show_fields: true
|
|
238
|
+
},
|
|
239
|
+
content_ids: ['product-123'],
|
|
240
|
+
value: 100.00,
|
|
241
|
+
currency: 'USD',
|
|
242
|
+
net_revenue: 85.00
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const result = formatFBEvent(payload as Payload)
|
|
246
|
+
|
|
247
|
+
expect(result).toEqual({
|
|
248
|
+
partner_agent: 'segment',
|
|
249
|
+
content_ids: ['product-123'],
|
|
250
|
+
value: 100.00,
|
|
251
|
+
currency: 'USD',
|
|
252
|
+
net_revenue: 85.00
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should include custom_data when provided', () => {
|
|
257
|
+
const payload: Partial<Payload> = {
|
|
258
|
+
event_config: {
|
|
259
|
+
event_name: 'Lead',
|
|
260
|
+
show_fields: true
|
|
261
|
+
},
|
|
262
|
+
value: 0,
|
|
263
|
+
custom_data: {
|
|
264
|
+
lead_source: 'facebook_ad',
|
|
265
|
+
lead_type: 'newsletter_signup',
|
|
266
|
+
campaign_name: 'Q1-2024'
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const result = formatFBEvent(payload as Payload)
|
|
271
|
+
|
|
272
|
+
expect(result).toEqual({
|
|
273
|
+
partner_agent: 'segment',
|
|
274
|
+
value: 0,
|
|
275
|
+
custom_data: {
|
|
276
|
+
lead_source: 'facebook_ad',
|
|
277
|
+
lead_type: 'newsletter_signup',
|
|
278
|
+
campaign_name: 'Q1-2024'
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
})
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { formatUserData } from '../functions'
|
|
2
|
+
import { Payload } from '../generated-types'
|
|
3
|
+
|
|
4
|
+
describe('formatUserData', () => {
|
|
5
|
+
describe('without clientParamBuilder', () => {
|
|
6
|
+
it('should format and hash email', async () => {
|
|
7
|
+
const userData: Payload['userData'] = {
|
|
8
|
+
em: 'TEST@EXAMPLE.COM'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const result = await formatUserData(userData, undefined)
|
|
12
|
+
|
|
13
|
+
// Email should be normalized (lowercase, trimmed) and hashed
|
|
14
|
+
// 'test@example.com' -> '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b'
|
|
15
|
+
expect(result).toEqual({
|
|
16
|
+
em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b'
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should format and hash phone number', async () => {
|
|
21
|
+
const userData: Payload['userData'] = {
|
|
22
|
+
ph: '(555) 123-4567'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const result = await formatUserData(userData, undefined)
|
|
26
|
+
|
|
27
|
+
// Phone should have non-numeric characters removed, then hashed
|
|
28
|
+
// '5551234567' -> '3c95277da5fd0da6a1a44ee3fdf56d20af6c6d242695a40e18e6e90dc3c5872c'
|
|
29
|
+
expect(result).toEqual({
|
|
30
|
+
ph: '3c95277da5fd0da6a1a44ee3fdf56d20af6c6d242695a40e18e6e90dc3c5872c'
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should format and hash first and last name', async () => {
|
|
35
|
+
const userData: Payload['userData'] = {
|
|
36
|
+
fn: ' JOHN ',
|
|
37
|
+
ln: ' DOE '
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = await formatUserData(userData, undefined)
|
|
41
|
+
|
|
42
|
+
// Names should be lowercased and trimmed, then hashed
|
|
43
|
+
// 'john' -> '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a'
|
|
44
|
+
// 'doe' -> '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f'
|
|
45
|
+
expect(result).toEqual({
|
|
46
|
+
fn: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a',
|
|
47
|
+
ln: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f'
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should format and hash gender', async () => {
|
|
52
|
+
const userData: Payload['userData'] = {
|
|
53
|
+
ge: 'm'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = await formatUserData(userData, undefined)
|
|
57
|
+
|
|
58
|
+
// 'm' -> '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a'
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
ge: '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a'
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should format date of birth as YYYYMMDD and hash', async () => {
|
|
65
|
+
const userData: Payload['userData'] = {
|
|
66
|
+
db: '1990-05-15T00:00:00.000Z'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = await formatUserData(userData, undefined)
|
|
70
|
+
|
|
71
|
+
// Date formatted as '19900515' then hashed
|
|
72
|
+
// '19900515' -> '53058fbd6731774c37a6d838c09d25b337fa7b9b5007f82cc934d857d2596e0c'
|
|
73
|
+
expect(result).toEqual({
|
|
74
|
+
db: '53058fbd6731774c37a6d838c09d25b337fa7b9b5007f82cc934d857d2596e0c'
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should format and hash city', async () => {
|
|
79
|
+
const userData: Payload['userData'] = {
|
|
80
|
+
ct: ' New York '
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result = await formatUserData(userData, undefined)
|
|
84
|
+
|
|
85
|
+
// City should be lowercased with spaces removed, then hashed
|
|
86
|
+
// 'newyork' -> '350c754ba4d38897693aa077ef43072a859d23f613443133fecbbd90a3512ca5'
|
|
87
|
+
expect(result).toEqual({
|
|
88
|
+
ct: '350c754ba4d38897693aa077ef43072a859d23f613443133fecbbd90a3512ca5'
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should convert state name to code and hash', async () => {
|
|
93
|
+
const userData: Payload['userData'] = {
|
|
94
|
+
st: 'California'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await formatUserData(userData, undefined)
|
|
98
|
+
|
|
99
|
+
// 'California' -> 'ca' -> hashed
|
|
100
|
+
// 'ca' -> '6959097001d10501ac7d54c0bdb8db61420f658f2922cc26e46d536119a31126'
|
|
101
|
+
expect(result).toEqual({
|
|
102
|
+
st: '6959097001d10501ac7d54c0bdb8db61420f658f2922cc26e46d536119a31126'
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should lowercase state code and hash', async () => {
|
|
107
|
+
const userData: Payload['userData'] = {
|
|
108
|
+
st: 'NY'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = await formatUserData(userData, undefined)
|
|
112
|
+
|
|
113
|
+
// 'NY' -> 'ny' -> hashed
|
|
114
|
+
// 'ny' -> '1b06e2003f8420d6fa42badd8f77ec0f706b976b7a48b13c567dc5a559681683'
|
|
115
|
+
expect(result).toEqual({
|
|
116
|
+
st: '1b06e2003f8420d6fa42badd8f77ec0f706b976b7a48b13c567dc5a559681683'
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should convert country name to code and hash', async () => {
|
|
121
|
+
const userData: Payload['userData'] = {
|
|
122
|
+
country: 'United States'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const result = await formatUserData(userData, undefined)
|
|
126
|
+
|
|
127
|
+
// 'United States' -> 'us' -> hashed
|
|
128
|
+
// 'us' -> '79adb2a2fce5c6ba215fe5f27f532d4e7edbac4b6a5e09e1ef3a08084a904621'
|
|
129
|
+
expect(result).toEqual({
|
|
130
|
+
country: '79adb2a2fce5c6ba215fe5f27f532d4e7edbac4b6a5e09e1ef3a08084a904621'
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should format and hash zip code', async () => {
|
|
135
|
+
const userData: Payload['userData'] = {
|
|
136
|
+
zp: ' 94102 '
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = await formatUserData(userData, undefined)
|
|
140
|
+
|
|
141
|
+
// Zip should be trimmed then hashed
|
|
142
|
+
// '94102' -> '8137c19c8f35f6b6a1cce99753226e1c7211eaaebd68528b789f973b0be95e31'
|
|
143
|
+
expect(result).toEqual({
|
|
144
|
+
zp: '8137c19c8f35f6b6a1cce99753226e1c7211eaaebd68528b789f973b0be95e31'
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should format and hash external_id', async () => {
|
|
149
|
+
const userData: Payload['userData'] = {
|
|
150
|
+
external_id: ' user-123 '
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = await formatUserData(userData, undefined)
|
|
154
|
+
|
|
155
|
+
// External ID should be trimmed then hashed
|
|
156
|
+
// 'user-123' -> 'fcdec6df4d44dbc637c7c5b58efface52a7f8a88535423430255be0bb89bedd8'
|
|
157
|
+
expect(result).toEqual({
|
|
158
|
+
external_id: 'fcdec6df4d44dbc637c7c5b58efface52a7f8a88535423430255be0bb89bedd8'
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should NOT hash fbp and fbc cookies', async () => {
|
|
163
|
+
const userData: Payload['userData'] = {
|
|
164
|
+
fbp: ' fb.1.1234567890.1234567890 ',
|
|
165
|
+
fbc: ' fb.1.1234567890.AbCdEf123 '
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = await formatUserData(userData, undefined)
|
|
169
|
+
|
|
170
|
+
// FBP and FBC should only be trimmed, NOT hashed
|
|
171
|
+
expect(result).toEqual({
|
|
172
|
+
fbp: 'fb.1.1234567890.1234567890',
|
|
173
|
+
fbc: 'fb.1.1234567890.AbCdEf123'
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('should format all fields combined', async () => {
|
|
178
|
+
const userData: Payload['userData'] = {
|
|
179
|
+
external_id: 'user-123',
|
|
180
|
+
em: 'test@example.com',
|
|
181
|
+
ph: '5551234567',
|
|
182
|
+
fn: 'John',
|
|
183
|
+
ln: 'Doe',
|
|
184
|
+
ge: 'm',
|
|
185
|
+
db: '1990-05-15T00:00:00.000Z',
|
|
186
|
+
ct: 'San Francisco',
|
|
187
|
+
st: 'California',
|
|
188
|
+
zp: '94102',
|
|
189
|
+
country: 'United States',
|
|
190
|
+
fbp: 'fb.1.1234567890.1234567890',
|
|
191
|
+
fbc: 'fb.1.1234567890.AbCdEf123'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const result = await formatUserData(userData, undefined)
|
|
195
|
+
|
|
196
|
+
expect(result).toEqual({
|
|
197
|
+
external_id: 'fcdec6df4d44dbc637c7c5b58efface52a7f8a88535423430255be0bb89bedd8',
|
|
198
|
+
em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b',
|
|
199
|
+
ph: '3c95277da5fd0da6a1a44ee3fdf56d20af6c6d242695a40e18e6e90dc3c5872c',
|
|
200
|
+
fn: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a',
|
|
201
|
+
ln: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f',
|
|
202
|
+
ge: '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a',
|
|
203
|
+
db: '53058fbd6731774c37a6d838c09d25b337fa7b9b5007f82cc934d857d2596e0c',
|
|
204
|
+
ct: '1a6bd4d9d79dc0a79b53795c70d3349fa9e38968a3fbefbfe8783efb1d2b6aac',
|
|
205
|
+
st: '6959097001d10501ac7d54c0bdb8db61420f658f2922cc26e46d536119a31126',
|
|
206
|
+
zp: '8137c19c8f35f6b6a1cce99753226e1c7211eaaebd68528b789f973b0be95e31',
|
|
207
|
+
country: '79adb2a2fce5c6ba215fe5f27f532d4e7edbac4b6a5e09e1ef3a08084a904621',
|
|
208
|
+
fbp: 'fb.1.1234567890.1234567890',
|
|
209
|
+
fbc: 'fb.1.1234567890.AbCdEf123'
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('should return undefined when userData is undefined', async () => {
|
|
214
|
+
const result = await formatUserData(undefined, undefined)
|
|
215
|
+
|
|
216
|
+
expect(result).toBeUndefined()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('should return undefined when all fields are invalid', async () => {
|
|
220
|
+
const userData: Payload['userData'] = {
|
|
221
|
+
ge: 'invalid'
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const result = await formatUserData(userData, undefined)
|
|
225
|
+
|
|
226
|
+
expect(result).toBeUndefined()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('should skip invalid gender values', async () => {
|
|
230
|
+
const userData: Payload['userData'] = {
|
|
231
|
+
em: 'test@example.com',
|
|
232
|
+
ge: 'invalid'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const result = await formatUserData(userData, undefined)
|
|
236
|
+
|
|
237
|
+
// Should only include email, skip invalid gender
|
|
238
|
+
expect(result).toEqual({
|
|
239
|
+
em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b'
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('should skip invalid date of birth', async () => {
|
|
244
|
+
const userData: Payload['userData'] = {
|
|
245
|
+
em: 'test@example.com',
|
|
246
|
+
db: 'invalid-date'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const result = await formatUserData(userData, undefined)
|
|
250
|
+
|
|
251
|
+
// Should only include email, skip invalid date
|
|
252
|
+
expect(result).toEqual({
|
|
253
|
+
em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b'
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
describe('with clientParamBuilder', () => {
|
|
259
|
+
let mockClientParamBuilder: any
|
|
260
|
+
|
|
261
|
+
beforeEach(() => {
|
|
262
|
+
mockClientParamBuilder = {
|
|
263
|
+
getNormalizedAndHashedPII: jest.fn(),
|
|
264
|
+
processAndCollectAllParams: jest.fn(),
|
|
265
|
+
getFbc: jest.fn(),
|
|
266
|
+
getFbp: jest.fn()
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should use clientParamBuilder for email', async () => {
|
|
271
|
+
mockClientParamBuilder.getNormalizedAndHashedPII.mockReturnValue('hashed_email_value')
|
|
272
|
+
|
|
273
|
+
const userData: Payload['userData'] = {
|
|
274
|
+
em: 'TEST@EXAMPLE.COM'
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const result = await formatUserData(userData, mockClientParamBuilder)
|
|
278
|
+
|
|
279
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('TEST@EXAMPLE.COM', 'email')
|
|
280
|
+
expect(result).toEqual({
|
|
281
|
+
em: 'hashed_email_value'
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should use clientParamBuilder for all PII fields', async () => {
|
|
286
|
+
mockClientParamBuilder.getNormalizedAndHashedPII.mockImplementation((_, type) => {
|
|
287
|
+
return `hashed_${type}_value`
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
const userData: Payload['userData'] = {
|
|
291
|
+
em: 'test@example.com',
|
|
292
|
+
ph: '5551234567',
|
|
293
|
+
fn: 'John',
|
|
294
|
+
ln: 'Doe',
|
|
295
|
+
ge: 'm',
|
|
296
|
+
db: '1990-05-15',
|
|
297
|
+
ct: 'San Francisco',
|
|
298
|
+
st: 'CA',
|
|
299
|
+
zp: '94102',
|
|
300
|
+
country: 'US',
|
|
301
|
+
external_id: 'user-123'
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const result = await formatUserData(userData, mockClientParamBuilder)
|
|
305
|
+
|
|
306
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('test@example.com', 'email')
|
|
307
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('5551234567', 'phone')
|
|
308
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('John', 'first_name')
|
|
309
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('Doe', 'last_name')
|
|
310
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('m', 'gender')
|
|
311
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('1990-05-15', 'date_of_birth')
|
|
312
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('San Francisco', 'city')
|
|
313
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('CA', 'state')
|
|
314
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('94102', 'zip_code')
|
|
315
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('US', 'country')
|
|
316
|
+
expect(mockClientParamBuilder.getNormalizedAndHashedPII).toHaveBeenCalledWith('user-123', 'external_id')
|
|
317
|
+
|
|
318
|
+
expect(result).toEqual({
|
|
319
|
+
em: 'hashed_email_value',
|
|
320
|
+
ph: 'hashed_phone_value',
|
|
321
|
+
fn: 'hashed_first_name_value',
|
|
322
|
+
ln: 'hashed_last_name_value',
|
|
323
|
+
ge: 'hashed_gender_value',
|
|
324
|
+
db: 'hashed_date_of_birth_value',
|
|
325
|
+
ct: 'hashed_city_value',
|
|
326
|
+
st: 'hashed_state_value',
|
|
327
|
+
zp: 'hashed_zip_code_value',
|
|
328
|
+
country: 'hashed_country_value',
|
|
329
|
+
external_id: 'hashed_external_id_value'
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('should use clientParamBuilder getFbc and getFbp methods', async () => {
|
|
334
|
+
mockClientParamBuilder.getNormalizedAndHashedPII.mockReturnValue('hashed_email')
|
|
335
|
+
mockClientParamBuilder.getFbc.mockReturnValue('fb.1.1234567890.ClientParamBuilderFbc')
|
|
336
|
+
mockClientParamBuilder.getFbp.mockReturnValue('fb.1.1234567890.ClientParamBuilderFbp')
|
|
337
|
+
|
|
338
|
+
const userData: Payload['userData'] = {
|
|
339
|
+
em: 'test@example.com',
|
|
340
|
+
fbc: 'fb.1.1234567890.PayloadFbc',
|
|
341
|
+
fbp: 'fb.1.1234567890.PayloadFbp'
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const result = await formatUserData(userData, mockClientParamBuilder)
|
|
345
|
+
|
|
346
|
+
expect(mockClientParamBuilder.processAndCollectAllParams).toHaveBeenCalled()
|
|
347
|
+
expect(mockClientParamBuilder.getFbc).toHaveBeenCalled()
|
|
348
|
+
expect(mockClientParamBuilder.getFbp).toHaveBeenCalled()
|
|
349
|
+
|
|
350
|
+
// ClientParamBuilder values should override payload values
|
|
351
|
+
expect(result).toEqual({
|
|
352
|
+
em: 'hashed_email',
|
|
353
|
+
fbc: 'fb.1.1234567890.ClientParamBuilderFbc',
|
|
354
|
+
fbp: 'fb.1.1234567890.ClientParamBuilderFbp'
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('should fallback to payload fbc/fbp when clientParamBuilder returns null', async () => {
|
|
359
|
+
mockClientParamBuilder.getNormalizedAndHashedPII.mockReturnValue('hashed_email')
|
|
360
|
+
mockClientParamBuilder.getFbc.mockReturnValue(null)
|
|
361
|
+
mockClientParamBuilder.getFbp.mockReturnValue(null)
|
|
362
|
+
|
|
363
|
+
const userData: Payload['userData'] = {
|
|
364
|
+
em: 'test@example.com',
|
|
365
|
+
fbc: 'fb.1.1234567890.PayloadFbc',
|
|
366
|
+
fbp: 'fb.1.1234567890.PayloadFbp'
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const result = await formatUserData(userData, mockClientParamBuilder)
|
|
370
|
+
|
|
371
|
+
expect(result).toEqual({
|
|
372
|
+
em: 'hashed_email',
|
|
373
|
+
fbc: 'fb.1.1234567890.PayloadFbc',
|
|
374
|
+
fbp: 'fb.1.1234567890.PayloadFbp'
|
|
375
|
+
})
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('should return undefined when clientParamBuilder returns undefined for all fields', async () => {
|
|
379
|
+
mockClientParamBuilder.getNormalizedAndHashedPII.mockReturnValue(undefined)
|
|
380
|
+
|
|
381
|
+
const userData: Payload['userData'] = {
|
|
382
|
+
em: 'test@example.com',
|
|
383
|
+
ph: '5551234567'
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const result = await formatUserData(userData, mockClientParamBuilder)
|
|
387
|
+
|
|
388
|
+
expect(result).toBeUndefined()
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
})
|