@adtrackify/at-service-common 4.0.13 → 4.0.14

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 (71) 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 +83 -344
  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-service.spec.js +335 -1261
  8. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -1
  9. package/dist/cjs/clients/internal-api/identity-client.d.ts +12 -2
  10. package/dist/cjs/clients/internal-api/identity-client.js +59 -13
  11. package/dist/cjs/clients/internal-api/identity-client.js.map +1 -1
  12. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +1 -7
  13. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js +0 -305
  14. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -1
  15. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +10 -9
  16. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +116 -321
  17. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -1
  18. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.d.ts +0 -1
  19. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js +0 -1
  20. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js.map +1 -1
  21. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.d.ts +9 -157
  22. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js +1 -15
  23. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js.map +1 -1
  24. package/dist/esm/__tests__/identity-cache/helpers.spec.js +26 -27
  25. package/dist/esm/__tests__/identity-cache/helpers.spec.js.map +1 -1
  26. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js +83 -344
  27. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
  28. package/dist/esm/__tests__/identity-cache/identity-client.spec.js +358 -0
  29. package/dist/esm/__tests__/identity-cache/identity-client.spec.js.map +1 -0
  30. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js +335 -1261
  31. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -1
  32. package/dist/esm/clients/internal-api/identity-client.d.ts +12 -2
  33. package/dist/esm/clients/internal-api/identity-client.js +60 -14
  34. package/dist/esm/clients/internal-api/identity-client.js.map +1 -1
  35. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +1 -7
  36. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js +1 -303
  37. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -1
  38. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +10 -9
  39. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +119 -321
  40. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -1
  41. package/dist/esm/services/db/identity-cache-dynamodb-service/index.d.ts +0 -1
  42. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js +0 -1
  43. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js.map +1 -1
  44. package/dist/esm/services/db/identity-cache-dynamodb-service/types.d.ts +9 -157
  45. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js +0 -14
  46. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js.map +1 -1
  47. package/package.json +2 -2
  48. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +0 -646
  49. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +0 -1
  50. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +0 -325
  51. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +0 -1
  52. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +0 -1
  53. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +0 -1286
  54. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
  55. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.d.ts +0 -26
  56. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js +0 -252
  57. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js.map +0 -1
  58. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts +0 -1
  59. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +0 -644
  60. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +0 -1
  61. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts +0 -1
  62. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +0 -323
  63. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +0 -1
  64. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +0 -1
  65. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +0 -1284
  66. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
  67. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.d.ts +0 -26
  68. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js +0 -235
  69. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js.map +0 -1
  70. /package/dist/cjs/__tests__/identity-cache/{identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts → identity-client.spec.d.ts} +0 -0
  71. /package/dist/{cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts → esm/__tests__/identity-cache/identity-client.spec.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const mockInvokeFunction = jest.fn();
3
+ const mockLambdaInvokeFunction = jest.fn();
4
4
  const mockBuildAndSendMessage = jest.fn();
5
5
  const mockSQSClientConstructor = jest.fn();
6
6
  jest.mock('../../clients/index.js', () => ({
@@ -14,7 +14,7 @@ jest.mock('../../clients/index.js', () => ({
14
14
  batchWrite: jest.fn(),
15
15
  },
16
16
  LambdaInvokeClient: jest.fn().mockImplementation(() => ({
17
- invokeFunction: mockInvokeFunction,
17
+ invokeFunction: mockLambdaInvokeFunction,
18
18
  })),
19
19
  SQSClient: jest.fn().mockImplementation((_region, _accountId, _queueName) => {
20
20
  mockSQSClientConstructor(_region, _accountId, _queueName);
@@ -32,6 +32,12 @@ jest.mock('../../helpers/index.js', () => ({
32
32
  error: jest.fn(),
33
33
  },
34
34
  }));
35
+ const mockGetIdentity = jest.fn();
36
+ jest.mock('../../clients/internal-api/identity-client.js', () => ({
37
+ IdentityClient: jest.fn().mockImplementation(() => ({
38
+ getIdentity: mockGetIdentity,
39
+ })),
40
+ }));
35
41
  const identity_gateway_service_js_1 = require("../../services/db/identity-cache-dynamodb-service/identity-gateway-service.js");
36
42
  const types_js_1 = require("../../services/db/identity-cache-dynamodb-service/types.js");
37
43
  const index_js_1 = require("../../clients/index.js");
@@ -51,13 +57,14 @@ describe('IdentityGatewayService new features', () => {
51
57
  mockedDynamoDbClient.safeBatchWrite.mockReset();
52
58
  mockedDynamoDbClient.safeQueryByGSI.mockReset();
53
59
  mockedDynamoDbClient.batchWrite.mockReset();
54
- mockInvokeFunction.mockReset();
60
+ mockGetIdentity.mockReset();
55
61
  mockBuildAndSendMessage.mockReset();
56
62
  mockSQSClientConstructor.mockReset();
57
63
  mockedSQSClient.mockClear();
58
- identity_gateway_service_js_1.IdentityGatewayService.lambdaInvokeClient = undefined;
64
+ identity_gateway_service_js_1.IdentityGatewayService.identityClient = undefined;
59
65
  identity_gateway_service_js_1.IdentityGatewayService.sqsClient = undefined;
60
66
  identity_gateway_service_js_1.IdentityGatewayService.sqsQueueUrl = undefined;
67
+ identity_gateway_service_js_1.IdentityGatewayService.configure({ identifyPrivateLambdaArn: lambdaArn });
61
68
  });
62
69
  describe('configure()', () => {
63
70
  it('should configure with direct queue URL', () => {
@@ -272,7 +279,7 @@ describe('IdentityGatewayService new features', () => {
272
279
  }));
273
280
  });
274
281
  });
275
- describe('getIdentityWithCachingV2() with skipCacheWrite option', () => {
282
+ describe('resolveIdentityWithCaching() with skipCacheWrite option', () => {
276
283
  const incomingIdentity = {
277
284
  identityId: 'identity456',
278
285
  traits: { emails: ['test@email.com'] },
@@ -280,53 +287,38 @@ describe('IdentityGatewayService new features', () => {
280
287
  describe('default behavior (no options)', () => {
281
288
  it('should write to cache when Neptune returns new identity', async () => {
282
289
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
283
- mockInvokeFunction.mockResolvedValueOnce({
284
- statusCode: 200,
285
- body: JSON.stringify({
286
- identity: {
287
- identityId: 'resolved-identity',
288
- traits: { emails: ['test@email.com', 'resolved@email.com'] },
289
- },
290
- }),
290
+ mockGetIdentity.mockResolvedValueOnce({
291
+ identityId: 'resolved-identity',
292
+ traits: { emails: ['test@email.com', 'resolved@email.com'] },
291
293
  });
292
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
293
- expect(result.summary.didWriteCache).toBe(false);
294
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
295
+ expect(result.didWriteCache).toBe(false);
294
296
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
295
297
  });
296
298
  });
297
299
  describe('skipCacheWrite: false', () => {
298
300
  it('should write to cache (same as default)', async () => {
299
301
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
300
- mockInvokeFunction.mockResolvedValueOnce({
301
- statusCode: 200,
302
- body: JSON.stringify({
303
- identity: {
304
- identityId: 'resolved-identity',
305
- traits: { emails: ['test@email.com'] },
306
- },
307
- }),
302
+ mockGetIdentity.mockResolvedValueOnce({
303
+ identityId: 'resolved-identity',
304
+ traits: { emails: ['test@email.com'] },
308
305
  });
309
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn, { skipCacheWrite: false });
310
- expect(result.summary.didWriteCache).toBe(false);
306
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
307
+ expect(result.didWriteCache).toBe(false);
311
308
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
312
309
  });
313
310
  });
314
311
  describe('skipCacheWrite: true', () => {
315
312
  it('should NOT write to cache on Neptune write-back path', async () => {
316
313
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
317
- mockInvokeFunction.mockResolvedValueOnce({
318
- statusCode: 200,
319
- body: JSON.stringify({
320
- identity: {
321
- identityId: 'resolved-identity',
322
- traits: { emails: ['test@email.com', 'resolved@email.com'] },
323
- },
324
- }),
314
+ mockGetIdentity.mockResolvedValueOnce({
315
+ identityId: 'resolved-identity',
316
+ traits: { emails: ['test@email.com', 'resolved@email.com'] },
325
317
  });
326
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn, { skipCacheWrite: true });
318
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
327
319
  expect(result.identity?.identityId).toBe('resolved-identity');
328
- expect(result.summary.didWriteCache).toBe(false);
329
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
320
+ expect(result.didWriteCache).toBe(false);
321
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
330
322
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
331
323
  });
332
324
  it('should enqueue async update on address update path (never writes sync)', async () => {
@@ -357,808 +349,149 @@ describe('IdentityGatewayService new features', () => {
357
349
  addresses: [{ city: 'New City', upa: 1717200000000 }],
358
350
  },
359
351
  };
360
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, identityWithNewAddress, lambdaArn, { skipCacheWrite: true });
361
- if (result.summary.outcome === types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC) {
362
- expect(result.summary.didWriteCache).toBe(false);
363
- expect(result.summary.didEnqueueAsync).toBe(true);
352
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, identityWithNewAddress);
353
+ if (result.outcome === types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC) {
354
+ expect(result.didWriteCache).toBe(false);
355
+ expect(result.didEnqueueAsync).toBe(true);
364
356
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
365
357
  expect(mockBuildAndSendMessage).toHaveBeenCalled();
366
358
  }
367
359
  });
368
360
  it('should still return resolved identity when skipCacheWrite is true', async () => {
369
361
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
370
- mockInvokeFunction.mockResolvedValueOnce({
371
- statusCode: 200,
372
- body: JSON.stringify({
373
- identity: {
374
- identityId: 'neptune-resolved-id',
375
- traits: { emails: ['test@email.com', 'new@email.com'] },
376
- },
377
- }),
362
+ mockGetIdentity.mockResolvedValueOnce({
363
+ identityId: 'neptune-resolved-id',
364
+ traits: { emails: ['test@email.com', 'new@email.com'] },
378
365
  });
379
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn, { skipCacheWrite: true });
366
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
380
367
  expect(result.identity).toBeDefined();
381
368
  expect(result.identity?.identityId).toBe('neptune-resolved-id');
382
- expect(result.identity?.traits?.emails).toContain('new@email.com');
383
- });
384
- it('should return fresh cache hit without writing (skipCacheWrite irrelevant for hits)', async () => {
385
- const cachedResponse = {
386
- pk: `identity#${pixelId}#identity456`,
387
- pixelId,
388
- identityId: 'identity456',
389
- response: {
390
- identityId: 'identity456',
391
- traits: { emails: ['test@email.com'] },
392
- },
393
- updatedAt: new Date().toISOString(),
394
- };
395
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
396
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn, { skipCacheWrite: true });
397
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
398
- expect(result.summary.didWriteCache).toBe(false);
399
- expect(mockInvokeFunction).not.toHaveBeenCalled();
400
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
401
- });
402
- });
403
- describe('address update path - always enqueues async (never writes sync)', () => {
404
- it('should enqueue async (not write cache) when address update is detected', async () => {
405
- identity_gateway_service_js_1.IdentityGatewayService.configure({
406
- awsRegion: 'us-east-1',
407
- awsAccountId: '123456789',
408
- asyncUpdateQueueName: 'test-queue',
409
- });
410
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'test-msg-id' });
411
- const cachedResponse = {
412
- pk: `identity#${pixelId}#identity456`,
413
- pixelId,
414
- identityId: 'identity456',
415
- response: {
416
- identityId: 'identity456',
417
- traits: {
418
- emails: ['test@email.com'],
419
- addresses: [{ city: 'Old City', upa: 1704067200000 }],
420
- },
421
- },
422
- updatedAt: new Date().toISOString(),
423
- };
424
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
425
- const identityWithNewAddress = {
426
- identityId: 'identity456',
427
- traits: {
428
- emails: ['test@email.com'],
429
- addresses: [{ city: 'New City', upa: 1717200000000 }],
430
- },
431
- };
432
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, identityWithNewAddress, lambdaArn, { skipCacheWrite: false });
433
- if (result.summary.outcome === types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC) {
434
- expect(result.summary.didWriteCache).toBe(false);
435
- expect(result.summary.didEnqueueAsync).toBe(true);
436
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
437
- expect(mockBuildAndSendMessage).toHaveBeenCalledWith('identify.updateAsync', expect.objectContaining({
438
- pixelId,
439
- incomingIdentityId: 'identity456',
440
- }));
441
- }
442
- });
443
- it('should return merged identity without waiting for cache write', async () => {
444
- identity_gateway_service_js_1.IdentityGatewayService.configure({
445
- awsRegion: 'us-east-1',
446
- awsAccountId: '123456789',
447
- asyncUpdateQueueName: 'test-queue',
448
- });
449
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'test-msg-id' });
450
- const cachedResponse = {
451
- pk: `identity#${pixelId}#identity456`,
452
- pixelId,
453
- identityId: 'identity456',
454
- response: {
455
- identityId: 'identity456',
456
- traits: {
457
- emails: ['test@email.com'],
458
- addresses: [],
459
- },
460
- },
461
- updatedAt: new Date().toISOString(),
462
- };
463
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
464
- const identityWithNewAddress = {
465
- identityId: 'identity456',
466
- traits: {
467
- emails: ['test@email.com'],
468
- addresses: [{ city: 'New City', firstName: 'John' }],
469
- },
470
- };
471
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, identityWithNewAddress, lambdaArn);
472
- expect(result.identity).toBeDefined();
473
- expect(result.identity?.identityId).toBe('identity456');
474
- expect(result.summary.didSkipNeptune).toBe(true);
475
- expect(result.summary.didCallNeptune).toBe(false);
476
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
477
- });
478
- it('should set didEnqueueAsync to false when SQS enqueue fails', async () => {
479
- identity_gateway_service_js_1.IdentityGatewayService.configure({
480
- awsRegion: 'us-east-1',
481
- awsAccountId: '123456789',
482
- asyncUpdateQueueName: 'test-queue',
483
- });
484
- mockBuildAndSendMessage.mockRejectedValueOnce(new Error('SQS error'));
485
- const cachedResponse = {
486
- pk: `identity#${pixelId}#identity456`,
487
- pixelId,
488
- identityId: 'identity456',
489
- response: {
490
- identityId: 'identity456',
491
- traits: {
492
- emails: ['test@email.com'],
493
- addresses: [],
494
- },
495
- },
496
- updatedAt: new Date().toISOString(),
497
- };
498
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
499
- const identityWithNewAddress = {
500
- identityId: 'identity456',
501
- traits: {
502
- emails: ['test@email.com'],
503
- addresses: [{ city: 'New City', firstName: 'John' }],
504
- },
505
- };
506
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, identityWithNewAddress, lambdaArn);
507
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
508
- expect(result.identity).toBeDefined();
509
- expect(result.summary.didEnqueueAsync).toBe(false);
510
- });
511
- });
512
- });
513
- describe('edge cases', () => {
514
- it('should return invalid input when pixelId is undefined', async () => {
515
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(undefined, { identityId: 'test' }, lambdaArn);
516
- expect(result.identity).toBeUndefined();
517
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT);
518
- expect(result.summary.didReadCache).toBe(false);
519
- expect(result.summary.didWriteCache).toBe(false);
520
- });
521
- it('should return invalid input when incomingIdentity is null', async () => {
522
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, null, lambdaArn);
523
- expect(result.identity).toBeUndefined();
524
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT);
525
- });
526
- it('should return invalid input when both pixelId and identity are missing', async () => {
527
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2('', undefined, lambdaArn);
528
- expect(result.identity).toBeUndefined();
529
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT);
530
- });
531
- it('should gracefully fallback when cache read fails (fail-open pattern)', async () => {
532
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('Unexpected DynamoDB error'));
533
- mockInvokeFunction.mockResolvedValueOnce({
534
- statusCode: 200,
535
- body: JSON.stringify({
536
- identity: {
537
- identityId: 'neptune-resolved',
538
- traits: { emails: ['test@email.com'] },
539
- },
540
- }),
541
- });
542
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'test-id', traits: { emails: ['test@email.com'] } }, lambdaArn);
543
- expect(result.identity).toBeDefined();
544
- expect(result.identity?.identityId).toBe('neptune-resolved');
545
- expect(result.summary.didReadCache).toBe(true);
546
- expect(result.summary.didCallNeptune).toBe(true);
547
- });
548
- it('should reject identity with undefined identityId in enqueue', async () => {
549
- identity_gateway_service_js_1.IdentityGatewayService.configure({
550
- awsRegion: 'us-east-1',
551
- awsAccountId: '123456789',
552
- asyncUpdateQueueName: 'test-queue',
553
- });
554
- const identity = { traits: { emails: ['test@email.com'] } };
555
- const context = { identity };
556
- const result = await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
557
- pixelId,
558
- identity,
559
- context,
560
- reason: 'anonymous user',
561
- });
562
- expect(result.success).toBe(false);
563
- expect(result.error).toBe('Missing required pixelId or identityId');
564
- expect(mockBuildAndSendMessage).not.toHaveBeenCalled();
565
- expect(mockedLogger.warn).toHaveBeenCalledWith('IdentityGateway - enqueueAsyncIdentityUpdate: missing required params', expect.objectContaining({
566
- pixelId,
567
- identityId: undefined,
568
- }));
569
- });
570
- });
571
- describe('safeWriteBackToCache behavior with skipCacheWrite', () => {
572
- it('should not call safeWriteBackToCache when skipCacheWrite is true and Neptune returns new identity', async () => {
573
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
574
- mockInvokeFunction.mockResolvedValueOnce({
575
- statusCode: 200,
576
- body: JSON.stringify({
577
- identity: {
578
- identityId: 'new-resolved-identity',
579
- traits: { emails: ['test@email.com'] },
580
- },
581
- }),
582
- });
583
- await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'original-id', traits: { emails: ['test@email.com'] } }, lambdaArn, { skipCacheWrite: true });
584
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
585
- });
586
- it('should call safeWriteBackToCache when skipCacheWrite is false and Neptune returns new identity', async () => {
587
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
588
- mockInvokeFunction.mockResolvedValueOnce({
589
- statusCode: 200,
590
- body: JSON.stringify({
591
- identity: {
592
- identityId: 'new-resolved-identity',
593
- traits: { emails: ['test@email.com'] },
594
- },
595
- }),
596
- });
597
- await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'original-id', traits: { emails: ['test@email.com'] } }, lambdaArn, { skipCacheWrite: false });
598
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
599
- });
600
- });
601
- describe('summary fields consistency', () => {
602
- it('should include all required summary fields in getIdentityWithCachingV2 result', async () => {
603
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
604
- mockInvokeFunction.mockResolvedValueOnce({
605
- statusCode: 200,
606
- body: JSON.stringify({
607
- identity: {
608
- identityId: 'resolved-id',
609
- traits: { emails: ['test@email.com'] },
610
- },
611
- }),
612
- });
613
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'incoming-id', traits: { emails: ['test@email.com'] } }, lambdaArn, { skipCacheWrite: true });
614
- expect(result.summary).toEqual(expect.objectContaining({
615
- outcome: expect.any(String),
616
- pixelId,
617
- identityId: 'resolved-id',
618
- didReadCache: true,
619
- didCallNeptune: true,
620
- didWriteCache: false,
621
- didSkipNeptune: false,
622
- didFallbackToIncoming: false,
623
- didEnqueueAsync: false,
624
- staleTraits: expect.any(Array),
625
- }));
626
- });
627
- it('should include traitChanges in summary when identity changes', async () => {
628
- const cachedResponse = {
629
- pk: `identity#${pixelId}#identity456`,
630
- pixelId,
631
- identityId: 'identity456',
632
- response: {
633
- identityId: 'identity456',
634
- traits: { emails: ['old@email.com'] },
635
- },
636
- updatedAt: new Date().toISOString(),
637
- };
638
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
639
- mockInvokeFunction.mockResolvedValueOnce({
640
- statusCode: 200,
641
- body: JSON.stringify({
642
- identity: {
643
- identityId: 'identity456',
644
- traits: { emails: ['old@email.com', 'new@email.com'] },
645
- },
646
- }),
647
- });
648
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'identity456', traits: { emails: ['old@email.com', 'new@email.com'] } }, lambdaArn);
649
- if (result.summary.outcome === types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED) {
650
- expect(result.summary.traitChanges).toBeDefined();
651
- }
652
- });
653
- });
654
- describe('ADDRESS_UPDATE async behavior (comprehensive)', () => {
655
- describe('ADDRESS_UPDATE enqueues async (not sync write)', () => {
656
- it('should enqueue async when staleness tier is ADDRESS_UPDATE with non-IP address', async () => {
657
- identity_gateway_service_js_1.IdentityGatewayService.configure({
658
- awsRegion: 'us-east-1',
659
- awsAccountId: '123456789',
660
- asyncUpdateQueueName: 'identity-async-queue',
661
- });
662
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'async-msg-123' });
663
- const cachedResponse = {
664
- pk: `identity#${pixelId}#identity456`,
665
- pixelId,
666
- identityId: 'identity456',
667
- response: {
668
- identityId: 'identity456',
669
- traits: {
670
- emails: ['test@email.com'],
671
- addresses: [],
672
- },
673
- },
674
- updatedAt: new Date().toISOString(),
675
- };
676
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
677
- const incomingWithAddress = {
678
- identityId: 'identity456',
679
- traits: {
680
- emails: ['test@email.com'],
681
- addresses: [{ firstName: 'John', lastName: 'Doe', city: 'New York' }],
682
- },
683
- };
684
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
685
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
686
- expect(mockBuildAndSendMessage).toHaveBeenCalledWith('identify.updateAsync', expect.objectContaining({
687
- pixelId,
688
- incomingIdentityId: 'identity456',
689
- }));
690
- expect(result.summary.didEnqueueAsync).toBe(true);
691
- expect(result.summary.didWriteCache).toBe(false);
692
- expect(result.summary.didCallNeptune).toBe(false);
693
- expect(result.summary.didSkipNeptune).toBe(true);
694
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
695
- expect(mockInvokeFunction).not.toHaveBeenCalled();
696
- });
697
- it('should return merged identity with updated addresses', async () => {
698
- identity_gateway_service_js_1.IdentityGatewayService.configure({
699
- awsRegion: 'us-east-1',
700
- awsAccountId: '123456789',
701
- asyncUpdateQueueName: 'identity-async-queue',
702
- });
703
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'msg-456' });
704
- const cachedResponse = {
705
- pk: `identity#${pixelId}#identity456`,
706
- pixelId,
707
- identityId: 'identity456',
708
- response: {
709
- identityId: 'identity456',
710
- traits: {
711
- emails: ['test@email.com'],
712
- phones: ['555-1234'],
713
- addresses: [],
714
- },
715
- },
716
- updatedAt: new Date().toISOString(),
717
- };
718
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
719
- const incomingWithAddress = {
720
- identityId: 'identity456',
721
- traits: {
722
- emails: ['test@email.com'],
723
- addresses: [{ firstName: 'Jane', city: 'Boston', zip: '02101' }],
724
- },
725
- };
726
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
727
- expect(result.identity).toBeDefined();
728
- expect(result.identity?.identityId).toBe('identity456');
729
- expect(result.identity?.traits?.emails).toContain('test@email.com');
730
- expect(result.identity?.traits?.addresses).toHaveLength(1);
731
- expect(result.identity?.traits?.addresses?.[0]?.firstName).toBe('Jane');
732
- });
733
- it('should include writeReason ADDRESS_UPDATE in summary', async () => {
734
- identity_gateway_service_js_1.IdentityGatewayService.configure({
735
- awsRegion: 'us-east-1',
736
- awsAccountId: '123456789',
737
- asyncUpdateQueueName: 'identity-async-queue',
738
- });
739
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'msg-789' });
740
- const cachedResponse = {
741
- pk: `identity#${pixelId}#identity456`,
742
- pixelId,
743
- identityId: 'identity456',
744
- response: {
745
- identityId: 'identity456',
746
- traits: {
747
- emails: ['test@email.com'],
748
- addresses: [],
749
- },
750
- },
751
- updatedAt: new Date().toISOString(),
752
- };
753
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
754
- const incomingWithAddress = {
755
- identityId: 'identity456',
756
- traits: {
757
- emails: ['test@email.com'],
758
- addresses: [{ firstName: 'Bob', city: 'Seattle' }],
759
- },
760
- };
761
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
762
- expect(result.summary.writeReason).toBe('address_update');
763
- });
764
- it('should include traitChanges for address update', async () => {
765
- identity_gateway_service_js_1.IdentityGatewayService.configure({
766
- awsRegion: 'us-east-1',
767
- awsAccountId: '123456789',
768
- asyncUpdateQueueName: 'identity-async-queue',
769
- });
770
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'msg-trait' });
771
- const cachedResponse = {
772
- pk: `identity#${pixelId}#identity456`,
773
- pixelId,
774
- identityId: 'identity456',
775
- response: {
776
- identityId: 'identity456',
777
- traits: {
778
- emails: ['test@email.com'],
779
- addresses: [],
780
- },
781
- },
782
- updatedAt: new Date().toISOString(),
783
- };
784
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
785
- const incomingWithAddress = {
786
- identityId: 'identity456',
787
- traits: {
788
- emails: ['test@email.com'],
789
- addresses: [{ firstName: 'Alice', city: 'Chicago' }],
790
- },
791
- };
792
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
793
- expect(result.summary.traitChanges).toBeDefined();
794
- });
795
- });
796
- describe('ADDRESS_UPDATE async failure handling', () => {
797
- it('should set didEnqueueAsync to false when enqueue fails', async () => {
798
- identity_gateway_service_js_1.IdentityGatewayService.configure({
799
- awsRegion: 'us-east-1',
800
- awsAccountId: '123456789',
801
- asyncUpdateQueueName: 'identity-async-queue',
802
- });
803
- mockBuildAndSendMessage.mockRejectedValueOnce(new Error('SQS unavailable'));
804
- const cachedResponse = {
805
- pk: `identity#${pixelId}#identity456`,
806
- pixelId,
807
- identityId: 'identity456',
808
- response: {
809
- identityId: 'identity456',
810
- traits: {
811
- emails: ['test@email.com'],
812
- addresses: [],
813
- },
814
- },
815
- updatedAt: new Date().toISOString(),
816
- };
817
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
818
- const incomingWithAddress = {
819
- identityId: 'identity456',
820
- traits: {
821
- emails: ['test@email.com'],
822
- addresses: [{ firstName: 'Test', city: 'TestCity' }],
823
- },
824
- };
825
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
826
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
827
- expect(result.summary.didEnqueueAsync).toBe(false);
828
- expect(result.identity).toBeDefined();
829
- expect(result.identity?.identityId).toBe('identity456');
830
- });
831
- it('should log warning when enqueue fails', async () => {
832
- identity_gateway_service_js_1.IdentityGatewayService.configure({
833
- awsRegion: 'us-east-1',
834
- awsAccountId: '123456789',
835
- asyncUpdateQueueName: 'identity-async-queue',
836
- });
837
- mockBuildAndSendMessage.mockRejectedValueOnce(new Error('Network timeout'));
838
- const cachedResponse = {
839
- pk: `identity#${pixelId}#identity456`,
840
- pixelId,
841
- identityId: 'identity456',
842
- response: {
843
- identityId: 'identity456',
844
- traits: {
845
- emails: ['test@email.com'],
846
- addresses: [],
847
- },
848
- },
849
- updatedAt: new Date().toISOString(),
850
- };
851
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
852
- const incomingWithAddress = {
853
- identityId: 'identity456',
854
- traits: {
855
- emails: ['test@email.com'],
856
- addresses: [{ firstName: 'Test', city: 'TestCity' }],
857
- },
858
- };
859
- await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
860
- expect(mockedLogger.warn).toHaveBeenCalledWith('IdentityGateway - enqueueAsyncIdentityUpdate: failed to enqueue (failing open)', expect.objectContaining({
861
- error: expect.any(Error),
862
- errorMessage: 'Network timeout',
863
- pixelId,
864
- }));
865
- });
866
- it('should set didEnqueueAsync to false when SQS client is not configured', async () => {
867
- const cachedResponse = {
868
- pk: `identity#${pixelId}#identity456`,
869
- pixelId,
870
- identityId: 'identity456',
871
- response: {
872
- identityId: 'identity456',
873
- traits: {
874
- emails: ['test@email.com'],
875
- addresses: [],
876
- },
877
- },
878
- updatedAt: new Date().toISOString(),
879
- };
880
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
881
- const incomingWithAddress = {
882
- identityId: 'identity456',
883
- traits: {
884
- emails: ['test@email.com'],
885
- addresses: [{ firstName: 'Test', city: 'TestCity' }],
886
- },
887
- };
888
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
889
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
890
- expect(result.summary.didEnqueueAsync).toBe(false);
891
- expect(result.identity).toBeDefined();
892
- });
893
- });
894
- describe('IDENTITY_STALE path (comparison with ADDRESS_UPDATE)', () => {
895
- it('should call Neptune when staleness tier is IDENTITY_STALE (not ADDRESS_UPDATE)', async () => {
896
- identity_gateway_service_js_1.IdentityGatewayService.configure({
897
- awsRegion: 'us-east-1',
898
- awsAccountId: '123456789',
899
- asyncUpdateQueueName: 'identity-async-queue',
900
- });
901
- const cachedResponse = {
902
- pk: `identity#${pixelId}#identity456`,
903
- pixelId,
904
- identityId: 'identity456',
905
- response: {
906
- identityId: 'identity456',
907
- traits: {
908
- emails: ['old@email.com'],
909
- },
910
- },
911
- updatedAt: new Date().toISOString(),
912
- };
913
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
914
- mockInvokeFunction.mockResolvedValueOnce({
915
- statusCode: 200,
916
- body: JSON.stringify({
917
- identity: {
918
- identityId: 'identity456',
919
- traits: {
920
- emails: ['old@email.com', 'new@email.com'],
921
- },
922
- },
923
- }),
924
- });
925
- const incomingWithNewEmail = {
926
- identityId: 'identity456',
927
- traits: {
928
- emails: ['old@email.com', 'new@email.com'],
929
- },
930
- };
931
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithNewEmail, lambdaArn);
932
- expect(mockInvokeFunction).toHaveBeenCalled();
933
- expect(result.summary.didCallNeptune).toBe(true);
934
- expect(result.summary.didSkipNeptune).toBe(false);
935
- expect(mockBuildAndSendMessage).not.toHaveBeenCalled();
936
- expect(result.summary.didEnqueueAsync).toBe(false);
937
- expect(result.summary.outcome).not.toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
938
- });
939
- it('should NOT call Neptune when staleness tier is ADDRESS_UPDATE', async () => {
940
- identity_gateway_service_js_1.IdentityGatewayService.configure({
941
- awsRegion: 'us-east-1',
942
- awsAccountId: '123456789',
943
- asyncUpdateQueueName: 'identity-async-queue',
944
- });
945
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'msg-no-neptune' });
946
- const cachedResponse = {
947
- pk: `identity#${pixelId}#identity456`,
948
- pixelId,
949
- identityId: 'identity456',
950
- response: {
951
- identityId: 'identity456',
952
- traits: {
953
- emails: ['test@email.com'],
954
- addresses: [],
955
- },
956
- },
957
- updatedAt: new Date().toISOString(),
958
- };
959
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
960
- const incomingWithAddress = {
961
- identityId: 'identity456',
962
- traits: {
963
- emails: ['test@email.com'],
964
- addresses: [{ firstName: 'Test', city: 'TestCity' }],
965
- },
966
- };
967
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
968
- expect(mockInvokeFunction).not.toHaveBeenCalled();
969
- expect(result.summary.didCallNeptune).toBe(false);
970
- expect(result.summary.didSkipNeptune).toBe(true);
971
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
972
- });
973
- it('should write cache from Neptune in IDENTITY_STALE path (identity-service handles it)', async () => {
974
- const cachedResponse = {
975
- pk: `identity#${pixelId}#identity456`,
976
- pixelId,
977
- identityId: 'identity456',
978
- response: {
979
- identityId: 'identity456',
980
- traits: {
981
- emails: ['old@email.com'],
982
- },
983
- },
984
- updatedAt: new Date().toISOString(),
985
- };
986
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
987
- mockInvokeFunction.mockResolvedValueOnce({
988
- statusCode: 200,
989
- body: JSON.stringify({
990
- identity: {
991
- identityId: 'identity456',
992
- traits: {
993
- emails: ['old@email.com', 'new@email.com'],
994
- },
995
- },
996
- }),
997
- });
998
- const incomingWithNewEmail = {
999
- identityId: 'identity456',
1000
- traits: {
1001
- emails: ['old@email.com', 'new@email.com'],
1002
- },
1003
- };
1004
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithNewEmail, lambdaArn);
1005
- expect(result.summary.didCallNeptune).toBe(true);
1006
- expect(result.summary.didWriteCache).toBe(false);
1007
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1008
- });
1009
- });
1010
- describe('summary correctness for ADDRESS_UPDATE path', () => {
1011
- it('should have correct summary fields for successful ADDRESS_UPDATE async enqueue', async () => {
1012
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1013
- awsRegion: 'us-east-1',
1014
- awsAccountId: '123456789',
1015
- asyncUpdateQueueName: 'identity-async-queue',
1016
- });
1017
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'summary-test-msg' });
1018
- const cachedResponse = {
1019
- pk: `identity#${pixelId}#identity456`,
1020
- pixelId,
1021
- identityId: 'identity456',
1022
- response: {
1023
- identityId: 'identity456',
1024
- traits: {
1025
- emails: ['test@email.com'],
1026
- addresses: [],
1027
- },
1028
- },
1029
- updatedAt: new Date().toISOString(),
1030
- };
1031
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1032
- const incomingWithAddress = {
1033
- identityId: 'identity456',
1034
- traits: {
1035
- emails: ['test@email.com'],
1036
- addresses: [{ firstName: 'Summary', city: 'SummaryCity' }],
1037
- },
1038
- };
1039
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
1040
- expect(result.summary).toEqual(expect.objectContaining({
1041
- outcome: types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC,
1042
- pixelId,
1043
- identityId: 'identity456',
1044
- didReadCache: true,
1045
- didCallNeptune: false,
1046
- didWriteCache: false,
1047
- didSkipNeptune: true,
1048
- didFallbackToIncoming: false,
1049
- didEnqueueAsync: true,
1050
- writeReason: 'address_update',
1051
- }));
1052
- });
1053
- it('should have correct summary fields for failed ADDRESS_UPDATE async enqueue', async () => {
1054
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1055
- awsRegion: 'us-east-1',
1056
- awsAccountId: '123456789',
1057
- asyncUpdateQueueName: 'identity-async-queue',
1058
- });
1059
- mockBuildAndSendMessage.mockRejectedValueOnce(new Error('Enqueue failed'));
1060
- const cachedResponse = {
1061
- pk: `identity#${pixelId}#identity456`,
1062
- pixelId,
1063
- identityId: 'identity456',
1064
- response: {
1065
- identityId: 'identity456',
1066
- traits: {
1067
- emails: ['test@email.com'],
1068
- addresses: [],
1069
- },
1070
- },
1071
- updatedAt: new Date().toISOString(),
1072
- };
1073
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1074
- const incomingWithAddress = {
1075
- identityId: 'identity456',
1076
- traits: {
1077
- emails: ['test@email.com'],
1078
- addresses: [{ firstName: 'Fail', city: 'FailCity' }],
1079
- },
1080
- };
1081
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithAddress, lambdaArn);
1082
- expect(result.summary).toEqual(expect.objectContaining({
1083
- outcome: types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC,
1084
- pixelId,
1085
- identityId: 'identity456',
1086
- didReadCache: true,
1087
- didCallNeptune: false,
1088
- didWriteCache: false,
1089
- didSkipNeptune: true,
1090
- didFallbackToIncoming: false,
1091
- didEnqueueAsync: false,
1092
- writeReason: 'address_update',
1093
- }));
1094
- });
1095
- });
1096
- describe('IP-sourced vs non-IP address behavior', () => {
1097
- it('should NOT trigger ADDRESS_UPDATE for IP-sourced addresses (VOLATILE_ONLY tier)', async () => {
1098
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1099
- awsRegion: 'us-east-1',
1100
- awsAccountId: '123456789',
1101
- asyncUpdateQueueName: 'identity-async-queue',
1102
- });
1103
- const cachedResponse = {
1104
- pk: `identity#${pixelId}#identity456`,
1105
- pixelId,
1106
- identityId: 'identity456',
1107
- response: {
1108
- identityId: 'identity456',
1109
- traits: {
1110
- emails: ['test@email.com'],
1111
- addresses: [],
1112
- },
1113
- },
1114
- updatedAt: new Date().toISOString(),
1115
- };
1116
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1117
- const incomingWithIpAddress = {
1118
- identityId: 'identity456',
1119
- traits: {
1120
- emails: ['test@email.com'],
1121
- addresses: [{ city: 'IPCity', country: 'US' }],
1122
- },
1123
- };
1124
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithIpAddress, lambdaArn);
1125
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.VOLATILE_ONLY_CACHE_HIT);
1126
- expect(result.summary.didEnqueueAsync).toBe(false);
1127
- expect(mockBuildAndSendMessage).not.toHaveBeenCalled();
1128
- });
1129
- it('should trigger ADDRESS_UPDATE for non-IP address (has PII fields)', async () => {
1130
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1131
- awsRegion: 'us-east-1',
1132
- awsAccountId: '123456789',
1133
- asyncUpdateQueueName: 'identity-async-queue',
1134
- });
1135
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'non-ip-msg' });
369
+ expect(result.identity?.traits?.emails).toContain('new@email.com');
370
+ });
371
+ it('should return fresh cache hit without writing (skipCacheWrite irrelevant for hits)', async () => {
1136
372
  const cachedResponse = {
1137
373
  pk: `identity#${pixelId}#identity456`,
1138
374
  pixelId,
1139
375
  identityId: 'identity456',
1140
376
  response: {
1141
377
  identityId: 'identity456',
1142
- traits: {
1143
- emails: ['test@email.com'],
1144
- addresses: [],
1145
- },
378
+ traits: { emails: ['test@email.com'] },
1146
379
  },
1147
380
  updatedAt: new Date().toISOString(),
1148
381
  };
1149
382
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1150
- const incomingWithNonIpAddress = {
383
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
384
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
385
+ expect(result.didWriteCache).toBe(false);
386
+ expect(mockGetIdentity).not.toHaveBeenCalled();
387
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
388
+ });
389
+ });
390
+ });
391
+ describe('edge cases', () => {
392
+ it('should return invalid input when pixelId is undefined', async () => {
393
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(undefined, { identityId: 'test' });
394
+ expect(result.identity).toBeUndefined();
395
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT);
396
+ expect(result.didReadCache).toBe(false);
397
+ expect(result.didWriteCache).toBe(false);
398
+ });
399
+ it('should return invalid input when incomingIdentity is null', async () => {
400
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, null);
401
+ expect(result.identity).toBeUndefined();
402
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT);
403
+ });
404
+ it('should return invalid input when both pixelId and identity are missing', async () => {
405
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching('', undefined);
406
+ expect(result.identity).toBeUndefined();
407
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT);
408
+ });
409
+ it('should gracefully fallback when cache read fails (fail-open pattern)', async () => {
410
+ mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('Unexpected DynamoDB error'));
411
+ mockGetIdentity.mockResolvedValueOnce({
412
+ identityId: 'neptune-resolved',
413
+ traits: { emails: ['test@email.com'] },
414
+ });
415
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'test-id', traits: { emails: ['test@email.com'] } });
416
+ expect(result.identity).toBeDefined();
417
+ expect(result.identity?.identityId).toBe('neptune-resolved');
418
+ expect(result.didReadCache).toBe(true);
419
+ expect(result.didCallNeptune).toBe(true);
420
+ });
421
+ it('should reject identity with undefined identityId in enqueue', async () => {
422
+ identity_gateway_service_js_1.IdentityGatewayService.configure({
423
+ awsRegion: 'us-east-1',
424
+ awsAccountId: '123456789',
425
+ asyncUpdateQueueName: 'test-queue',
426
+ });
427
+ const identity = { traits: { emails: ['test@email.com'] } };
428
+ const context = { identity };
429
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
430
+ pixelId,
431
+ identity,
432
+ context,
433
+ reason: 'anonymous user',
434
+ });
435
+ expect(result.success).toBe(false);
436
+ expect(result.error).toBe('Missing required pixelId or identityId');
437
+ expect(mockBuildAndSendMessage).not.toHaveBeenCalled();
438
+ expect(mockedLogger.warn).toHaveBeenCalledWith('IdentityGateway - enqueueAsyncIdentityUpdate: missing required params', expect.objectContaining({
439
+ pixelId,
440
+ identityId: undefined,
441
+ }));
442
+ });
443
+ });
444
+ describe('safeWriteBackToCache behavior with skipCacheWrite', () => {
445
+ it('should not call safeWriteBackToCache when skipCacheWrite is true and Neptune returns new identity', async () => {
446
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
447
+ mockGetIdentity.mockResolvedValueOnce({
448
+ identityId: 'new-resolved-identity',
449
+ traits: { emails: ['test@email.com'] },
450
+ });
451
+ await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'original-id', traits: { emails: ['test@email.com'] } });
452
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
453
+ });
454
+ });
455
+ describe('result fields consistency', () => {
456
+ it('should include all required fields in resolveIdentityWithCaching result', async () => {
457
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
458
+ mockGetIdentity.mockResolvedValueOnce({
459
+ identityId: 'resolved-id',
460
+ traits: { emails: ['test@email.com'] },
461
+ });
462
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'incoming-id', traits: { emails: ['test@email.com'] } });
463
+ expect(result).toEqual(expect.objectContaining({
464
+ outcome: expect.any(String),
465
+ pixelId,
466
+ identityId: 'resolved-id',
467
+ didReadCache: true,
468
+ didCallNeptune: true,
469
+ didWriteCache: false,
470
+ didSkipNeptune: false,
471
+ didFallbackToIncoming: false,
472
+ didEnqueueAsync: false,
473
+ }));
474
+ });
475
+ it('should include traitChanges in summary when identity changes', async () => {
476
+ const cachedResponse = {
477
+ pk: `identity#${pixelId}#identity456`,
478
+ pixelId,
479
+ identityId: 'identity456',
480
+ response: {
1151
481
  identityId: 'identity456',
1152
- traits: {
1153
- emails: ['test@email.com'],
1154
- addresses: [{ firstName: 'John', city: 'NonIPCity' }],
1155
- },
1156
- };
1157
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithNonIpAddress, lambdaArn);
1158
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
1159
- expect(result.summary.didEnqueueAsync).toBe(true);
1160
- expect(mockBuildAndSendMessage).toHaveBeenCalled();
482
+ traits: { emails: ['old@email.com'] },
483
+ },
484
+ updatedAt: new Date().toISOString(),
485
+ };
486
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
487
+ mockGetIdentity.mockResolvedValueOnce({
488
+ identityId: 'identity456',
489
+ traits: { emails: ['old@email.com', 'new@email.com'] },
1161
490
  });
491
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'identity456', traits: { emails: ['old@email.com', 'new@email.com'] } });
492
+ if (result.outcome === types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED) {
493
+ expect(result.traitChanges).toBeDefined();
494
+ }
1162
495
  });
1163
496
  });
1164
497
  describe('refactored flow: merge and staleness computation', () => {
@@ -1184,7 +517,7 @@ describe('IdentityGatewayService new features', () => {
1184
517
  emails: ['cached@email.com'],
1185
518
  },
1186
519
  };
1187
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
520
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
1188
521
  expect(result.identity?.traits?.emails).toContain('cached@email.com');
1189
522
  expect(result.identity?.traits?.phones).toContain('555-cached');
1190
523
  });
@@ -1202,14 +535,9 @@ describe('IdentityGatewayService new features', () => {
1202
535
  updatedAt: new Date().toISOString(),
1203
536
  };
1204
537
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1205
- mockInvokeFunction.mockResolvedValueOnce({
1206
- statusCode: 200,
1207
- body: JSON.stringify({
1208
- identity: {
1209
- identityId: 'staleness-compute',
1210
- traits: { emails: ['old@email.com', 'new@email.com'] },
1211
- },
1212
- }),
538
+ mockGetIdentity.mockResolvedValueOnce({
539
+ identityId: 'staleness-compute',
540
+ traits: { emails: ['old@email.com', 'new@email.com'] },
1213
541
  });
1214
542
  const incomingIdentity = {
1215
543
  identityId: 'staleness-compute',
@@ -1217,9 +545,9 @@ describe('IdentityGatewayService new features', () => {
1217
545
  emails: ['old@email.com', 'new@email.com'],
1218
546
  },
1219
547
  };
1220
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1221
- expect(result.summary.didCallNeptune).toBe(true);
1222
- expect(result.summary.staleTraits).toContain('emails');
548
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
549
+ expect(result.didCallNeptune).toBe(true);
550
+ expect(result.stalenessResult?.staleTraits).toContain('emails');
1223
551
  });
1224
552
  });
1225
553
  describe('deep equality check', () => {
@@ -1245,10 +573,10 @@ describe('IdentityGatewayService new features', () => {
1245
573
  phones: ['555-1234'],
1246
574
  },
1247
575
  };
1248
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1249
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
1250
- expect(result.summary.didCallNeptune).toBe(false);
1251
- expect(mockInvokeFunction).not.toHaveBeenCalled();
576
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
577
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
578
+ expect(result.didCallNeptune).toBe(false);
579
+ expect(mockGetIdentity).not.toHaveBeenCalled();
1252
580
  });
1253
581
  it('should strip address timestamps before deep equality comparison', async () => {
1254
582
  const now = Date.now();
@@ -1273,9 +601,9 @@ describe('IdentityGatewayService new features', () => {
1273
601
  addresses: [{ city: 'NYC', cra: now, upa: now }],
1274
602
  },
1275
603
  };
1276
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1277
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
1278
- expect(result.summary.didCallNeptune).toBe(false);
604
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
605
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
606
+ expect(result.didCallNeptune).toBe(false);
1279
607
  });
1280
608
  });
1281
609
  describe('volatile-only override', () => {
@@ -1301,10 +629,10 @@ describe('IdentityGatewayService new features', () => {
1301
629
  userAgent: 'Mozilla/5.0 (New)',
1302
630
  },
1303
631
  };
1304
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1305
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.VOLATILE_ONLY_CACHE_HIT);
1306
- expect(result.summary.didCallNeptune).toBe(false);
1307
- expect(result.summary.didWriteCache).toBe(false);
632
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
633
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.VOLATILE_ONLY_CACHE_HIT);
634
+ expect(result.didCallNeptune).toBe(false);
635
+ expect(result.didWriteCache).toBe(false);
1308
636
  });
1309
637
  it('should return merged identity for VOLATILE_ONLY tier', async () => {
1310
638
  const cachedResponse = {
@@ -1328,47 +656,14 @@ describe('IdentityGatewayService new features', () => {
1328
656
  ipAddress: '10.0.0.1',
1329
657
  },
1330
658
  };
1331
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1332
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.VOLATILE_ONLY_CACHE_HIT);
659
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
660
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.VOLATILE_ONLY_CACHE_HIT);
1333
661
  expect(result.identity?.traits?.emails).toContain('test@email.com');
1334
662
  expect(result.identity?.traits?.phones).toContain('555-cached');
1335
663
  expect(result.identity?.traits?.ipAddress).toBe('10.0.0.1');
1336
664
  });
1337
665
  });
1338
666
  describe('skipNeptune derivation from staleTier', () => {
1339
- it('should set skipNeptune=true for ADDRESS_UPDATE tier', async () => {
1340
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1341
- awsRegion: 'us-east-1',
1342
- awsAccountId: '123456789',
1343
- asyncUpdateQueueName: 'identity-async-queue',
1344
- });
1345
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'skip-neptune-test' });
1346
- const cachedResponse = {
1347
- pk: `identity#${pixelId}#skip-neptune-addr`,
1348
- pixelId,
1349
- identityId: 'skip-neptune-addr',
1350
- response: {
1351
- identityId: 'skip-neptune-addr',
1352
- traits: {
1353
- emails: ['test@email.com'],
1354
- addresses: [],
1355
- },
1356
- },
1357
- updatedAt: new Date().toISOString(),
1358
- };
1359
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1360
- const incomingIdentity = {
1361
- identityId: 'skip-neptune-addr',
1362
- traits: {
1363
- emails: ['test@email.com'],
1364
- addresses: [{ firstName: 'John', city: 'NYC' }],
1365
- },
1366
- };
1367
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1368
- expect(result.summary.didSkipNeptune).toBe(true);
1369
- expect(result.summary.didCallNeptune).toBe(false);
1370
- expect(mockInvokeFunction).not.toHaveBeenCalled();
1371
- });
1372
667
  it('should set skipNeptune=false for IDENTITY_STALE tier', async () => {
1373
668
  const cachedResponse = {
1374
669
  pk: `identity#${pixelId}#call-neptune`,
@@ -1383,14 +678,9 @@ describe('IdentityGatewayService new features', () => {
1383
678
  updatedAt: new Date().toISOString(),
1384
679
  };
1385
680
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1386
- mockInvokeFunction.mockResolvedValueOnce({
1387
- statusCode: 200,
1388
- body: JSON.stringify({
1389
- identity: {
1390
- identityId: 'call-neptune',
1391
- traits: { emails: ['old@email.com', 'new@email.com'] },
1392
- },
1393
- }),
681
+ mockGetIdentity.mockResolvedValueOnce({
682
+ identityId: 'call-neptune',
683
+ traits: { emails: ['old@email.com', 'new@email.com'] },
1394
684
  });
1395
685
  const incomingIdentity = {
1396
686
  identityId: 'call-neptune',
@@ -1398,44 +688,13 @@ describe('IdentityGatewayService new features', () => {
1398
688
  emails: ['old@email.com', 'new@email.com'],
1399
689
  },
1400
690
  };
1401
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1402
- expect(result.summary.didSkipNeptune).toBe(false);
1403
- expect(result.summary.didCallNeptune).toBe(true);
1404
- expect(mockInvokeFunction).toHaveBeenCalled();
691
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
692
+ expect(result.didSkipNeptune).toBe(false);
693
+ expect(result.didCallNeptune).toBe(true);
694
+ expect(mockGetIdentity).toHaveBeenCalled();
1405
695
  });
1406
696
  });
1407
697
  describe('writeReason derivation from staleTier', () => {
1408
- it('should set writeReason=address_update for ADDRESS_UPDATE tier', async () => {
1409
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1410
- awsRegion: 'us-east-1',
1411
- awsAccountId: '123456789',
1412
- asyncUpdateQueueName: 'identity-async-queue',
1413
- });
1414
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'addr-reason-test' });
1415
- const cachedResponse = {
1416
- pk: `identity#${pixelId}#addr-reason`,
1417
- pixelId,
1418
- identityId: 'addr-reason',
1419
- response: {
1420
- identityId: 'addr-reason',
1421
- traits: {
1422
- emails: ['test@email.com'],
1423
- addresses: [],
1424
- },
1425
- },
1426
- updatedAt: new Date().toISOString(),
1427
- };
1428
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1429
- const incomingIdentity = {
1430
- identityId: 'addr-reason',
1431
- traits: {
1432
- emails: ['test@email.com'],
1433
- addresses: [{ firstName: 'Test', city: 'TestCity' }],
1434
- },
1435
- };
1436
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1437
- expect(result.summary.writeReason).toBe('address_update');
1438
- });
1439
698
  it('should set writeReason=stale_traits for IDENTITY_STALE tier', async () => {
1440
699
  const cachedResponse = {
1441
700
  pk: `identity#${pixelId}#stale-reason`,
@@ -1450,14 +709,9 @@ describe('IdentityGatewayService new features', () => {
1450
709
  updatedAt: new Date().toISOString(),
1451
710
  };
1452
711
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1453
- mockInvokeFunction.mockResolvedValueOnce({
1454
- statusCode: 200,
1455
- body: JSON.stringify({
1456
- identity: {
1457
- identityId: 'stale-reason',
1458
- traits: { emails: ['old@email.com', 'new@email.com'] },
1459
- },
1460
- }),
712
+ mockGetIdentity.mockResolvedValueOnce({
713
+ identityId: 'stale-reason',
714
+ traits: { emails: ['old@email.com', 'new@email.com'] },
1461
715
  });
1462
716
  const incomingIdentity = {
1463
717
  identityId: 'stale-reason',
@@ -1465,84 +719,9 @@ describe('IdentityGatewayService new features', () => {
1465
719
  emails: ['old@email.com', 'new@email.com'],
1466
720
  },
1467
721
  };
1468
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1469
- expect(result.summary.writeReason).toBeUndefined();
1470
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1471
- });
1472
- });
1473
- describe('ADDRESS_UPDATE → enqueueAsyncIdentityUpdate path', () => {
1474
- it('should enqueue async update instead of calling Neptune', async () => {
1475
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1476
- awsRegion: 'us-east-1',
1477
- awsAccountId: '123456789',
1478
- asyncUpdateQueueName: 'identity-async-queue',
1479
- });
1480
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'async-path-test' });
1481
- const cachedResponse = {
1482
- pk: `identity#${pixelId}#async-enqueue`,
1483
- pixelId,
1484
- identityId: 'async-enqueue',
1485
- response: {
1486
- identityId: 'async-enqueue',
1487
- traits: {
1488
- emails: ['test@email.com'],
1489
- addresses: [],
1490
- },
1491
- },
1492
- updatedAt: new Date().toISOString(),
1493
- };
1494
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1495
- const incomingIdentity = {
1496
- identityId: 'async-enqueue',
1497
- traits: {
1498
- emails: ['test@email.com'],
1499
- addresses: [{ firstName: 'Async', city: 'AsyncCity' }],
1500
- },
1501
- };
1502
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1503
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
1504
- expect(result.summary.didEnqueueAsync).toBe(true);
1505
- expect(result.summary.didCallNeptune).toBe(false);
1506
- expect(mockBuildAndSendMessage).toHaveBeenCalledWith('identify.updateAsync', expect.objectContaining({
1507
- pixelId,
1508
- incomingIdentityId: 'async-enqueue',
1509
- }));
1510
- expect(mockInvokeFunction).not.toHaveBeenCalled();
1511
- });
1512
- it('should return merged identity immediately (no Neptune wait)', async () => {
1513
- identity_gateway_service_js_1.IdentityGatewayService.configure({
1514
- awsRegion: 'us-east-1',
1515
- awsAccountId: '123456789',
1516
- asyncUpdateQueueName: 'identity-async-queue',
1517
- });
1518
- mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'immediate-return' });
1519
- const cachedResponse = {
1520
- pk: `identity#${pixelId}#immediate`,
1521
- pixelId,
1522
- identityId: 'immediate',
1523
- response: {
1524
- identityId: 'immediate',
1525
- traits: {
1526
- emails: ['test@email.com'],
1527
- phones: ['555-cached'],
1528
- addresses: [],
1529
- },
1530
- },
1531
- updatedAt: new Date().toISOString(),
1532
- };
1533
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1534
- const incomingIdentity = {
1535
- identityId: 'immediate',
1536
- traits: {
1537
- emails: ['test@email.com'],
1538
- addresses: [{ firstName: 'Immediate', city: 'ImmediateCity' }],
1539
- },
1540
- };
1541
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1542
- expect(result.identity).toBeDefined();
1543
- expect(result.identity?.identityId).toBe('immediate');
1544
- expect(result.identity?.traits?.phones).toContain('555-cached');
1545
- expect(result.identity?.traits?.addresses).toHaveLength(1);
722
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
723
+ expect(result.writeReason).toBeUndefined();
724
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1546
725
  });
1547
726
  });
1548
727
  describe('IDENTITY_STALE → Neptune path', () => {
@@ -1560,14 +739,9 @@ describe('IdentityGatewayService new features', () => {
1560
739
  updatedAt: new Date().toISOString(),
1561
740
  };
1562
741
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1563
- mockInvokeFunction.mockResolvedValueOnce({
1564
- statusCode: 200,
1565
- body: JSON.stringify({
1566
- identity: {
1567
- identityId: 'neptune-path',
1568
- traits: { emails: ['old@email.com', 'new@email.com'] },
1569
- },
1570
- }),
742
+ mockGetIdentity.mockResolvedValueOnce({
743
+ identityId: 'neptune-path',
744
+ traits: { emails: ['old@email.com', 'new@email.com'] },
1571
745
  });
1572
746
  const incomingIdentity = {
1573
747
  identityId: 'neptune-path',
@@ -1575,10 +749,12 @@ describe('IdentityGatewayService new features', () => {
1575
749
  emails: ['old@email.com', 'new@email.com'],
1576
750
  },
1577
751
  };
1578
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1579
- expect(result.summary.didCallNeptune).toBe(true);
1580
- expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
1581
- pixelId,
752
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
753
+ expect(result.didCallNeptune).toBe(true);
754
+ expect(mockGetIdentity).toHaveBeenCalledWith(pixelId, expect.objectContaining({
755
+ identity: expect.objectContaining({
756
+ identityId: expect.any(String),
757
+ }),
1582
758
  }));
1583
759
  });
1584
760
  it('should write Neptune result to cache when different from cached', async () => {
@@ -1595,17 +771,12 @@ describe('IdentityGatewayService new features', () => {
1595
771
  updatedAt: new Date().toISOString(),
1596
772
  };
1597
773
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1598
- mockInvokeFunction.mockResolvedValueOnce({
1599
- statusCode: 200,
1600
- body: JSON.stringify({
1601
- identity: {
1602
- identityId: 'neptune-write',
1603
- traits: {
1604
- emails: ['old@email.com', 'new@email.com'],
1605
- phones: ['555-neptune'],
1606
- },
1607
- },
1608
- }),
774
+ mockGetIdentity.mockResolvedValueOnce({
775
+ identityId: 'neptune-write',
776
+ traits: {
777
+ emails: ['old@email.com', 'new@email.com'],
778
+ phones: ['555-neptune'],
779
+ },
1609
780
  });
1610
781
  const incomingIdentity = {
1611
782
  identityId: 'neptune-write',
@@ -1613,9 +784,9 @@ describe('IdentityGatewayService new features', () => {
1613
784
  emails: ['old@email.com', 'new@email.com'],
1614
785
  },
1615
786
  };
1616
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1617
- expect(result.summary.didWriteCache).toBe(false);
1618
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
787
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
788
+ expect(result.didWriteCache).toBe(false);
789
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1619
790
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1620
791
  });
1621
792
  it('should skip cache write when Neptune result matches cached', async () => {
@@ -1632,16 +803,11 @@ describe('IdentityGatewayService new features', () => {
1632
803
  updatedAt: new Date().toISOString(),
1633
804
  };
1634
805
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1635
- mockInvokeFunction.mockResolvedValueOnce({
1636
- statusCode: 200,
1637
- body: JSON.stringify({
1638
- identity: {
1639
- identityId: 'neptune-skip-write',
1640
- traits: {
1641
- emails: ['existing@email.com'],
1642
- },
1643
- },
1644
- }),
806
+ mockGetIdentity.mockResolvedValueOnce({
807
+ identityId: 'neptune-skip-write',
808
+ traits: {
809
+ emails: ['existing@email.com'],
810
+ },
1645
811
  });
1646
812
  const incomingIdentity = {
1647
813
  identityId: 'neptune-skip-write',
@@ -1649,10 +815,10 @@ describe('IdentityGatewayService new features', () => {
1649
815
  emails: ['existing@email.com', 'thought-new@email.com'],
1650
816
  },
1651
817
  };
1652
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1653
- expect(result.summary.didCallNeptune).toBe(true);
1654
- expect(result.summary.didWriteCache).toBe(false);
1655
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
818
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
819
+ expect(result.didCallNeptune).toBe(true);
820
+ expect(result.didWriteCache).toBe(false);
821
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1656
822
  });
1657
823
  it('should return NEPTUNE_RESOLUTION_FAILED when Neptune throws', async () => {
1658
824
  const cachedResponse = {
@@ -1668,17 +834,17 @@ describe('IdentityGatewayService new features', () => {
1668
834
  updatedAt: new Date().toISOString(),
1669
835
  };
1670
836
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1671
- mockInvokeFunction.mockRejectedValueOnce(new Error('Neptune invocation failed'));
837
+ mockGetIdentity.mockRejectedValueOnce(new Error('Neptune invocation failed'));
1672
838
  const incomingIdentity = {
1673
839
  identityId: 'neptune-throws',
1674
840
  traits: {
1675
841
  emails: ['old@email.com', 'new@email.com'],
1676
842
  },
1677
843
  };
1678
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1679
- expect(result.summary.didCallNeptune).toBe(true);
1680
- expect(result.summary.didWriteCache).toBe(false);
1681
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
844
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
845
+ expect(result.didCallNeptune).toBe(true);
846
+ expect(result.didWriteCache).toBe(false);
847
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
1682
848
  expect(result.identity).toBeDefined();
1683
849
  expect(result.identity?.identityId).toBe('neptune-throws');
1684
850
  expect(result.identity?.traits?.emails).toEqual(['old@email.com', 'new@email.com']);
@@ -1689,43 +855,29 @@ describe('IdentityGatewayService new features', () => {
1689
855
  describe('cache miss without discovered identity ID', () => {
1690
856
  it('should call Neptune with incoming identity when no discovered ID', async () => {
1691
857
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1692
- mockInvokeFunction.mockResolvedValueOnce({
1693
- statusCode: 200,
1694
- body: JSON.stringify({
1695
- identity: {
1696
- identityId: 'neptune-resolved-id',
1697
- traits: { emails: ['test@email.com'] },
1698
- },
1699
- }),
858
+ mockGetIdentity.mockResolvedValueOnce({
859
+ identityId: 'neptune-resolved-id',
860
+ traits: { emails: ['test@email.com'] },
1700
861
  });
1701
862
  const incomingIdentity = {
1702
863
  identityId: 'incoming-id',
1703
864
  traits: { emails: ['test@email.com'] },
1704
865
  };
1705
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1706
- expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
1707
- pixelId,
1708
- context: expect.objectContaining({
1709
- identity: expect.objectContaining({
1710
- identityId: 'incoming-id',
1711
- }),
866
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
867
+ expect(mockGetIdentity).toHaveBeenCalledWith(pixelId, expect.objectContaining({
868
+ identity: expect.objectContaining({
869
+ identityId: 'incoming-id',
1712
870
  }),
1713
871
  }));
1714
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1715
- expect(result.summary.hasDiscoveredIdentityId).toBeUndefined();
1716
- expect(result.summary.writeReason).toBeUndefined();
872
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
873
+ expect(result.writeReason).toBeUndefined();
1717
874
  });
1718
875
  it('should set state.didReadCache=true and state.didCallNeptune=true', async () => {
1719
876
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1720
- mockInvokeFunction.mockResolvedValueOnce({
1721
- statusCode: 200,
1722
- body: JSON.stringify({
1723
- identity: { identityId: 'resolved', traits: {} },
1724
- }),
1725
- });
1726
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'test', traits: {} }, lambdaArn);
1727
- expect(result.summary.didReadCache).toBe(true);
1728
- expect(result.summary.didCallNeptune).toBe(true);
877
+ mockGetIdentity.mockResolvedValueOnce({ identityId: 'resolved', traits: {} });
878
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'test', traits: {} });
879
+ expect(result.didReadCache).toBe(true);
880
+ expect(result.didCallNeptune).toBe(true);
1729
881
  });
1730
882
  });
1731
883
  describe('email pointer discovery with discovered identity ID', () => {
@@ -1737,16 +889,11 @@ describe('IdentityGatewayService new features', () => {
1737
889
  pk: `email#${pixelId}#discovered@email.com`,
1738
890
  identityId: 'discovered-identity-id',
1739
891
  });
1740
- mockInvokeFunction.mockResolvedValueOnce({
1741
- statusCode: 200,
1742
- body: JSON.stringify({
1743
- identity: {
1744
- identityId: 'discovered-identity-id',
1745
- traits: { emails: ['discovered@email.com', 'other@email.com'] },
1746
- },
1747
- }),
892
+ mockGetIdentity.mockResolvedValueOnce({
893
+ identityId: 'discovered-identity-id',
894
+ traits: { emails: ['discovered@email.com', 'other@email.com'] },
1748
895
  });
1749
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingNoId, lambdaArn);
896
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingNoId);
1750
897
  expect(result.identity?.identityId).toBe('discovered-identity-id');
1751
898
  });
1752
899
  it('should use incoming identity in Neptune call (not discovered ID)', async () => {
@@ -1754,22 +901,14 @@ describe('IdentityGatewayService new features', () => {
1754
901
  pk: `email#${pixelId}#test@email.com`,
1755
902
  identityId: 'discovered-from-email',
1756
903
  });
1757
- mockInvokeFunction.mockResolvedValueOnce({
1758
- statusCode: 200,
1759
- body: JSON.stringify({
1760
- identity: {
1761
- identityId: 'neptune-resolved',
1762
- traits: { emails: ['test@email.com'] },
1763
- },
1764
- }),
904
+ mockGetIdentity.mockResolvedValueOnce({
905
+ identityId: 'neptune-resolved',
906
+ traits: { emails: ['test@email.com'] },
1765
907
  });
1766
- await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
1767
- expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
1768
- pixelId,
1769
- context: expect.objectContaining({
1770
- identity: expect.objectContaining({
1771
- traits: { emails: ['test@email.com'] },
1772
- }),
908
+ await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } });
909
+ expect(mockGetIdentity).toHaveBeenCalledWith(pixelId, expect.objectContaining({
910
+ identity: expect.objectContaining({
911
+ traits: expect.objectContaining({ emails: ['test@email.com'] }),
1773
912
  }),
1774
913
  }));
1775
914
  });
@@ -1777,48 +916,33 @@ describe('IdentityGatewayService new features', () => {
1777
916
  describe('Neptune resolution success → write-back', () => {
1778
917
  it('should write to cache when Neptune returns identity', async () => {
1779
918
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1780
- mockInvokeFunction.mockResolvedValueOnce({
1781
- statusCode: 200,
1782
- body: JSON.stringify({
1783
- identity: {
1784
- identityId: 'neptune-success',
1785
- traits: { emails: ['test@email.com'], phones: ['555-1234'] },
1786
- },
1787
- }),
919
+ mockGetIdentity.mockResolvedValueOnce({
920
+ identityId: 'neptune-success',
921
+ traits: { emails: ['test@email.com'], phones: ['555-1234'] },
1788
922
  });
1789
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'incoming', traits: { emails: ['test@email.com'] } }, lambdaArn);
1790
- expect(result.summary.didWriteCache).toBe(false);
1791
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
923
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'incoming', traits: { emails: ['test@email.com'] } });
924
+ expect(result.didWriteCache).toBe(false);
925
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1792
926
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1793
927
  });
1794
928
  it('should set resolvedIdentity to Neptune response', async () => {
1795
929
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1796
- mockInvokeFunction.mockResolvedValueOnce({
1797
- statusCode: 200,
1798
- body: JSON.stringify({
1799
- identity: {
1800
- identityId: 'from-neptune',
1801
- traits: { emails: ['neptune@email.com'] },
1802
- },
1803
- }),
930
+ mockGetIdentity.mockResolvedValueOnce({
931
+ identityId: 'from-neptune',
932
+ traits: { emails: ['neptune@email.com'] },
1804
933
  });
1805
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'original', traits: { emails: ['original@email.com'] } }, lambdaArn);
934
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'original', traits: { emails: ['original@email.com'] } });
1806
935
  expect(result.identity?.identityId).toBe('from-neptune');
1807
936
  expect(result.identity?.traits?.emails).toContain('neptune@email.com');
1808
937
  });
1809
938
  it('should include traitChanges with "no cached identity" reason', async () => {
1810
939
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1811
- mockInvokeFunction.mockResolvedValueOnce({
1812
- statusCode: 200,
1813
- body: JSON.stringify({
1814
- identity: {
1815
- identityId: 'new-identity',
1816
- traits: { emails: ['new@email.com'] },
1817
- },
1818
- }),
940
+ mockGetIdentity.mockResolvedValueOnce({
941
+ identityId: 'new-identity',
942
+ traits: { emails: ['new@email.com'] },
1819
943
  });
1820
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'incoming', traits: { emails: ['incoming@email.com'] } }, lambdaArn);
1821
- expect(result.summary.traitChanges).toEqual({
944
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'incoming', traits: { emails: ['incoming@email.com'] } });
945
+ expect(result.traitChanges).toEqual({
1822
946
  reason: { resolved: 'no cached identity' },
1823
947
  });
1824
948
  });
@@ -1826,21 +950,21 @@ describe('IdentityGatewayService new features', () => {
1826
950
  describe('Neptune resolution failure → fallback to incoming', () => {
1827
951
  it('should fallback to incoming identity when Neptune fails', async () => {
1828
952
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1829
- mockInvokeFunction.mockRejectedValueOnce(new Error('Neptune unavailable'));
953
+ mockGetIdentity.mockRejectedValueOnce(new Error('Neptune unavailable'));
1830
954
  const incomingIdentity = {
1831
955
  identityId: 'fallback-incoming',
1832
956
  traits: { emails: ['fallback@email.com'] },
1833
957
  };
1834
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1835
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
1836
- expect(result.summary.didFallbackToIncoming).toBe(true);
958
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, incomingIdentity);
959
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
960
+ expect(result.didFallbackToIncoming).toBe(true);
1837
961
  expect(result.identity?.identityId).toBe('fallback-incoming');
1838
962
  });
1839
963
  it('should set didWriteCache=false when Neptune fails', async () => {
1840
964
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1841
- mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda timeout'));
1842
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'test', traits: {} }, lambdaArn);
1843
- expect(result.summary.didWriteCache).toBe(false);
965
+ mockGetIdentity.mockRejectedValueOnce(new Error('Lambda timeout'));
966
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'test', traits: {} });
967
+ expect(result.didWriteCache).toBe(false);
1844
968
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1845
969
  });
1846
970
  it('should use identityForNeptune (with discovered ID) as fallback when available', async () => {
@@ -1848,9 +972,9 @@ describe('IdentityGatewayService new features', () => {
1848
972
  pk: `email#${pixelId}#test@email.com`,
1849
973
  identityId: 'discovered-id',
1850
974
  });
1851
- mockInvokeFunction.mockRejectedValueOnce(new Error('Neptune error'));
1852
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
1853
- expect(result.summary.didFallbackToIncoming).toBe(true);
975
+ mockGetIdentity.mockRejectedValueOnce(new Error('Neptune error'));
976
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } });
977
+ expect(result.didFallbackToIncoming).toBe(true);
1854
978
  expect(result.identity?.identityId).toBe('discovered-id');
1855
979
  expect(result.identity?.traits?.emails).toContain('test@email.com');
1856
980
  });
@@ -1858,31 +982,21 @@ describe('IdentityGatewayService new features', () => {
1858
982
  describe('skipCacheWrite=true behavior', () => {
1859
983
  it('should NOT write to cache when skipCacheWrite=true', async () => {
1860
984
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1861
- mockInvokeFunction.mockResolvedValueOnce({
1862
- statusCode: 200,
1863
- body: JSON.stringify({
1864
- identity: {
1865
- identityId: 'skip-write-identity',
1866
- traits: { emails: ['test@email.com'] },
1867
- },
1868
- }),
985
+ mockGetIdentity.mockResolvedValueOnce({
986
+ identityId: 'skip-write-identity',
987
+ traits: { emails: ['test@email.com'] },
1869
988
  });
1870
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'incoming', traits: { emails: ['test@email.com'] } }, lambdaArn, { skipCacheWrite: true });
1871
- expect(result.summary.didWriteCache).toBe(false);
989
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'incoming', traits: { emails: ['test@email.com'] } });
990
+ expect(result.didWriteCache).toBe(false);
1872
991
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1873
992
  expect(result.identity?.identityId).toBe('skip-write-identity');
1874
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
993
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1875
994
  });
1876
995
  it('should still call Neptune when skipCacheWrite=true', async () => {
1877
996
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1878
- mockInvokeFunction.mockResolvedValueOnce({
1879
- statusCode: 200,
1880
- body: JSON.stringify({
1881
- identity: { identityId: 'resolved', traits: {} },
1882
- }),
1883
- });
1884
- await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'test', traits: {} }, lambdaArn, { skipCacheWrite: true });
1885
- expect(mockInvokeFunction).toHaveBeenCalled();
997
+ mockGetIdentity.mockResolvedValueOnce({ identityId: 'resolved', traits: {} });
998
+ await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'test', traits: {} });
999
+ expect(mockGetIdentity).toHaveBeenCalled();
1886
1000
  });
1887
1001
  });
1888
1002
  });
@@ -1900,18 +1014,13 @@ describe('IdentityGatewayService new features', () => {
1900
1014
  updatedAt: new Date().toISOString(),
1901
1015
  };
1902
1016
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1903
- mockInvokeFunction.mockResolvedValueOnce({
1904
- statusCode: 200,
1905
- body: JSON.stringify({
1906
- identity: {
1907
- identityId: 'stale-changes',
1908
- traits: { emails: ['old@email.com', 'new@email.com'], phones: ['555-new'] },
1909
- },
1910
- }),
1017
+ mockGetIdentity.mockResolvedValueOnce({
1018
+ identityId: 'stale-changes',
1019
+ traits: { emails: ['old@email.com', 'new@email.com'], phones: ['555-new'] },
1911
1020
  });
1912
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'stale-changes', traits: { emails: ['old@email.com', 'new@email.com'] } }, lambdaArn);
1913
- expect(result.summary.didWriteCache).toBe(false);
1914
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1021
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'stale-changes', traits: { emails: ['old@email.com', 'new@email.com'] } });
1022
+ expect(result.didWriteCache).toBe(false);
1023
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1915
1024
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1916
1025
  });
1917
1026
  it('should compute traitChanges between cached and Neptune identity', async () => {
@@ -1929,21 +1038,16 @@ describe('IdentityGatewayService new features', () => {
1929
1038
  updatedAt: new Date().toISOString(),
1930
1039
  };
1931
1040
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1932
- mockInvokeFunction.mockResolvedValueOnce({
1933
- statusCode: 200,
1934
- body: JSON.stringify({
1935
- identity: {
1936
- identityId: 'trait-changes',
1937
- traits: {
1938
- emails: ['old@email.com', 'new@email.com'],
1939
- phones: ['555-old', '555-new'],
1940
- },
1941
- },
1942
- }),
1041
+ mockGetIdentity.mockResolvedValueOnce({
1042
+ identityId: 'trait-changes',
1043
+ traits: {
1044
+ emails: ['old@email.com', 'new@email.com'],
1045
+ phones: ['555-old', '555-new'],
1046
+ },
1943
1047
  });
1944
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'trait-changes', traits: { emails: ['old@email.com', 'new@email.com'] } }, lambdaArn);
1945
- expect(result.summary.traitChanges).toBeDefined();
1946
- expect(result.summary.traitChanges?.emails).toBeDefined();
1048
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'trait-changes', traits: { emails: ['old@email.com', 'new@email.com'] } });
1049
+ expect(result.traitChanges).toBeDefined();
1050
+ expect(result.traitChanges?.emails).toBeDefined();
1947
1051
  });
1948
1052
  });
1949
1053
  describe('Neptune resolution success without changes → skip write', () => {
@@ -1959,19 +1063,14 @@ describe('IdentityGatewayService new features', () => {
1959
1063
  updatedAt: new Date().toISOString(),
1960
1064
  };
1961
1065
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1962
- mockInvokeFunction.mockResolvedValueOnce({
1963
- statusCode: 200,
1964
- body: JSON.stringify({
1965
- identity: {
1966
- identityId: 'no-changes',
1967
- traits: { emails: ['same@email.com'] },
1968
- },
1969
- }),
1066
+ mockGetIdentity.mockResolvedValueOnce({
1067
+ identityId: 'no-changes',
1068
+ traits: { emails: ['same@email.com'] },
1970
1069
  });
1971
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'no-changes', traits: { emails: ['same@email.com', 'thought-new@email.com'] } }, lambdaArn);
1972
- expect(result.summary.didCallNeptune).toBe(true);
1973
- expect(result.summary.didWriteCache).toBe(false);
1974
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1070
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'no-changes', traits: { emails: ['same@email.com', 'thought-new@email.com'] } });
1071
+ expect(result.didCallNeptune).toBe(true);
1072
+ expect(result.didWriteCache).toBe(false);
1073
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1975
1074
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1976
1075
  });
1977
1076
  it('should still return Neptune identity even when skipping write', async () => {
@@ -1986,16 +1085,11 @@ describe('IdentityGatewayService new features', () => {
1986
1085
  updatedAt: new Date().toISOString(),
1987
1086
  };
1988
1087
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1989
- mockInvokeFunction.mockResolvedValueOnce({
1990
- statusCode: 200,
1991
- body: JSON.stringify({
1992
- identity: {
1993
- identityId: 'return-identity',
1994
- traits: { emails: ['cached@email.com'] },
1995
- },
1996
- }),
1088
+ mockGetIdentity.mockResolvedValueOnce({
1089
+ identityId: 'return-identity',
1090
+ traits: { emails: ['cached@email.com'] },
1997
1091
  });
1998
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'return-identity', traits: { emails: ['cached@email.com', 'extra@email.com'] } }, lambdaArn);
1092
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'return-identity', traits: { emails: ['cached@email.com', 'extra@email.com'] } });
1999
1093
  expect(result.identity?.identityId).toBe('return-identity');
2000
1094
  });
2001
1095
  it('should strip address timestamps for comparison', async () => {
@@ -2014,21 +1108,16 @@ describe('IdentityGatewayService new features', () => {
2014
1108
  updatedAt: new Date().toISOString(),
2015
1109
  };
2016
1110
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
2017
- mockInvokeFunction.mockResolvedValueOnce({
2018
- statusCode: 200,
2019
- body: JSON.stringify({
2020
- identity: {
2021
- identityId: 'addr-timestamp',
2022
- traits: {
2023
- emails: ['test@email.com'],
2024
- addresses: [{ city: 'NYC', cra: now, upa: now }],
2025
- },
2026
- },
2027
- }),
1111
+ mockGetIdentity.mockResolvedValueOnce({
1112
+ identityId: 'addr-timestamp',
1113
+ traits: {
1114
+ emails: ['test@email.com'],
1115
+ addresses: [{ city: 'NYC', cra: now, upa: now }],
1116
+ },
2028
1117
  });
2029
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'addr-timestamp', traits: { emails: ['test@email.com', 'new@email.com'] } }, lambdaArn);
2030
- expect(result.summary.didWriteCache).toBe(false);
2031
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1118
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'addr-timestamp', traits: { emails: ['test@email.com', 'new@email.com'] } });
1119
+ expect(result.didWriteCache).toBe(false);
1120
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
2032
1121
  });
2033
1122
  });
2034
1123
  describe('Neptune resolution failure', () => {
@@ -2044,10 +1133,10 @@ describe('IdentityGatewayService new features', () => {
2044
1133
  updatedAt: new Date().toISOString(),
2045
1134
  };
2046
1135
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
2047
- mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda invocation failed'));
2048
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'neptune-fail', traits: { emails: ['cached@email.com', 'new@email.com'] } }, lambdaArn);
2049
- expect(result.summary.didCallNeptune).toBe(true);
2050
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
1136
+ mockGetIdentity.mockRejectedValueOnce(new Error('Lambda invocation failed'));
1137
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'neptune-fail', traits: { emails: ['cached@email.com', 'new@email.com'] } });
1138
+ expect(result.didCallNeptune).toBe(true);
1139
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
2051
1140
  });
2052
1141
  it('should not write to cache when Neptune fails', async () => {
2053
1142
  const cachedResponse = {
@@ -2061,9 +1150,9 @@ describe('IdentityGatewayService new features', () => {
2061
1150
  updatedAt: new Date().toISOString(),
2062
1151
  };
2063
1152
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
2064
- mockInvokeFunction.mockRejectedValueOnce(new Error('Network error'));
2065
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'no-write-fail', traits: { emails: ['test@email.com', 'extra@email.com'] } }, lambdaArn);
2066
- expect(result.summary.didWriteCache).toBe(false);
1153
+ mockGetIdentity.mockRejectedValueOnce(new Error('Network error'));
1154
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'no-write-fail', traits: { emails: ['test@email.com', 'extra@email.com'] } });
1155
+ expect(result.didWriteCache).toBe(false);
2067
1156
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
2068
1157
  });
2069
1158
  it('should still return merged identity when Neptune fails', async () => {
@@ -2081,8 +1170,8 @@ describe('IdentityGatewayService new features', () => {
2081
1170
  updatedAt: new Date().toISOString(),
2082
1171
  };
2083
1172
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
2084
- mockInvokeFunction.mockRejectedValueOnce(new Error('Timeout'));
2085
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'merged-on-fail', traits: { emails: ['cached@email.com', 'new@email.com'] } }, lambdaArn);
1173
+ mockGetIdentity.mockRejectedValueOnce(new Error('Timeout'));
1174
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'merged-on-fail', traits: { emails: ['cached@email.com', 'new@email.com'] } });
2086
1175
  expect(result.identity).toBeDefined();
2087
1176
  });
2088
1177
  });
@@ -2099,19 +1188,14 @@ describe('IdentityGatewayService new features', () => {
2099
1188
  updatedAt: new Date().toISOString(),
2100
1189
  };
2101
1190
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
2102
- mockInvokeFunction.mockResolvedValueOnce({
2103
- statusCode: 200,
2104
- body: JSON.stringify({
2105
- identity: {
2106
- identityId: 'skip-stale',
2107
- traits: { emails: ['old@email.com', 'new@email.com'], phones: ['555-new'] },
2108
- },
2109
- }),
1191
+ mockGetIdentity.mockResolvedValueOnce({
1192
+ identityId: 'skip-stale',
1193
+ traits: { emails: ['old@email.com', 'new@email.com'], phones: ['555-new'] },
2110
1194
  });
2111
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'skip-stale', traits: { emails: ['old@email.com', 'new@email.com'] } }, lambdaArn, { skipCacheWrite: true });
2112
- expect(result.summary.didWriteCache).toBe(false);
1195
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'skip-stale', traits: { emails: ['old@email.com', 'new@email.com'] } });
1196
+ expect(result.didWriteCache).toBe(false);
2113
1197
  expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
2114
- expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
1198
+ expect(result.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
2115
1199
  });
2116
1200
  it('should still call Neptune when skipCacheWrite=true', async () => {
2117
1201
  const cachedResponse = {
@@ -2125,17 +1209,12 @@ describe('IdentityGatewayService new features', () => {
2125
1209
  updatedAt: new Date().toISOString(),
2126
1210
  };
2127
1211
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
2128
- mockInvokeFunction.mockResolvedValueOnce({
2129
- statusCode: 200,
2130
- body: JSON.stringify({
2131
- identity: {
2132
- identityId: 'call-neptune-skip',
2133
- traits: { emails: ['test@email.com', 'added@email.com'] },
2134
- },
2135
- }),
1212
+ mockGetIdentity.mockResolvedValueOnce({
1213
+ identityId: 'call-neptune-skip',
1214
+ traits: { emails: ['test@email.com', 'added@email.com'] },
2136
1215
  });
2137
- await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'call-neptune-skip', traits: { emails: ['test@email.com', 'added@email.com'] } }, lambdaArn, { skipCacheWrite: true });
2138
- expect(mockInvokeFunction).toHaveBeenCalled();
1216
+ await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'call-neptune-skip', traits: { emails: ['test@email.com', 'added@email.com'] } });
1217
+ expect(mockGetIdentity).toHaveBeenCalled();
2139
1218
  });
2140
1219
  it('should return Neptune identity when skipCacheWrite=true', async () => {
2141
1220
  const cachedResponse = {
@@ -2149,19 +1228,14 @@ describe('IdentityGatewayService new features', () => {
2149
1228
  updatedAt: new Date().toISOString(),
2150
1229
  };
2151
1230
  mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
2152
- mockInvokeFunction.mockResolvedValueOnce({
2153
- statusCode: 200,
2154
- body: JSON.stringify({
2155
- identity: {
2156
- identityId: 'return-skip',
2157
- traits: {
2158
- emails: ['cached@email.com', 'neptune@email.com'],
2159
- phones: ['555-neptune'],
2160
- },
2161
- },
2162
- }),
1231
+ mockGetIdentity.mockResolvedValueOnce({
1232
+ identityId: 'return-skip',
1233
+ traits: {
1234
+ emails: ['cached@email.com', 'neptune@email.com'],
1235
+ phones: ['555-neptune'],
1236
+ },
2163
1237
  });
2164
- const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'return-skip', traits: { emails: ['cached@email.com', 'neptune@email.com'] } }, lambdaArn, { skipCacheWrite: true });
1238
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.resolveIdentityWithCaching(pixelId, { identityId: 'return-skip', traits: { emails: ['cached@email.com', 'neptune@email.com'] } });
2165
1239
  expect(result.identity?.traits?.phones).toContain('555-neptune');
2166
1240
  });
2167
1241
  });