@adtrackify/at-service-common 4.0.5 → 4.0.6

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 (79) hide show
  1. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts +1 -0
  2. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +646 -0
  3. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +1 -0
  4. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts +1 -0
  5. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +327 -0
  6. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +1 -0
  7. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +250 -108
  8. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +1 -1
  9. package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js +37 -36
  10. package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
  11. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.d.ts +1 -0
  12. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js +567 -0
  13. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -0
  14. package/dist/cjs/services/db/identity-cache-dynamodb-service/constants.d.ts +3 -0
  15. package/dist/cjs/services/db/identity-cache-dynamodb-service/constants.js +7 -0
  16. package/dist/cjs/services/db/identity-cache-dynamodb-service/constants.js.map +1 -0
  17. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.d.ts +14 -0
  18. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js +251 -0
  19. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js.map +1 -0
  20. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +24 -0
  21. package/dist/cjs/services/db/{identity-cache-dynamodb-service.js → identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js} +221 -343
  22. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -0
  23. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +22 -0
  24. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +375 -0
  25. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -0
  26. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.d.ts +4 -0
  27. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js +21 -0
  28. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js.map +1 -0
  29. package/dist/cjs/services/db/identity-cache-dynamodb-service/key-builders.d.ts +12 -0
  30. package/dist/cjs/services/db/identity-cache-dynamodb-service/key-builders.js +32 -0
  31. package/dist/cjs/services/db/identity-cache-dynamodb-service/key-builders.js.map +1 -0
  32. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.d.ts +205 -0
  33. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js +35 -0
  34. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js.map +1 -0
  35. package/dist/cjs/services/db/index.d.ts +4 -1
  36. package/dist/cjs/services/db/index.js +4 -1
  37. package/dist/cjs/services/db/index.js.map +1 -1
  38. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts +1 -0
  39. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +644 -0
  40. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +1 -0
  41. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts +1 -0
  42. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +325 -0
  43. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +1 -0
  44. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +250 -108
  45. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +1 -1
  46. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js +37 -36
  47. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
  48. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.d.ts +1 -0
  49. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js +565 -0
  50. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -0
  51. package/dist/esm/services/db/identity-cache-dynamodb-service/constants.d.ts +3 -0
  52. package/dist/esm/services/db/identity-cache-dynamodb-service/constants.js +4 -0
  53. package/dist/esm/services/db/identity-cache-dynamodb-service/constants.js.map +1 -0
  54. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.d.ts +14 -0
  55. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js +234 -0
  56. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js.map +1 -0
  57. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +24 -0
  58. package/dist/esm/services/db/{identity-cache-dynamodb-service.js → identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js} +199 -321
  59. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -0
  60. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +22 -0
  61. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +368 -0
  62. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -0
  63. package/dist/esm/services/db/identity-cache-dynamodb-service/index.d.ts +4 -0
  64. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js +5 -0
  65. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js.map +1 -0
  66. package/dist/esm/services/db/identity-cache-dynamodb-service/key-builders.d.ts +12 -0
  67. package/dist/esm/services/db/identity-cache-dynamodb-service/key-builders.js +24 -0
  68. package/dist/esm/services/db/identity-cache-dynamodb-service/key-builders.js.map +1 -0
  69. package/dist/esm/services/db/identity-cache-dynamodb-service/types.d.ts +205 -0
  70. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js +32 -0
  71. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js.map +1 -0
  72. package/dist/esm/services/db/index.d.ts +4 -1
  73. package/dist/esm/services/db/index.js +4 -1
  74. package/dist/esm/services/db/index.js.map +1 -1
  75. package/package.json +2 -1
  76. package/dist/cjs/services/db/identity-cache-dynamodb-service.d.ts +0 -44
  77. package/dist/cjs/services/db/identity-cache-dynamodb-service.js.map +0 -1
  78. package/dist/esm/services/db/identity-cache-dynamodb-service.d.ts +0 -44
  79. package/dist/esm/services/db/identity-cache-dynamodb-service.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { buildEmailPk as buildTrackingTypesEmailPk, buildForcePurgePk as buildTrackingTypesForcePurgePk, buildIdentityMapPk as buildTrackingTypesIdentityMapPk, buildIdentityPk as buildTrackingTypesIdentityPk, buildUserIdPk as buildTrackingTypesUserIdPk, } from '@adtrackify/at-tracking-event-types';
1
2
  const mockInvokeFunction = jest.fn();
2
3
  jest.mock('../../clients/index.js', () => ({
3
4
  DynamoDbClient: {
@@ -21,10 +22,13 @@ jest.mock('../../helpers/index.js', () => ({
21
22
  error: jest.fn(),
22
23
  },
23
24
  }));
24
- import { IdentityCacheDynamoDbService, } from '../../services/db/identity-cache-dynamodb-service.js';
25
+ import { buildEmailPk, buildForcePurgePk, buildIdentityMapPk, buildIdentityPk, buildUserIdPk, } from '../../services/db/identity-cache-dynamodb-service/key-builders.js';
26
+ import { IdentityGatewayService, } from '../../services/db/identity-cache-dynamodb-service/identity-gateway-service.js';
27
+ import { IDENTITY_CACHE_WITH_CACHING_OUTCOME, } from '../../services/db/identity-cache-dynamodb-service/types.js';
28
+ import { IdentityCacheDynamoDbDbService } from '../../services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js';
25
29
  import { DynamoDbClient } from '../../clients/index.js';
26
30
  const mockedDynamoDbClient = DynamoDbClient;
27
- describe('IdentityCacheDynamoDbService', () => {
31
+ describe('IdentityGatewayService', () => {
28
32
  beforeEach(() => {
29
33
  jest.clearAllMocks();
30
34
  mockedDynamoDbClient.safeGet.mockReset();
@@ -35,7 +39,31 @@ describe('IdentityCacheDynamoDbService', () => {
35
39
  mockedDynamoDbClient.safeQueryByGSI.mockReset();
36
40
  mockedDynamoDbClient.batchWrite.mockReset();
37
41
  mockInvokeFunction.mockReset();
38
- IdentityCacheDynamoDbService.lambdaInvokeClient = undefined;
42
+ IdentityGatewayService.lambdaInvokeClient = undefined;
43
+ });
44
+ describe('identity cache key builders', () => {
45
+ it('should build primary identity cache keys', () => {
46
+ expect(buildIdentityPk('pixel123', 'identity456')).toBe('identity#pixel123#identity456');
47
+ });
48
+ it('should build lowercase email pointer keys', () => {
49
+ expect(buildEmailPk('pixel123', 'Test@Email.COM')).toBe('email#pixel123#test@email.com');
50
+ });
51
+ it('should build trimmed user id pointer keys', () => {
52
+ expect(buildUserIdPk('pixel123', ' user-1 ')).toBe('user_id#pixel123#user-1');
53
+ });
54
+ it('should build identity map keys', () => {
55
+ expect(buildIdentityMapPk('pixel123', 'identity456')).toBe('identity_map#pixel123#identity456');
56
+ });
57
+ it('should build force purge keys', () => {
58
+ expect(buildForcePurgePk('pixel123', 'identity456')).toBe('force_purge#pixel123#identity456');
59
+ });
60
+ it('should match the installed tracking-event-types key builder behavior during migration', () => {
61
+ expect(buildIdentityPk('pixel123', 'identity456')).toBe(buildTrackingTypesIdentityPk('pixel123', 'identity456'));
62
+ expect(buildEmailPk('pixel123', 'Test@Email.COM')).toBe(buildTrackingTypesEmailPk('pixel123', 'Test@Email.COM'));
63
+ expect(buildUserIdPk('pixel123', ' user-1 ')).toBe(buildTrackingTypesUserIdPk('pixel123', ' user-1 '));
64
+ expect(buildIdentityMapPk('pixel123', 'identity456')).toBe(buildTrackingTypesIdentityMapPk('pixel123', 'identity456'));
65
+ expect(buildForcePurgePk('pixel123', 'identity456')).toBe(buildTrackingTypesForcePurgePk('pixel123', 'identity456'));
66
+ });
39
67
  });
40
68
  describe('getIdentityWithCaching', () => {
41
69
  const pixelId = 'pixel123';
@@ -57,7 +85,7 @@ describe('IdentityCacheDynamoDbService', () => {
57
85
  updatedAt: new Date().toISOString(),
58
86
  };
59
87
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
60
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
88
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
61
89
  expect(result).toBeDefined();
62
90
  expect(result?.identityId).toBe('identity456');
63
91
  expect(mockInvokeFunction).not.toHaveBeenCalled();
@@ -79,7 +107,7 @@ describe('IdentityCacheDynamoDbService', () => {
79
107
  },
80
108
  }),
81
109
  });
82
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
110
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
83
111
  expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, {
84
112
  pixelId,
85
113
  context: { identity: incomingIdentity },
@@ -97,7 +125,7 @@ describe('IdentityCacheDynamoDbService', () => {
97
125
  statusCode: 200,
98
126
  body: JSON.stringify({ identity: undefined }),
99
127
  });
100
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
128
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
101
129
  expect(result).toEqual(incomingIdentity);
102
130
  });
103
131
  });
@@ -127,7 +155,7 @@ describe('IdentityCacheDynamoDbService', () => {
127
155
  },
128
156
  }),
129
157
  });
130
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
158
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
131
159
  expect(mockInvokeFunction).toHaveBeenCalled();
132
160
  expect(result?.identityId).toBe('identity456');
133
161
  expect(result?.traits?.emails).toContain('new@email.com');
@@ -152,7 +180,7 @@ describe('IdentityCacheDynamoDbService', () => {
152
180
  statusCode: 500,
153
181
  body: 'Internal Server Error',
154
182
  });
155
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
183
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
156
184
  expect(result).toBeDefined();
157
185
  expect(result?.identityId).toBe('identity456');
158
186
  });
@@ -172,7 +200,7 @@ describe('IdentityCacheDynamoDbService', () => {
172
200
  },
173
201
  }),
174
202
  });
175
- await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
203
+ await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
176
204
  expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalled();
177
205
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
178
206
  const identityPks = writtenItems.map((item) => item.pk);
@@ -187,18 +215,18 @@ describe('IdentityCacheDynamoDbService', () => {
187
215
  statusCode: 200,
188
216
  body: JSON.stringify({ identity: undefined }),
189
217
  });
190
- await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
218
+ await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
191
219
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
192
220
  });
193
221
  });
194
222
  describe('error handling (fail-open)', () => {
195
223
  it('should return undefined when pixelId is missing', async () => {
196
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(null, { identityId: 'test-id' }, lambdaArn);
224
+ const result = await IdentityGatewayService.getIdentityWithCaching(null, { identityId: 'test-id' }, lambdaArn);
197
225
  expect(result).toBeUndefined();
198
226
  expect(mockInvokeFunction).not.toHaveBeenCalled();
199
227
  });
200
228
  it('should return undefined when incomingIdentity is missing', async () => {
201
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, null, lambdaArn);
229
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, null, lambdaArn);
202
230
  expect(result).toBeUndefined();
203
231
  expect(mockInvokeFunction).not.toHaveBeenCalled();
204
232
  });
@@ -209,7 +237,7 @@ describe('IdentityCacheDynamoDbService', () => {
209
237
  };
210
238
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
211
239
  mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda invocation failed'));
212
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
240
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
213
241
  expect(result).toEqual(incomingIdentity);
214
242
  });
215
243
  it('should not throw when cache write fails after Neptune success', async () => {
@@ -227,7 +255,7 @@ describe('IdentityCacheDynamoDbService', () => {
227
255
  }),
228
256
  });
229
257
  mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('DynamoDB write failed'));
230
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
258
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
231
259
  expect(result?.identityId).toBe('neptune-resolved-id');
232
260
  });
233
261
  });
@@ -238,7 +266,7 @@ describe('IdentityCacheDynamoDbService', () => {
238
266
  const pixelId = 'pixel123';
239
267
  const identityId = 'identity456';
240
268
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
241
- await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, { identityId });
269
+ await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, { identityId });
242
270
  expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `identity#${pixelId}#${identityId}`);
243
271
  });
244
272
  });
@@ -247,7 +275,7 @@ describe('IdentityCacheDynamoDbService', () => {
247
275
  const pixelId = 'pixel123';
248
276
  const email = 'Test@Email.COM';
249
277
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
250
- await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
278
+ await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
251
279
  traits: { emails: [email] },
252
280
  });
253
281
  expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#test@email.com`);
@@ -258,7 +286,7 @@ describe('IdentityCacheDynamoDbService', () => {
258
286
  const pixelId = 'pixel123';
259
287
  const userId = ' user789 ';
260
288
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
261
- await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
289
+ await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
262
290
  traits: { userIds: [userId] },
263
291
  });
264
292
  expect(mockedDynamoDbClient.safeBatchGet).not.toHaveBeenCalled();
@@ -281,7 +309,7 @@ describe('IdentityCacheDynamoDbService', () => {
281
309
  updatedAt: new Date().toISOString(),
282
310
  };
283
311
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
284
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
312
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
285
313
  identityId,
286
314
  traits: { emails: ['test@email.com'] },
287
315
  });
@@ -291,7 +319,7 @@ describe('IdentityCacheDynamoDbService', () => {
291
319
  });
292
320
  it('should return cache miss when identity not found', async () => {
293
321
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
294
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
322
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
295
323
  identityId: 'unknown-id',
296
324
  });
297
325
  expect(result.resolvedIdentity).toBeUndefined();
@@ -299,7 +327,7 @@ describe('IdentityCacheDynamoDbService', () => {
299
327
  });
300
328
  it('should NOT fallback to secondary keys when identityId lookup misses', async () => {
301
329
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
302
- await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
330
+ await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
303
331
  identityId: 'unknown-id',
304
332
  traits: { emails: ['test@email.com'] },
305
333
  });
@@ -318,7 +346,7 @@ describe('IdentityCacheDynamoDbService', () => {
318
346
  updatedAt: new Date().toISOString(),
319
347
  };
320
348
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
321
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
349
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
322
350
  identityId,
323
351
  traits: { emails: ['old@email.com', 'new@email.com'] },
324
352
  });
@@ -336,7 +364,7 @@ describe('IdentityCacheDynamoDbService', () => {
336
364
  updatedAt: new Date().toISOString(),
337
365
  };
338
366
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
339
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
367
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
340
368
  traits: { emails: ['test@email.com'], userIds: ['user123'] },
341
369
  });
342
370
  expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#test@email.com`);
@@ -354,7 +382,7 @@ describe('IdentityCacheDynamoDbService', () => {
354
382
  updatedAt: new Date().toISOString(),
355
383
  };
356
384
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
357
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
385
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
358
386
  traits: { emails: ['test@email.com'], userIds: ['user123'] },
359
387
  });
360
388
  expect(result.resolvedIdentity).toBeDefined();
@@ -363,14 +391,14 @@ describe('IdentityCacheDynamoDbService', () => {
363
391
  });
364
392
  it('should return cache miss when no secondary keys match', async () => {
365
393
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
366
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
394
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
367
395
  traits: { emails: ['unknown@email.com'] },
368
396
  });
369
397
  expect(result.resolvedIdentity).toBeUndefined();
370
398
  expect(result.isCacheStale).toBe(true);
371
399
  });
372
400
  it('should return cache miss when no emails to lookup', async () => {
373
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
401
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
374
402
  traits: { userIds: ['user123'] },
375
403
  });
376
404
  expect(result.resolvedIdentity).toBeUndefined();
@@ -378,7 +406,7 @@ describe('IdentityCacheDynamoDbService', () => {
378
406
  expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
379
407
  });
380
408
  it('should return cache miss when empty traits', async () => {
381
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
409
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
382
410
  traits: {},
383
411
  });
384
412
  expect(result.resolvedIdentity).toBeUndefined();
@@ -388,7 +416,7 @@ describe('IdentityCacheDynamoDbService', () => {
388
416
  describe('error handling (fail-open)', () => {
389
417
  it('should return cache miss on safeGet error', async () => {
390
418
  mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
391
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
419
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
392
420
  identityId: 'test-id',
393
421
  });
394
422
  expect(result.resolvedIdentity).toBeUndefined();
@@ -396,21 +424,21 @@ describe('IdentityCacheDynamoDbService', () => {
396
424
  });
397
425
  it('should return cache miss on safeGet error for email lookup', async () => {
398
426
  mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
399
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
427
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
400
428
  traits: { emails: ['test@email.com'] },
401
429
  });
402
430
  expect(result.resolvedIdentity).toBeUndefined();
403
431
  expect(result.isCacheStale).toBe(true);
404
432
  });
405
433
  it('should return cache miss for null pixelId', async () => {
406
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(null, {
434
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(null, {
407
435
  identityId: 'test-id',
408
436
  });
409
437
  expect(result.resolvedIdentity).toBeUndefined();
410
438
  expect(result.isCacheStale).toBe(true);
411
439
  });
412
440
  it('should return cache miss for null incomingIdentity', async () => {
413
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, null);
441
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, null);
414
442
  expect(result.resolvedIdentity).toBeUndefined();
415
443
  expect(result.isCacheStale).toBe(true);
416
444
  });
@@ -426,7 +454,7 @@ describe('IdentityCacheDynamoDbService', () => {
426
454
  userIds: ['user1', 'user2'],
427
455
  },
428
456
  };
429
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
457
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
430
458
  expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalledTimes(1);
431
459
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
432
460
  expect(writtenItems).toHaveLength(3);
@@ -438,12 +466,12 @@ describe('IdentityCacheDynamoDbService', () => {
438
466
  expect(pks).not.toContain(`user_id#${pixelId}#user2`);
439
467
  const identityItem = writtenItems.find((item) => item.pk.startsWith('identity#'));
440
468
  expect(identityItem).toBeDefined();
441
- expect(identityItem.response).toBeDefined();
442
- expect(identityItem.response.identityId).toBe('identity456');
469
+ expect(identityItem?.response).toBeDefined();
470
+ expect(identityItem?.response.identityId).toBe('identity456');
443
471
  const emailItem = writtenItems.find((item) => item.pk.startsWith('email#'));
444
472
  expect(emailItem).toBeDefined();
445
- expect(emailItem.response).toBeUndefined();
446
- expect(emailItem.identityId).toBe('identity456');
473
+ expect(emailItem?.response).toBeUndefined();
474
+ expect(emailItem?.identityId).toBe('identity456');
447
475
  });
448
476
  it('should not throw on safeBatchWrite error (fail-open)', async () => {
449
477
  mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('DynamoDB error'));
@@ -451,14 +479,14 @@ describe('IdentityCacheDynamoDbService', () => {
451
479
  identityId: 'identity456',
452
480
  traits: { emails: ['test@email.com'] },
453
481
  };
454
- await expect(IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity)).resolves.toBeUndefined();
482
+ await expect(IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity)).resolves.toBeUndefined();
455
483
  });
456
484
  it('should return early for missing identityId', async () => {
457
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, {});
485
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, {});
458
486
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
459
487
  });
460
488
  it('should return early for missing pixelId', async () => {
461
- await IdentityCacheDynamoDbService.updateIdentityCache(null, {
489
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(null, {
462
490
  identityId: 'test-id',
463
491
  });
464
492
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
@@ -467,7 +495,7 @@ describe('IdentityCacheDynamoDbService', () => {
467
495
  const identity = {
468
496
  identityId: 'identity456',
469
497
  };
470
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
498
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
471
499
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
472
500
  expect(writtenItems).toHaveLength(1);
473
501
  });
@@ -476,7 +504,7 @@ describe('IdentityCacheDynamoDbService', () => {
476
504
  identityId: 'identity456',
477
505
  traits: { emails: ['test@email.com'] },
478
506
  };
479
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
507
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
480
508
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
481
509
  for (const item of writtenItems) {
482
510
  expect(item.gsi1pk).toBe('identity456');
@@ -493,33 +521,33 @@ describe('IdentityCacheDynamoDbService', () => {
493
521
  updatedAt: new Date().toISOString(),
494
522
  };
495
523
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(mockMap);
496
- const result = await IdentityCacheDynamoDbService.getIdentityMap('pixel123', 'identity456');
524
+ const result = await IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', 'identity456');
497
525
  expect(result).toEqual(mockMap);
498
526
  expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', 'identity_map#pixel123#identity456');
499
527
  });
500
528
  it('should return undefined when not found', async () => {
501
529
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
502
- const result = await IdentityCacheDynamoDbService.getIdentityMap('pixel123', 'identity456');
530
+ const result = await IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', 'identity456');
503
531
  expect(result).toBeUndefined();
504
532
  });
505
533
  it('should return undefined for missing params (fail-open)', async () => {
506
- const result = await IdentityCacheDynamoDbService.getIdentityMap('', '');
534
+ const result = await IdentityCacheDynamoDbDbService.getIdentityMap('', '');
507
535
  expect(result).toBeUndefined();
508
536
  expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
509
537
  });
510
538
  it('should return undefined when only pixelId is missing', async () => {
511
- const result = await IdentityCacheDynamoDbService.getIdentityMap('', 'identity456');
539
+ const result = await IdentityCacheDynamoDbDbService.getIdentityMap('', 'identity456');
512
540
  expect(result).toBeUndefined();
513
541
  expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
514
542
  });
515
543
  it('should return undefined when only identityId is missing', async () => {
516
- const result = await IdentityCacheDynamoDbService.getIdentityMap('pixel123', '');
544
+ const result = await IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', '');
517
545
  expect(result).toBeUndefined();
518
546
  expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
519
547
  });
520
548
  it('should return undefined on safeGet error (fail-open)', async () => {
521
549
  mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
522
- const result = await IdentityCacheDynamoDbService.getIdentityMap('pixel123', 'identity456');
550
+ const result = await IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', 'identity456');
523
551
  expect(result).toBeUndefined();
524
552
  });
525
553
  });
@@ -528,28 +556,28 @@ describe('IdentityCacheDynamoDbService', () => {
528
556
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
529
557
  pk: 'force_purge#pixel123#identity456',
530
558
  });
531
- const result = await IdentityCacheDynamoDbService.getForcePurgeFlag('pixel123', 'identity456');
559
+ const result = await IdentityCacheDynamoDbDbService.getForcePurgeFlag('pixel123', 'identity456');
532
560
  expect(result).toBe(true);
533
561
  });
534
562
  it('should return false when flag not found', async () => {
535
563
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
536
- const result = await IdentityCacheDynamoDbService.getForcePurgeFlag('pixel123', 'identity456');
564
+ const result = await IdentityCacheDynamoDbDbService.getForcePurgeFlag('pixel123', 'identity456');
537
565
  expect(result).toBe(false);
538
566
  });
539
567
  it('should return false on error (fail-open)', async () => {
540
568
  mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
541
- const result = await IdentityCacheDynamoDbService.getForcePurgeFlag('pixel123', 'identity456');
569
+ const result = await IdentityCacheDynamoDbDbService.getForcePurgeFlag('pixel123', 'identity456');
542
570
  expect(result).toBe(false);
543
571
  });
544
572
  it('should return false for missing params', async () => {
545
- const result = await IdentityCacheDynamoDbService.getForcePurgeFlag('', '');
573
+ const result = await IdentityCacheDynamoDbDbService.getForcePurgeFlag('', '');
546
574
  expect(result).toBe(false);
547
575
  expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
548
576
  });
549
577
  });
550
578
  describe('setForcePurgeFlag', () => {
551
579
  it('should set flag with TTL', async () => {
552
- await IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456');
580
+ await IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456');
553
581
  expect(mockedDynamoDbClient.safePut).toHaveBeenCalledTimes(1);
554
582
  const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
555
583
  expect(putItem.pk).toBe('force_purge#pixel123#identity456');
@@ -558,13 +586,13 @@ describe('IdentityCacheDynamoDbService', () => {
558
586
  expect(putItem.ttl).toBeGreaterThan(0);
559
587
  });
560
588
  it('should accept custom TTL', async () => {
561
- await IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456', 3600);
589
+ await IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456', 3600);
562
590
  const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
563
591
  expect(putItem.ttl).toBeGreaterThan(0);
564
592
  });
565
593
  it('should include createdAt timestamp', async () => {
566
594
  const before = new Date().toISOString();
567
- await IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456');
595
+ await IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456');
568
596
  const after = new Date().toISOString();
569
597
  const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
570
598
  expect(putItem.createdAt).toBeDefined();
@@ -572,41 +600,41 @@ describe('IdentityCacheDynamoDbService', () => {
572
600
  expect(putItem.createdAt <= after).toBe(true);
573
601
  });
574
602
  it('should return early when pixelId is missing', async () => {
575
- await IdentityCacheDynamoDbService.setForcePurgeFlag('', 'identity456');
603
+ await IdentityCacheDynamoDbDbService.setForcePurgeFlag('', 'identity456');
576
604
  expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
577
605
  });
578
606
  it('should return early when identityId is missing', async () => {
579
- await IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', '');
607
+ await IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', '');
580
608
  expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
581
609
  });
582
610
  it('should not throw on error (fail-open)', async () => {
583
611
  mockedDynamoDbClient.safePut.mockRejectedValueOnce(new Error('DynamoDB error'));
584
- await expect(IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
612
+ await expect(IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
585
613
  });
586
614
  });
587
615
  describe('putIdentityMap', () => {
588
616
  it('should write identity map', async () => {
589
- await IdentityCacheDynamoDbService.putIdentityMap('pixel123', 'identity456', ['linked1', 'linked2']);
617
+ await IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', 'identity456', ['linked1', 'linked2']);
590
618
  expect(mockedDynamoDbClient.safePut).toHaveBeenCalledTimes(1);
591
619
  const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
592
620
  expect(putItem.pk).toBe('identity_map#pixel123#identity456');
593
621
  expect(putItem.linkedIdentities).toEqual(['linked1', 'linked2']);
594
622
  });
595
623
  it('should return early for missing params', async () => {
596
- await IdentityCacheDynamoDbService.putIdentityMap('', '', []);
624
+ await IdentityCacheDynamoDbDbService.putIdentityMap('', '', []);
597
625
  expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
598
626
  });
599
627
  it('should return early when only pixelId is missing', async () => {
600
- await IdentityCacheDynamoDbService.putIdentityMap('', 'identity456', ['linked1']);
628
+ await IdentityCacheDynamoDbDbService.putIdentityMap('', 'identity456', ['linked1']);
601
629
  expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
602
630
  });
603
631
  it('should return early when only identityId is missing', async () => {
604
- await IdentityCacheDynamoDbService.putIdentityMap('pixel123', '', ['linked1']);
632
+ await IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', '', ['linked1']);
605
633
  expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
606
634
  });
607
635
  it('should include updatedAt timestamp', async () => {
608
636
  const before = new Date().toISOString();
609
- await IdentityCacheDynamoDbService.putIdentityMap('pixel123', 'identity456', ['linked1']);
637
+ await IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', 'identity456', ['linked1']);
610
638
  const after = new Date().toISOString();
611
639
  const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
612
640
  expect(putItem.updatedAt).toBeDefined();
@@ -615,51 +643,51 @@ describe('IdentityCacheDynamoDbService', () => {
615
643
  });
616
644
  it('should not throw on safePut error (fail-open)', async () => {
617
645
  mockedDynamoDbClient.safePut.mockRejectedValueOnce(new Error('DynamoDB error'));
618
- await expect(IdentityCacheDynamoDbService.putIdentityMap('pixel123', 'identity456', ['linked1'])).resolves.toBeUndefined();
646
+ await expect(IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', 'identity456', ['linked1'])).resolves.toBeUndefined();
619
647
  });
620
648
  });
621
649
  describe('deleteIdentityMap', () => {
622
650
  it('should delete identity map', async () => {
623
- await IdentityCacheDynamoDbService.deleteIdentityMap('pixel123', 'identity456');
651
+ await IdentityCacheDynamoDbDbService.deleteIdentityMap('pixel123', 'identity456');
624
652
  expect(mockedDynamoDbClient.safeDelete).toHaveBeenCalledWith(expect.any(String), 'pk', 'identity_map#pixel123#identity456');
625
653
  });
626
654
  it('should return early for missing params', async () => {
627
- await IdentityCacheDynamoDbService.deleteIdentityMap('', '');
655
+ await IdentityCacheDynamoDbDbService.deleteIdentityMap('', '');
628
656
  expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
629
657
  });
630
658
  it('should return early when only pixelId is missing', async () => {
631
- await IdentityCacheDynamoDbService.deleteIdentityMap('', 'identity456');
659
+ await IdentityCacheDynamoDbDbService.deleteIdentityMap('', 'identity456');
632
660
  expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
633
661
  });
634
662
  it('should return early when only identityId is missing', async () => {
635
- await IdentityCacheDynamoDbService.deleteIdentityMap('pixel123', '');
663
+ await IdentityCacheDynamoDbDbService.deleteIdentityMap('pixel123', '');
636
664
  expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
637
665
  });
638
666
  it('should not throw on safeDelete error (fail-open)', async () => {
639
667
  mockedDynamoDbClient.safeDelete.mockRejectedValueOnce(new Error('DynamoDB error'));
640
- await expect(IdentityCacheDynamoDbService.deleteIdentityMap('pixel123', 'identity456')).resolves.toBeUndefined();
668
+ await expect(IdentityCacheDynamoDbDbService.deleteIdentityMap('pixel123', 'identity456')).resolves.toBeUndefined();
641
669
  });
642
670
  });
643
671
  describe('deleteForcePurgeFlag', () => {
644
672
  it('should delete force purge flag', async () => {
645
- await IdentityCacheDynamoDbService.deleteForcePurgeFlag('pixel123', 'identity456');
673
+ await IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('pixel123', 'identity456');
646
674
  expect(mockedDynamoDbClient.safeDelete).toHaveBeenCalledWith(expect.any(String), 'pk', 'force_purge#pixel123#identity456');
647
675
  });
648
676
  it('should return early for missing params', async () => {
649
- await IdentityCacheDynamoDbService.deleteForcePurgeFlag('', '');
677
+ await IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('', '');
650
678
  expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
651
679
  });
652
680
  it('should return early when only pixelId is missing', async () => {
653
- await IdentityCacheDynamoDbService.deleteForcePurgeFlag('', 'identity456');
681
+ await IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('', 'identity456');
654
682
  expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
655
683
  });
656
684
  it('should return early when only identityId is missing', async () => {
657
- await IdentityCacheDynamoDbService.deleteForcePurgeFlag('pixel123', '');
685
+ await IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('pixel123', '');
658
686
  expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
659
687
  });
660
688
  it('should not throw on safeDelete error (fail-open)', async () => {
661
689
  mockedDynamoDbClient.safeDelete.mockRejectedValueOnce(new Error('DynamoDB error'));
662
- await expect(IdentityCacheDynamoDbService.deleteForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
690
+ await expect(IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
663
691
  });
664
692
  });
665
693
  describe('deleteIdentityCache', () => {
@@ -678,12 +706,12 @@ describe('IdentityCacheDynamoDbService', () => {
678
706
  identityId: 'identity456',
679
707
  traits: { emails: ['test@email.com', 'other@email.com'] },
680
708
  };
681
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
709
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
682
710
  expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalled();
683
711
  expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
684
712
  });
685
713
  it('should return early for missing pixelId', async () => {
686
- await IdentityCacheDynamoDbService.deleteIdentityCache(null, {}, {});
714
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(null, {}, {});
687
715
  expect(mockedDynamoDbClient.safeQueryByGSI).not.toHaveBeenCalled();
688
716
  });
689
717
  it('should handle different identityIds in incoming vs resolved', async () => {
@@ -697,19 +725,19 @@ describe('IdentityCacheDynamoDbService', () => {
697
725
  identityId: 'resolved-id',
698
726
  traits: { emails: ['test@email.com'] },
699
727
  };
700
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
728
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
701
729
  expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalledTimes(2);
702
730
  });
703
731
  it('should not throw on batchWrite error (fail-open)', async () => {
704
732
  mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([{ pk: `identity#${pixelId}#identity456`, pixelId }]);
705
733
  mockedDynamoDbClient.batchWrite.mockRejectedValueOnce(new Error('DynamoDB error'));
706
- await expect(IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
734
+ await expect(IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
707
735
  });
708
736
  it('should deduplicate keys when same items returned from GSI query', async () => {
709
737
  const duplicateItem = { pk: `identity#${pixelId}#identity456`, pixelId };
710
738
  mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([duplicateItem, duplicateItem]);
711
739
  mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
712
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
740
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
713
741
  expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
714
742
  const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0];
715
743
  expect(batchWriteCall).toBeDefined();
@@ -732,7 +760,7 @@ describe('IdentityCacheDynamoDbService', () => {
732
760
  updatedAt: new Date().toISOString(),
733
761
  };
734
762
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
735
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
763
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
736
764
  identityId: 'identity456',
737
765
  traits: {
738
766
  emails: ['incoming@email.com'],
@@ -756,7 +784,7 @@ describe('IdentityCacheDynamoDbService', () => {
756
784
  updatedAt: new Date().toISOString(),
757
785
  };
758
786
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
759
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
787
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
760
788
  identityId: 'identity456',
761
789
  traits: {
762
790
  emails: ['cached@email.com', 'new@email.com'],
@@ -779,7 +807,7 @@ describe('IdentityCacheDynamoDbService', () => {
779
807
  updatedAt: new Date().toISOString(),
780
808
  };
781
809
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
782
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
810
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
783
811
  identityId: 'identity456',
784
812
  traits: {
785
813
  emails: ['a@email.com'],
@@ -797,7 +825,7 @@ describe('IdentityCacheDynamoDbService', () => {
797
825
  emails: ['first@email.com', 'second@email.com', 'third@email.com', 'fourth@email.com'],
798
826
  },
799
827
  };
800
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
828
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
801
829
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
802
830
  expect(writtenItems).toHaveLength(5);
803
831
  const emailPks = writtenItems
@@ -815,7 +843,7 @@ describe('IdentityCacheDynamoDbService', () => {
815
843
  emails: ['valid@email.com', null, undefined, '', 'another@email.com'],
816
844
  },
817
845
  };
818
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
846
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
819
847
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
820
848
  const emailItems = writtenItems.filter((item) => item.pk.startsWith('email#'));
821
849
  expect(emailItems).toHaveLength(2);
@@ -827,7 +855,7 @@ describe('IdentityCacheDynamoDbService', () => {
827
855
  identityId: 'identity456',
828
856
  traits: { emails: [] },
829
857
  };
830
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
858
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
831
859
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
832
860
  expect(writtenItems).toHaveLength(1);
833
861
  expect(writtenItems[0].pk).toBe(`identity#${pixelId}#identity456`);
@@ -839,7 +867,7 @@ describe('IdentityCacheDynamoDbService', () => {
839
867
  emails: ['UPPERCASE@EMAIL.COM', 'MixedCase@Email.Com'],
840
868
  },
841
869
  };
842
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
870
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
843
871
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
844
872
  const emailPks = writtenItems
845
873
  .filter((item) => item.pk.startsWith('email#'))
@@ -855,7 +883,7 @@ describe('IdentityCacheDynamoDbService', () => {
855
883
  identityId: 'identity456',
856
884
  traits: null,
857
885
  };
858
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
886
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
859
887
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
860
888
  expect(writtenItems).toHaveLength(1);
861
889
  });
@@ -864,12 +892,12 @@ describe('IdentityCacheDynamoDbService', () => {
864
892
  identityId: 'identity456',
865
893
  traits: undefined,
866
894
  };
867
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
895
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
868
896
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
869
897
  expect(writtenItems).toHaveLength(1);
870
898
  });
871
899
  it('should return cache miss for identity with null traits in lookup', async () => {
872
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
900
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
873
901
  traits: null,
874
902
  });
875
903
  expect(result.resolvedIdentity).toBeUndefined();
@@ -877,7 +905,7 @@ describe('IdentityCacheDynamoDbService', () => {
877
905
  expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
878
906
  });
879
907
  it('should handle identity with undefined emails in traits', async () => {
880
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
908
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
881
909
  traits: { userIds: ['user123'] },
882
910
  });
883
911
  expect(result.resolvedIdentity).toBeUndefined();
@@ -899,7 +927,7 @@ describe('IdentityCacheDynamoDbService', () => {
899
927
  updatedAt: new Date().toISOString(),
900
928
  };
901
929
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(oldFormatEmailRecord);
902
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
930
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
903
931
  traits: { emails: ['test@email.com'] },
904
932
  });
905
933
  expect(result.resolvedIdentity).toBeDefined();
@@ -915,7 +943,7 @@ describe('IdentityCacheDynamoDbService', () => {
915
943
  updatedAt: new Date().toISOString(),
916
944
  };
917
945
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(newFormatPointerRecord);
918
- const result = await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
946
+ const result = await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
919
947
  traits: { emails: ['test@email.com'] },
920
948
  });
921
949
  expect(result.resolvedIdentity).toBeDefined();
@@ -941,7 +969,7 @@ describe('IdentityCacheDynamoDbService', () => {
941
969
  },
942
970
  }),
943
971
  });
944
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
972
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
945
973
  expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
946
974
  pixelId,
947
975
  context: {
@@ -968,7 +996,7 @@ describe('IdentityCacheDynamoDbService', () => {
968
996
  identityId: 'identity456',
969
997
  traits: { emails: ['resolved1@email.com', 'resolved2@email.com', 'resolved3@email.com'] },
970
998
  };
971
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
999
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
972
1000
  expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
973
1001
  const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0][0];
974
1002
  const deleteRequests = batchWriteCall.RequestItems[Object.keys(batchWriteCall.RequestItems)[0]];
@@ -985,7 +1013,7 @@ describe('IdentityCacheDynamoDbService', () => {
985
1013
  { pk: `identity#${pixelId}#identity456`, pixelId },
986
1014
  ]);
987
1015
  mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
988
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456', traits: { emails: [] } }, { identityId: 'identity456' });
1016
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456', traits: { emails: [] } }, { identityId: 'identity456' });
989
1017
  expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
990
1018
  });
991
1019
  it('should handle missing traits in delete', async () => {
@@ -993,7 +1021,7 @@ describe('IdentityCacheDynamoDbService', () => {
993
1021
  { pk: `identity#${pixelId}#identity456`, pixelId },
994
1022
  ]);
995
1023
  mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
996
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
1024
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
997
1025
  expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
998
1026
  });
999
1027
  it('should not delete items from other pixelIds', async () => {
@@ -1003,7 +1031,7 @@ describe('IdentityCacheDynamoDbService', () => {
1003
1031
  { pk: `identity#${otherPixelId}#identity456`, pixelId: otherPixelId },
1004
1032
  ]);
1005
1033
  mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1006
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
1034
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
1007
1035
  const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0][0];
1008
1036
  const deleteRequests = batchWriteCall.RequestItems[Object.keys(batchWriteCall.RequestItems)[0]];
1009
1037
  const deletedPks = deleteRequests.map((req) => req.DeleteRequest.Key.pk);
@@ -1022,24 +1050,24 @@ describe('IdentityCacheDynamoDbService', () => {
1022
1050
  ];
1023
1051
  mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue(mockResults);
1024
1052
  mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1025
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId }, { identityId });
1053
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId }, { identityId });
1026
1054
  expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalledWith(expect.any(String), expect.any(String), 'gsi1pk', identityId);
1027
1055
  });
1028
1056
  it('should handle GSI1 query returning empty results', async () => {
1029
1057
  mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([]);
1030
- await IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'unknown-id' }, { identityId: 'unknown-id' });
1058
+ await IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'unknown-id' }, { identityId: 'unknown-id' });
1031
1059
  expect(mockedDynamoDbClient.batchWrite).not.toHaveBeenCalled();
1032
1060
  });
1033
1061
  it('should handle GSI1 query error gracefully (fail-open)', async () => {
1034
1062
  mockedDynamoDbClient.safeQueryByGSI.mockRejectedValueOnce(new Error('GSI query failed'));
1035
- await expect(IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
1063
+ await expect(IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
1036
1064
  });
1037
1065
  });
1038
1066
  describe('email pointer lookups', () => {
1039
1067
  const pixelId = 'pixel123';
1040
1068
  it('should only lookup first email (optimization)', async () => {
1041
1069
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1042
- await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
1070
+ await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
1043
1071
  traits: { emails: ['first@email.com', 'second@email.com', 'third@email.com'] },
1044
1072
  });
1045
1073
  expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledTimes(1);
@@ -1047,7 +1075,7 @@ describe('IdentityCacheDynamoDbService', () => {
1047
1075
  });
1048
1076
  it('should handle whitespace in email during lookup', async () => {
1049
1077
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1050
- await IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
1078
+ await IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
1051
1079
  traits: { emails: [' test@email.com '] },
1052
1080
  });
1053
1081
  expect(mockedDynamoDbClient.safeGet).toHaveBeenCalled();
@@ -1063,7 +1091,7 @@ describe('IdentityCacheDynamoDbService', () => {
1063
1091
  };
1064
1092
  mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB down'));
1065
1093
  mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda down'));
1066
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1094
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1067
1095
  expect(result).toEqual(incomingIdentity);
1068
1096
  });
1069
1097
  it('should continue when cache write fails after Neptune success', async () => {
@@ -1075,7 +1103,7 @@ describe('IdentityCacheDynamoDbService', () => {
1075
1103
  }),
1076
1104
  });
1077
1105
  mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('Write failed'));
1078
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
1106
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
1079
1107
  expect(result?.identityId).toBe('resolved-id');
1080
1108
  });
1081
1109
  it('should return incoming identity when Neptune returns non-200', async () => {
@@ -1088,7 +1116,7 @@ describe('IdentityCacheDynamoDbService', () => {
1088
1116
  statusCode: 500,
1089
1117
  body: 'Internal Server Error',
1090
1118
  });
1091
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1119
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1092
1120
  expect(result).toEqual(incomingIdentity);
1093
1121
  });
1094
1122
  it('should return incoming identity when Neptune returns malformed JSON (fail-open)', async () => {
@@ -1101,14 +1129,128 @@ describe('IdentityCacheDynamoDbService', () => {
1101
1129
  statusCode: 200,
1102
1130
  body: 'not valid json',
1103
1131
  });
1104
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1132
+ const result = await IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1105
1133
  expect(result).toEqual(incomingIdentity);
1106
1134
  });
1107
1135
  it('should return undefined only when both inputs are invalid', async () => {
1108
- const result = await IdentityCacheDynamoDbService.getIdentityWithCaching(null, null, lambdaArn);
1136
+ const result = await IdentityGatewayService.getIdentityWithCaching(null, null, lambdaArn);
1109
1137
  expect(result).toBeUndefined();
1110
1138
  });
1111
1139
  });
1140
+ describe('getIdentityWithCachingV2', () => {
1141
+ const pixelId = 'pixel123';
1142
+ const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
1143
+ it('should return invalid input summary without resolving', async () => {
1144
+ const result = await IdentityGatewayService.getIdentityWithCachingV2('', { identityId: 'identity456' }, lambdaArn);
1145
+ expect(result.identity).toBeUndefined();
1146
+ expect(result.summary).toEqual(expect.objectContaining({
1147
+ outcome: IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT,
1148
+ didReadCache: false,
1149
+ didCallNeptune: false,
1150
+ didWriteCache: false,
1151
+ }));
1152
+ expect(mockInvokeFunction).not.toHaveBeenCalled();
1153
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1154
+ });
1155
+ it('should return fresh cache hit summary without invoking Neptune', async () => {
1156
+ const incomingIdentity = {
1157
+ identityId: 'identity456',
1158
+ traits: { emails: ['test@email.com'] },
1159
+ };
1160
+ const cachedResponse = {
1161
+ pk: `identity#${pixelId}#identity456`,
1162
+ pixelId,
1163
+ identityId: 'identity456',
1164
+ response: {
1165
+ identityId: 'identity456',
1166
+ traits: { emails: ['test@email.com'] },
1167
+ },
1168
+ updatedAt: new Date().toISOString(),
1169
+ };
1170
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1171
+ const result = await IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1172
+ expect(result.identity?.identityId).toBe('identity456');
1173
+ expect(result.summary).toEqual(expect.objectContaining({
1174
+ outcome: IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT,
1175
+ pixelId,
1176
+ identityId: 'identity456',
1177
+ didReadCache: true,
1178
+ didCallNeptune: false,
1179
+ didWriteCache: false,
1180
+ cachedIdentity: cachedResponse.response,
1181
+ incomingIdentity,
1182
+ resolvedIdentity: expect.objectContaining({ identityId: 'identity456' }),
1183
+ }));
1184
+ expect(mockInvokeFunction).not.toHaveBeenCalled();
1185
+ });
1186
+ it('should return Neptune write-back summary on cache miss', async () => {
1187
+ const incomingIdentity = {
1188
+ identityId: 'identity456',
1189
+ traits: { emails: ['test@email.com'] },
1190
+ };
1191
+ const neptuneIdentity = {
1192
+ identityId: 'resolved-identity',
1193
+ traits: { emails: ['test@email.com', 'resolved@email.com'] },
1194
+ };
1195
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1196
+ mockInvokeFunction.mockResolvedValueOnce({
1197
+ statusCode: 200,
1198
+ body: JSON.stringify({ identity: neptuneIdentity }),
1199
+ });
1200
+ const result = await IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1201
+ expect(result.identity).toEqual(neptuneIdentity);
1202
+ expect(result.summary).toEqual(expect.objectContaining({
1203
+ outcome: IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_WRITE_BACK,
1204
+ pixelId,
1205
+ identityId: 'resolved-identity',
1206
+ didReadCache: true,
1207
+ didCallNeptune: true,
1208
+ didWriteCache: true,
1209
+ incomingIdentity,
1210
+ identityForNeptune: incomingIdentity,
1211
+ neptuneIdentity,
1212
+ traitChanges: { reason: { resolved: 'no cached identity' } },
1213
+ }));
1214
+ expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalled();
1215
+ });
1216
+ it('should return Neptune skip-write summary when Neptune matches cached identity', async () => {
1217
+ const incomingIdentity = {
1218
+ identityId: 'identity456',
1219
+ traits: { emails: ['old@email.com', 'new@email.com'] },
1220
+ };
1221
+ const cachedIdentity = {
1222
+ identityId: 'identity456',
1223
+ traits: { emails: ['old@email.com'] },
1224
+ };
1225
+ const cachedResponse = {
1226
+ pk: `identity#${pixelId}#identity456`,
1227
+ pixelId,
1228
+ identityId: 'identity456',
1229
+ response: cachedIdentity,
1230
+ updatedAt: new Date().toISOString(),
1231
+ };
1232
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1233
+ mockInvokeFunction.mockResolvedValueOnce({
1234
+ statusCode: 200,
1235
+ body: JSON.stringify({ identity: cachedIdentity }),
1236
+ });
1237
+ const result = await IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1238
+ expect(result.identity).toEqual(cachedIdentity);
1239
+ expect(result.summary).toEqual(expect.objectContaining({
1240
+ outcome: IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_SKIP_WRITE,
1241
+ pixelId,
1242
+ identityId: 'identity456',
1243
+ didReadCache: true,
1244
+ didCallNeptune: true,
1245
+ didWriteCache: false,
1246
+ cachedIdentity,
1247
+ incomingIdentity,
1248
+ identityForNeptune: expect.objectContaining({ identityId: 'identity456' }),
1249
+ neptuneIdentity: cachedIdentity,
1250
+ }));
1251
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1252
+ });
1253
+ });
1112
1254
  describe('gsi1pk attribute on all records', () => {
1113
1255
  const pixelId = 'pixel123';
1114
1256
  it('should include gsi1pk on identity record', async () => {
@@ -1116,18 +1258,18 @@ describe('IdentityCacheDynamoDbService', () => {
1116
1258
  identityId: 'identity456',
1117
1259
  traits: { emails: ['test@email.com'] },
1118
1260
  };
1119
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
1261
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
1120
1262
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
1121
1263
  const identityItem = writtenItems.find((item) => item.pk.startsWith('identity#'));
1122
1264
  expect(identityItem).toBeDefined();
1123
- expect(identityItem.gsi1pk).toBe('identity456');
1265
+ expect(identityItem?.gsi1pk).toBe('identity456');
1124
1266
  });
1125
1267
  it('should include gsi1pk on all email pointer records', async () => {
1126
1268
  const identity = {
1127
1269
  identityId: 'identity456',
1128
1270
  traits: { emails: ['email1@test.com', 'email2@test.com'] },
1129
1271
  };
1130
- await IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
1272
+ await IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
1131
1273
  const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
1132
1274
  const emailItems = writtenItems.filter((item) => item.pk.startsWith('email#'));
1133
1275
  for (const emailItem of emailItems) {