@adtrackify/at-service-common 4.0.13 → 4.0.15

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.
Files changed (109) hide show
  1. package/dist/cjs/__tests__/identity-cache/helpers.spec.js +37 -38
  2. package/dist/cjs/__tests__/identity-cache/helpers.spec.js.map +1 -1
  3. package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js +92 -349
  4. package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
  5. package/dist/cjs/__tests__/identity-cache/identity-client.spec.js +360 -0
  6. package/dist/cjs/__tests__/identity-cache/identity-client.spec.js.map +1 -0
  7. package/dist/cjs/__tests__/identity-cache/identity-gateway-cache-hit.spec.js +138 -0
  8. package/dist/cjs/__tests__/identity-cache/identity-gateway-cache-hit.spec.js.map +1 -0
  9. package/dist/cjs/__tests__/identity-cache/identity-gateway-cache-miss.spec.js +153 -0
  10. package/dist/cjs/__tests__/identity-cache/identity-gateway-cache-miss.spec.js.map +1 -0
  11. package/dist/cjs/__tests__/identity-cache/identity-gateway-constructor.spec.js +72 -0
  12. package/dist/cjs/__tests__/identity-cache/identity-gateway-constructor.spec.js.map +1 -0
  13. package/dist/cjs/__tests__/identity-cache/identity-gateway-edge-cases.spec.js +89 -0
  14. package/dist/cjs/__tests__/identity-cache/identity-gateway-edge-cases.spec.js.map +1 -0
  15. package/dist/cjs/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js +136 -0
  16. package/dist/cjs/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js.map +1 -0
  17. package/dist/cjs/__tests__/identity-cache/identity-gateway-stale-cache.spec.js +224 -0
  18. package/dist/cjs/__tests__/identity-cache/identity-gateway-stale-cache.spec.js.map +1 -0
  19. package/dist/cjs/__tests__/identity-cache/identity-gateway-test-setup.d.ts +28 -0
  20. package/dist/cjs/__tests__/identity-cache/identity-gateway-test-setup.js +90 -0
  21. package/dist/cjs/__tests__/identity-cache/identity-gateway-test-setup.js.map +1 -0
  22. package/dist/cjs/clients/internal-api/identity-client.d.ts +12 -2
  23. package/dist/cjs/clients/internal-api/identity-client.js +59 -13
  24. package/dist/cjs/clients/internal-api/identity-client.js.map +1 -1
  25. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +1 -7
  26. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js +0 -305
  27. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -1
  28. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +14 -14
  29. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +122 -330
  30. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -1
  31. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.d.ts +0 -1
  32. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js +0 -1
  33. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js.map +1 -1
  34. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.d.ts +8 -158
  35. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js +1 -15
  36. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js.map +1 -1
  37. package/dist/esm/__tests__/identity-cache/helpers.spec.js +26 -27
  38. package/dist/esm/__tests__/identity-cache/helpers.spec.js.map +1 -1
  39. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js +92 -349
  40. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
  41. package/dist/esm/__tests__/identity-cache/identity-client.spec.js +358 -0
  42. package/dist/esm/__tests__/identity-cache/identity-client.spec.js.map +1 -0
  43. package/dist/esm/__tests__/identity-cache/identity-gateway-cache-hit.spec.d.ts +1 -0
  44. package/dist/esm/__tests__/identity-cache/identity-gateway-cache-hit.spec.js +136 -0
  45. package/dist/esm/__tests__/identity-cache/identity-gateway-cache-hit.spec.js.map +1 -0
  46. package/dist/esm/__tests__/identity-cache/identity-gateway-cache-miss.spec.d.ts +1 -0
  47. package/dist/esm/__tests__/identity-cache/identity-gateway-cache-miss.spec.js +151 -0
  48. package/dist/esm/__tests__/identity-cache/identity-gateway-cache-miss.spec.js.map +1 -0
  49. package/dist/esm/__tests__/identity-cache/identity-gateway-constructor.spec.d.ts +1 -0
  50. package/dist/esm/__tests__/identity-cache/identity-gateway-constructor.spec.js +70 -0
  51. package/dist/esm/__tests__/identity-cache/identity-gateway-constructor.spec.js.map +1 -0
  52. package/dist/esm/__tests__/identity-cache/identity-gateway-edge-cases.spec.d.ts +1 -0
  53. package/dist/esm/__tests__/identity-cache/identity-gateway-edge-cases.spec.js +87 -0
  54. package/dist/esm/__tests__/identity-cache/identity-gateway-edge-cases.spec.js.map +1 -0
  55. package/dist/esm/__tests__/identity-cache/identity-gateway-enqueue-async.spec.d.ts +1 -0
  56. package/dist/esm/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js +134 -0
  57. package/dist/esm/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js.map +1 -0
  58. package/dist/esm/__tests__/identity-cache/identity-gateway-stale-cache.spec.d.ts +1 -0
  59. package/dist/esm/__tests__/identity-cache/identity-gateway-stale-cache.spec.js +222 -0
  60. package/dist/esm/__tests__/identity-cache/identity-gateway-stale-cache.spec.js.map +1 -0
  61. package/dist/esm/__tests__/identity-cache/identity-gateway-test-setup.d.ts +28 -0
  62. package/dist/esm/__tests__/identity-cache/identity-gateway-test-setup.js +83 -0
  63. package/dist/esm/__tests__/identity-cache/identity-gateway-test-setup.js.map +1 -0
  64. package/dist/esm/clients/internal-api/identity-client.d.ts +12 -2
  65. package/dist/esm/clients/internal-api/identity-client.js +60 -14
  66. package/dist/esm/clients/internal-api/identity-client.js.map +1 -1
  67. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +1 -7
  68. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js +1 -303
  69. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -1
  70. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +14 -14
  71. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +125 -330
  72. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -1
  73. package/dist/esm/services/db/identity-cache-dynamodb-service/index.d.ts +0 -1
  74. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js +0 -1
  75. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js.map +1 -1
  76. package/dist/esm/services/db/identity-cache-dynamodb-service/types.d.ts +8 -158
  77. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js +0 -14
  78. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js.map +1 -1
  79. package/package.json +2 -2
  80. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +0 -646
  81. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +0 -1
  82. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +0 -325
  83. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +0 -1
  84. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +0 -1286
  85. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
  86. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js +0 -2170
  87. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js.map +0 -1
  88. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.d.ts +0 -26
  89. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js +0 -252
  90. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js.map +0 -1
  91. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +0 -644
  92. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +0 -1
  93. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +0 -323
  94. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +0 -1
  95. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +0 -1284
  96. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
  97. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js +0 -2168
  98. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js.map +0 -1
  99. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.d.ts +0 -26
  100. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js +0 -235
  101. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js.map +0 -1
  102. /package/dist/cjs/__tests__/identity-cache/{identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts → identity-client.spec.d.ts} +0 -0
  103. /package/dist/cjs/__tests__/identity-cache/{identity-cache-dynamodb-service-v2-comparison.spec.d.ts → identity-gateway-cache-hit.spec.d.ts} +0 -0
  104. /package/dist/cjs/__tests__/identity-cache/{identity-cache-dynamodb-service.spec.d.ts → identity-gateway-cache-miss.spec.d.ts} +0 -0
  105. /package/dist/cjs/__tests__/identity-cache/{identity-gateway-service.spec.d.ts → identity-gateway-constructor.spec.d.ts} +0 -0
  106. /package/dist/{esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts → cjs/__tests__/identity-cache/identity-gateway-edge-cases.spec.d.ts} +0 -0
  107. /package/dist/{esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts → cjs/__tests__/identity-cache/identity-gateway-enqueue-async.spec.d.ts} +0 -0
  108. /package/dist/{esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts → cjs/__tests__/identity-cache/identity-gateway-stale-cache.spec.d.ts} +0 -0
  109. /package/dist/esm/__tests__/identity-cache/{identity-gateway-service.spec.d.ts → identity-client.spec.d.ts} +0 -0
@@ -1,644 +0,0 @@
1
- import { ADDRESS_INFO_SOURCE } from '@adtrackify/at-tracking-event-types';
2
- jest.mock('../../clients/index.js', () => ({
3
- DynamoDbClient: {
4
- safeGet: jest.fn(),
5
- safePut: jest.fn(),
6
- safeDelete: jest.fn(),
7
- safeBatchGet: jest.fn(),
8
- safeBatchWrite: jest.fn(),
9
- safeQueryByGSI: jest.fn(),
10
- batchWrite: jest.fn(),
11
- },
12
- }));
13
- jest.mock('../../helpers/index.js', () => ({
14
- Logger: {
15
- debug: jest.fn(),
16
- info: jest.fn(),
17
- warn: jest.fn(),
18
- error: jest.fn(),
19
- },
20
- }));
21
- import { DynamoDbClient } from '../../clients/index.js';
22
- import { Logger } from '../../helpers/index.js';
23
- import { IdentityCacheDynamoDbDbService } from '../../services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js';
24
- import { CACHE_WRITE_REASON, IDENTITY_CACHE_LOOKUP_OUTCOME, } from '../../services/db/identity-cache-dynamodb-service/types.js';
25
- const mockedDynamoDbClient = DynamoDbClient;
26
- const mockedLogger = Logger;
27
- const pixelId = 'pixel-cache-lookup-v2';
28
- let dateNowSpy;
29
- const deepClone = (value) => {
30
- if (value === undefined) {
31
- return value;
32
- }
33
- return JSON.parse(JSON.stringify(value));
34
- };
35
- const normalizeDynamicValues = (value) => {
36
- if (Array.isArray(value)) {
37
- return value.map(normalizeDynamicValues);
38
- }
39
- if (value && typeof value === 'object') {
40
- const normalized = {};
41
- for (const [key, objectValue] of Object.entries(value)) {
42
- if (key === 'updatedAt' || key === 'createdAt') {
43
- normalized[key] = '<timestamp>';
44
- }
45
- else {
46
- normalized[key] = normalizeDynamicValues(objectValue);
47
- }
48
- }
49
- return normalized;
50
- }
51
- return value;
52
- };
53
- const cloneCalls = (mockFunction) => normalizeDynamicValues(deepClone(mockFunction.mock.calls));
54
- const captureDynamoDbCalls = () => ({
55
- safeGet: cloneCalls(mockedDynamoDbClient.safeGet),
56
- safePut: cloneCalls(mockedDynamoDbClient.safePut),
57
- safeDelete: cloneCalls(mockedDynamoDbClient.safeDelete),
58
- safeBatchGet: cloneCalls(mockedDynamoDbClient.safeBatchGet),
59
- safeBatchWrite: cloneCalls(mockedDynamoDbClient.safeBatchWrite),
60
- safeQueryByGSI: cloneCalls(mockedDynamoDbClient.safeQueryByGSI),
61
- batchWrite: cloneCalls(mockedDynamoDbClient.batchWrite),
62
- });
63
- const captureLoggerCalls = () => ({
64
- debug: cloneCalls(mockedLogger.debug),
65
- info: cloneCalls(mockedLogger.info),
66
- warn: cloneCalls(mockedLogger.warn),
67
- error: cloneCalls(mockedLogger.error),
68
- });
69
- const resetMocksForRun = () => {
70
- dateNowSpy?.mockRestore();
71
- dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1782532911813);
72
- jest.clearAllMocks();
73
- mockedDynamoDbClient.safeGet.mockReset();
74
- mockedDynamoDbClient.safePut.mockReset();
75
- mockedDynamoDbClient.safeDelete.mockReset();
76
- mockedDynamoDbClient.safeBatchGet.mockReset();
77
- mockedDynamoDbClient.safeBatchWrite.mockReset();
78
- mockedDynamoDbClient.safeQueryByGSI.mockReset();
79
- mockedDynamoDbClient.batchWrite.mockReset();
80
- };
81
- const buildCachedRecord = (identity) => ({
82
- pk: `identity#${pixelId}#${identity.identityId}`,
83
- pixelId,
84
- identityId: identity.identityId ?? 'missing-id',
85
- response: deepClone(identity),
86
- updatedAt: '2026-01-01T00:00:00.000Z',
87
- });
88
- const runOriginal = async (scenario) => {
89
- resetMocksForRun();
90
- scenario.setupMocks();
91
- const cacheResult = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, deepClone(scenario.incomingIdentity));
92
- return {
93
- cacheResult: normalizeDynamicValues(deepClone(cacheResult)),
94
- dynamoDbCalls: captureDynamoDbCalls(),
95
- loggerCalls: captureLoggerCalls(),
96
- };
97
- };
98
- const runV2 = async (scenario) => {
99
- resetMocksForRun();
100
- scenario.setupMocks();
101
- const result = await IdentityCacheDynamoDbDbService.getIdentityFromCacheV2(pixelId, deepClone(scenario.incomingIdentity));
102
- return {
103
- cacheResult: normalizeDynamicValues(deepClone(result.cacheResult)),
104
- summary: normalizeDynamicValues(deepClone(result.summary)),
105
- dynamoDbCalls: captureDynamoDbCalls(),
106
- loggerCalls: captureLoggerCalls(),
107
- };
108
- };
109
- const expectV2ToMatchOriginal = async (scenario) => {
110
- const originalRun = await runOriginal(scenario);
111
- const v2Run = await runV2(scenario);
112
- expect(v2Run.cacheResult).toEqual(originalRun.cacheResult);
113
- expect(v2Run.dynamoDbCalls).toEqual(originalRun.dynamoDbCalls);
114
- expect(v2Run.loggerCalls).toEqual(originalRun.loggerCalls);
115
- expect(v2Run.summary.outcome).toBe(scenario.expectedOutcome);
116
- scenario.assertSummary?.(v2Run.summary);
117
- return { originalRun, v2Run };
118
- };
119
- describe('IdentityCacheDynamoDbDbService getIdentityFromCache() vs getIdentityFromCacheV2()', () => {
120
- it('matches invalid input behavior', async () => {
121
- await expectV2ToMatchOriginal({
122
- incomingIdentity: null,
123
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.INVALID_INPUT,
124
- setupMocks: () => undefined,
125
- assertSummary: (summary) => {
126
- expect(summary.isCacheStale).toBe(true);
127
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.CACHE_MISS);
128
- },
129
- });
130
- });
131
- it('matches primary identity cache miss behavior', async () => {
132
- await expectV2ToMatchOriginal({
133
- incomingIdentity: {
134
- identityId: 'missing-primary',
135
- traits: { emails: ['missing@example.com'] },
136
- },
137
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_MISS,
138
- setupMocks: () => {
139
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
140
- },
141
- assertSummary: (summary) => {
142
- expect(summary.didLookupByIdentityId).toBe(true);
143
- expect(summary.didLookupByEmail).toBe(false);
144
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.CACHE_MISS);
145
- },
146
- });
147
- });
148
- it('matches cached identity without response behavior', async () => {
149
- await expectV2ToMatchOriginal({
150
- incomingIdentity: {
151
- identityId: 'missing-response',
152
- traits: { emails: ['missing-response@example.com'] },
153
- },
154
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_MISS_NO_RESPONSE,
155
- setupMocks: () => {
156
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
157
- pk: `identity#${pixelId}#missing-response`,
158
- pixelId,
159
- identityId: 'missing-response',
160
- updatedAt: '2026-01-01T00:00:00.000Z',
161
- });
162
- },
163
- assertSummary: (summary) => {
164
- expect(summary.didLookupByIdentityId).toBe(true);
165
- expect(summary.isCacheStale).toBe(true);
166
- },
167
- });
168
- });
169
- it('matches primary cache hit behavior when traits are equal', async () => {
170
- const cachedIdentity = {
171
- identityId: 'fresh-primary',
172
- traits: { emails: ['fresh@example.com'] },
173
- };
174
- await expectV2ToMatchOriginal({
175
- incomingIdentity: deepClone(cachedIdentity),
176
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
177
- setupMocks: () => {
178
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
179
- },
180
- assertSummary: (summary) => {
181
- expect(summary.didLookupByIdentityId).toBe(true);
182
- expect(summary.isDeepEqual).toBe(true);
183
- expect(summary.isCacheStale).toBe(false);
184
- },
185
- });
186
- });
187
- it('matches primary stale behavior for identity traits', async () => {
188
- const cachedIdentity = {
189
- identityId: 'stale-primary',
190
- traits: { emails: ['old@example.com'] },
191
- };
192
- await expectV2ToMatchOriginal({
193
- incomingIdentity: {
194
- identityId: 'stale-primary',
195
- traits: { emails: ['old@example.com', 'new@example.com'] },
196
- },
197
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_STALE,
198
- setupMocks: () => {
199
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
200
- },
201
- assertSummary: (summary) => {
202
- expect(summary.isCacheStale).toBe(true);
203
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.STALE_TRAITS);
204
- expect(summary.skipNeptune).toBe(false);
205
- expect(summary.staleTraits).toContain('emails');
206
- },
207
- });
208
- });
209
- it('matches primary address update behavior', async () => {
210
- const cachedIdentity = {
211
- identityId: 'address-primary',
212
- traits: {
213
- emails: ['address@example.com'],
214
- addresses: [],
215
- },
216
- };
217
- await expectV2ToMatchOriginal({
218
- incomingIdentity: {
219
- identityId: 'address-primary',
220
- traits: {
221
- emails: ['address@example.com'],
222
- addresses: [{ city: 'Boston', s: ADDRESS_INFO_SOURCE.INPUT }],
223
- },
224
- },
225
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_STALE,
226
- setupMocks: () => {
227
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
228
- },
229
- assertSummary: (summary) => {
230
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.ADDRESS_UPDATE);
231
- expect(summary.skipNeptune).toBe(true);
232
- expect(summary.staleTraits).toContain('addresses');
233
- },
234
- });
235
- });
236
- it('matches volatile-only behavior', async () => {
237
- const cachedIdentity = {
238
- identityId: 'volatile-primary',
239
- traits: {
240
- emails: ['volatile@example.com'],
241
- ipAddress: '192.168.1.1',
242
- },
243
- };
244
- await expectV2ToMatchOriginal({
245
- incomingIdentity: {
246
- identityId: 'volatile-primary',
247
- traits: {
248
- emails: ['volatile@example.com'],
249
- ipAddress: '10.0.0.1',
250
- },
251
- },
252
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
253
- setupMocks: () => {
254
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
255
- },
256
- assertSummary: (summary) => {
257
- expect(summary.isCacheStale).toBe(false);
258
- expect(summary.writeReason).toBeUndefined();
259
- },
260
- });
261
- });
262
- it('matches email pointer discovery behavior', async () => {
263
- await expectV2ToMatchOriginal({
264
- incomingIdentity: {
265
- traits: { emails: ['pointer@example.com'] },
266
- },
267
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.EMAIL_POINTER_DISCOVERY,
268
- setupMocks: () => {
269
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
270
- pk: `email#${pixelId}#pointer@example.com`,
271
- pixelId,
272
- identityId: 'discovered-id',
273
- gsi1pk: 'discovered-id',
274
- updatedAt: '2026-01-01T00:00:00.000Z',
275
- });
276
- },
277
- assertSummary: (summary) => {
278
- expect(summary.didLookupByEmail).toBe(true);
279
- expect(summary.discoveredIdentityId).toBe('discovered-id');
280
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.EMAIL_POINTER_DISCOVERY);
281
- },
282
- });
283
- });
284
- it('matches email pointer miss behavior', async () => {
285
- await expectV2ToMatchOriginal({
286
- incomingIdentity: {
287
- traits: { emails: ['missing-pointer@example.com'] },
288
- },
289
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.EMAIL_POINTER_MISS,
290
- setupMocks: () => {
291
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
292
- },
293
- assertSummary: (summary) => {
294
- expect(summary.didLookupByEmail).toBe(true);
295
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.CACHE_MISS);
296
- },
297
- });
298
- });
299
- it('matches no-email miss behavior without reading DynamoDB', async () => {
300
- await expectV2ToMatchOriginal({
301
- incomingIdentity: {
302
- traits: { userIds: ['user-only'] },
303
- },
304
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.EMAIL_POINTER_MISS,
305
- setupMocks: () => undefined,
306
- assertSummary: (summary) => {
307
- expect(summary.didLookupByEmail).toBe(false);
308
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.CACHE_MISS);
309
- },
310
- });
311
- });
312
- it('matches fail-open behavior when primary lookup throws', async () => {
313
- await expectV2ToMatchOriginal({
314
- incomingIdentity: {
315
- identityId: 'throws-primary',
316
- traits: { emails: ['throws@example.com'] },
317
- },
318
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_MISS,
319
- setupMocks: () => {
320
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('dynamo failed'));
321
- },
322
- assertSummary: (summary) => {
323
- expect(summary.errorMessage).toBeUndefined();
324
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.CACHE_MISS);
325
- },
326
- });
327
- });
328
- it('matches fail-open behavior when email lookup throws', async () => {
329
- await expectV2ToMatchOriginal({
330
- incomingIdentity: {
331
- traits: { emails: ['throws-email@example.com'] },
332
- },
333
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.EMAIL_POINTER_MISS,
334
- setupMocks: () => {
335
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('email lookup dynamo failed'));
336
- },
337
- assertSummary: (summary) => {
338
- expect(summary.didLookupByEmail).toBe(true);
339
- expect(summary.errorMessage).toBeUndefined();
340
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.CACHE_MISS);
341
- expect(summary.discoveredIdentityId).toBeUndefined();
342
- },
343
- });
344
- });
345
- it('matches primary cache hit with null traits on cached identity', async () => {
346
- const cachedIdentity = {
347
- identityId: 'null-traits-primary',
348
- traits: null,
349
- };
350
- await expectV2ToMatchOriginal({
351
- incomingIdentity: {
352
- identityId: 'null-traits-primary',
353
- traits: { emails: ['incoming@example.com'] },
354
- },
355
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_STALE,
356
- setupMocks: () => {
357
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
358
- },
359
- assertSummary: (summary) => {
360
- expect(summary.didLookupByIdentityId).toBe(true);
361
- expect(summary.isCacheStale).toBe(true);
362
- expect(summary.cachedIdentity).toEqual(cachedIdentity);
363
- expect(summary.mergedIdentity?.traits?.emails).toContain('incoming@example.com');
364
- },
365
- });
366
- });
367
- it('matches primary cache hit with empty traits object on cached identity', async () => {
368
- const cachedIdentity = {
369
- identityId: 'empty-traits-primary',
370
- traits: {},
371
- };
372
- await expectV2ToMatchOriginal({
373
- incomingIdentity: {
374
- identityId: 'empty-traits-primary',
375
- traits: { emails: ['new@example.com'] },
376
- },
377
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_STALE,
378
- setupMocks: () => {
379
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
380
- },
381
- assertSummary: (summary) => {
382
- expect(summary.didLookupByIdentityId).toBe(true);
383
- expect(summary.isCacheStale).toBe(true);
384
- expect(summary.mergedIdentity?.traits?.emails).toContain('new@example.com');
385
- },
386
- });
387
- });
388
- it('matches primary cache hit when incoming has empty traits (subset of cached)', async () => {
389
- const cachedIdentity = {
390
- identityId: 'incoming-empty-traits',
391
- traits: { emails: ['cached@example.com'], phones: ['+1234567890'] },
392
- };
393
- await expectV2ToMatchOriginal({
394
- incomingIdentity: {
395
- identityId: 'incoming-empty-traits',
396
- traits: {},
397
- },
398
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
399
- setupMocks: () => {
400
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
401
- },
402
- assertSummary: (summary) => {
403
- expect(summary.didLookupByIdentityId).toBe(true);
404
- expect(summary.isCacheStale).toBe(false);
405
- expect(summary.isDeepEqual).toBe(true);
406
- expect(summary.mergedIdentity?.traits?.emails).toContain('cached@example.com');
407
- expect(summary.mergedIdentity?.traits?.phones).toContain('+1234567890');
408
- },
409
- });
410
- });
411
- it('matches timestamp stripping behavior - addresses differ only by timestamp', async () => {
412
- const cachedIdentity = {
413
- identityId: 'timestamp-diff-primary',
414
- traits: {
415
- emails: ['timestamp@example.com'],
416
- addresses: [
417
- { city: 'Boston', s: ADDRESS_INFO_SOURCE.INPUT, cra: 1704067200000, upa: 1704067200000 },
418
- ],
419
- },
420
- };
421
- const incomingIdentity = {
422
- identityId: 'timestamp-diff-primary',
423
- traits: {
424
- emails: ['timestamp@example.com'],
425
- addresses: [
426
- { city: 'Boston', s: ADDRESS_INFO_SOURCE.INPUT, cra: 1717200000000, upa: 1718409600000 },
427
- ],
428
- },
429
- };
430
- await expectV2ToMatchOriginal({
431
- incomingIdentity,
432
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
433
- setupMocks: () => {
434
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
435
- },
436
- assertSummary: (summary) => {
437
- expect(summary.didLookupByIdentityId).toBe(true);
438
- expect(summary.isDeepEqual).toBe(true);
439
- expect(summary.isCacheStale).toBe(false);
440
- expect(summary.writeReason).toBeUndefined();
441
- },
442
- });
443
- });
444
- it('verifies summary fields populated on primary cache hit', async () => {
445
- const cachedIdentity = {
446
- identityId: 'summary-hit-primary',
447
- traits: { emails: ['summary-hit@example.com'], phones: ['+1111111111'] },
448
- };
449
- const incomingIdentity = {
450
- identityId: 'summary-hit-primary',
451
- traits: { emails: ['summary-hit@example.com'] },
452
- };
453
- const { v2Run } = await expectV2ToMatchOriginal({
454
- incomingIdentity,
455
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
456
- setupMocks: () => {
457
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
458
- },
459
- assertSummary: (summary) => {
460
- expect(summary.cachedIdentity).toEqual(cachedIdentity);
461
- expect(summary.incomingIdentity).toEqual(incomingIdentity);
462
- expect(summary.mergedIdentity).toBeDefined();
463
- expect(summary.mergedIdentity?.traits?.emails).toContain('summary-hit@example.com');
464
- expect(summary.mergedIdentity?.traits?.phones).toContain('+1111111111');
465
- expect(summary.resolvedIdentity).toEqual(summary.mergedIdentity);
466
- expect(summary.incomingClick).toBeNull();
467
- expect(summary.cachedClick).toBeNull();
468
- expect(summary.mergedClick).toBeNull();
469
- },
470
- });
471
- const cacheResult = v2Run.cacheResult;
472
- expect(cacheResult.resolvedIdentity).toEqual(v2Run.summary.mergedIdentity);
473
- expect(cacheResult.isCacheStale).toBe(false);
474
- expect(cacheResult.cachedIdentity).toEqual(cachedIdentity);
475
- expect(cacheResult.staleTracker).toBeUndefined();
476
- expect(cacheResult.writeReason).toBeUndefined();
477
- });
478
- it('verifies staleTracker present only when stale', async () => {
479
- const cachedIdentity = {
480
- identityId: 'stale-tracker-primary',
481
- traits: { emails: ['old@example.com'] },
482
- };
483
- const staleIncoming = {
484
- identityId: 'stale-tracker-primary',
485
- traits: { emails: ['old@example.com', 'new@example.com'] },
486
- };
487
- const { v2Run: staleRun } = await expectV2ToMatchOriginal({
488
- incomingIdentity: staleIncoming,
489
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_STALE,
490
- setupMocks: () => {
491
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
492
- },
493
- assertSummary: (summary) => {
494
- expect(summary.isCacheStale).toBe(true);
495
- },
496
- });
497
- const staleCacheResult = staleRun.cacheResult;
498
- expect(staleCacheResult.staleTracker).toEqual(expect.objectContaining({
499
- isCacheStale: true,
500
- isEmailsStale: true,
501
- }));
502
- const freshIncoming = {
503
- identityId: 'stale-tracker-primary',
504
- traits: { emails: ['old@example.com'] },
505
- };
506
- const { v2Run: freshRun } = await expectV2ToMatchOriginal({
507
- incomingIdentity: freshIncoming,
508
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
509
- setupMocks: () => {
510
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
511
- },
512
- assertSummary: (summary) => {
513
- expect(summary.isCacheStale).toBe(false);
514
- },
515
- });
516
- const freshCacheResult = freshRun.cacheResult;
517
- expect(freshCacheResult.staleTracker).toBeUndefined();
518
- });
519
- it('verifies summary fields populated on primary cache stale', async () => {
520
- const cachedIdentity = {
521
- identityId: 'summary-stale-primary',
522
- traits: { emails: ['old@example.com'] },
523
- };
524
- const incomingIdentity = {
525
- identityId: 'summary-stale-primary',
526
- traits: { emails: ['old@example.com', 'new@example.com'], phones: ['+1234567890'] },
527
- };
528
- await expectV2ToMatchOriginal({
529
- incomingIdentity,
530
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_STALE,
531
- setupMocks: () => {
532
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
533
- },
534
- assertSummary: (summary) => {
535
- expect(summary.cachedIdentity).toEqual(cachedIdentity);
536
- expect(summary.incomingIdentity).toEqual(incomingIdentity);
537
- expect(summary.mergedIdentity).toBeDefined();
538
- expect(summary.mergedIdentity?.traits?.emails).toContain('old@example.com');
539
- expect(summary.mergedIdentity?.traits?.emails).toContain('new@example.com');
540
- expect(summary.mergedIdentity?.traits?.phones).toContain('+1234567890');
541
- expect(summary.resolvedIdentity).toEqual(summary.mergedIdentity);
542
- expect(summary.staleTier).toBeDefined();
543
- expect(summary.staleTraits.length).toBeGreaterThan(0);
544
- },
545
- });
546
- });
547
- it('matches cache miss summary with correct flags', async () => {
548
- await expectV2ToMatchOriginal({
549
- incomingIdentity: {
550
- traits: { userIds: ['user-only-no-email'] },
551
- },
552
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.EMAIL_POINTER_MISS,
553
- setupMocks: () => undefined,
554
- assertSummary: (summary) => {
555
- expect(summary.didLookupByIdentityId).toBe(false);
556
- expect(summary.didLookupByEmail).toBe(false);
557
- expect(summary.isCacheStale).toBe(true);
558
- expect(summary.writeReason).toBe(CACHE_WRITE_REASON.CACHE_MISS);
559
- expect(summary.cachedIdentity).toBeUndefined();
560
- expect(summary.mergedIdentity).toBeUndefined();
561
- expect(summary.resolvedIdentity).toBeUndefined();
562
- expect(summary.discoveredIdentityId).toBeUndefined();
563
- expect(summary.staleTraits).toEqual([]);
564
- },
565
- });
566
- });
567
- it('matches email pointer discovery summary fields', async () => {
568
- const incomingIdentity = {
569
- traits: { emails: ['discovery-summary@example.com'], phones: ['+9999999999'] },
570
- };
571
- await expectV2ToMatchOriginal({
572
- incomingIdentity,
573
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.EMAIL_POINTER_DISCOVERY,
574
- setupMocks: () => {
575
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
576
- pk: `email#${pixelId}#discovery-summary@example.com`,
577
- pixelId,
578
- identityId: 'discovered-summary-id',
579
- gsi1pk: 'discovered-summary-id',
580
- updatedAt: '2026-01-01T00:00:00.000Z',
581
- });
582
- },
583
- assertSummary: (summary) => {
584
- expect(summary.didLookupByEmail).toBe(true);
585
- expect(summary.discoveredIdentityId).toBe('discovered-summary-id');
586
- expect(summary.resolvedIdentity?.identityId).toBe('discovered-summary-id');
587
- expect(summary.resolvedIdentity?.traits?.emails).toContain('discovery-summary@example.com');
588
- expect(summary.resolvedIdentity?.traits?.phones).toContain('+9999999999');
589
- expect(summary.cachedIdentity).toBeUndefined();
590
- expect(summary.mergedIdentity).toBeUndefined();
591
- },
592
- });
593
- });
594
- it('matches volatile-only with source field changes treated as cache hit', async () => {
595
- const cachedIdentity = {
596
- identityId: 'volatile-source-primary',
597
- traits: {
598
- emails: ['volatile-source@example.com'],
599
- addresses: [{ city: 'Boston', s: ADDRESS_INFO_SOURCE.INPUT }],
600
- ipAddress: '192.168.1.1',
601
- userAgent: 'Mozilla/5.0 Old',
602
- },
603
- };
604
- const incomingIdentity = {
605
- identityId: 'volatile-source-primary',
606
- traits: {
607
- emails: ['volatile-source@example.com'],
608
- addresses: [{ city: 'Boston', s: ADDRESS_INFO_SOURCE.INPUT }],
609
- ipAddress: '10.0.0.1',
610
- userAgent: 'Mozilla/5.0 New',
611
- },
612
- };
613
- await expectV2ToMatchOriginal({
614
- incomingIdentity,
615
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
616
- setupMocks: () => {
617
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
618
- },
619
- assertSummary: (summary) => {
620
- expect(summary.isCacheStale).toBe(false);
621
- expect(summary.writeReason).toBeUndefined();
622
- expect(summary.skipNeptune).toBeUndefined();
623
- },
624
- });
625
- });
626
- it('matches skipNeptune undefined when cache is fresh', async () => {
627
- const cachedIdentity = {
628
- identityId: 'skip-neptune-fresh',
629
- traits: { emails: ['fresh@example.com'] },
630
- };
631
- await expectV2ToMatchOriginal({
632
- incomingIdentity: deepClone(cachedIdentity),
633
- expectedOutcome: IDENTITY_CACHE_LOOKUP_OUTCOME.PRIMARY_CACHE_HIT,
634
- setupMocks: () => {
635
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(buildCachedRecord(cachedIdentity));
636
- },
637
- assertSummary: (summary) => {
638
- expect(summary.isCacheStale).toBe(false);
639
- expect(summary.skipNeptune).toBeUndefined();
640
- },
641
- });
642
- });
643
- });
644
- //# sourceMappingURL=identity-cache-dynamodb-db-service-v2-comparison.spec.js.map