@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.
- package/dist/cjs/__tests__/identity-cache/helpers.spec.js +37 -38
- package/dist/cjs/__tests__/identity-cache/helpers.spec.js.map +1 -1
- package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js +83 -344
- package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
- package/dist/cjs/__tests__/identity-cache/identity-client.spec.js +360 -0
- package/dist/cjs/__tests__/identity-cache/identity-client.spec.js.map +1 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js +335 -1261
- package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -1
- package/dist/cjs/clients/internal-api/identity-client.d.ts +12 -2
- package/dist/cjs/clients/internal-api/identity-client.js +59 -13
- package/dist/cjs/clients/internal-api/identity-client.js.map +1 -1
- package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +1 -7
- package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js +0 -305
- package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -1
- package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +10 -9
- package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +116 -321
- package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -1
- package/dist/cjs/services/db/identity-cache-dynamodb-service/index.d.ts +0 -1
- package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js +0 -1
- package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js.map +1 -1
- package/dist/cjs/services/db/identity-cache-dynamodb-service/types.d.ts +9 -157
- package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js +1 -15
- package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js.map +1 -1
- package/dist/esm/__tests__/identity-cache/helpers.spec.js +26 -27
- package/dist/esm/__tests__/identity-cache/helpers.spec.js.map +1 -1
- package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js +83 -344
- package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
- package/dist/esm/__tests__/identity-cache/identity-client.spec.js +358 -0
- package/dist/esm/__tests__/identity-cache/identity-client.spec.js.map +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js +335 -1261
- package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -1
- package/dist/esm/clients/internal-api/identity-client.d.ts +12 -2
- package/dist/esm/clients/internal-api/identity-client.js +60 -14
- package/dist/esm/clients/internal-api/identity-client.js.map +1 -1
- package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +1 -7
- package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js +1 -303
- package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -1
- package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +10 -9
- package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +119 -321
- package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -1
- package/dist/esm/services/db/identity-cache-dynamodb-service/index.d.ts +0 -1
- package/dist/esm/services/db/identity-cache-dynamodb-service/index.js +0 -1
- package/dist/esm/services/db/identity-cache-dynamodb-service/index.js.map +1 -1
- package/dist/esm/services/db/identity-cache-dynamodb-service/types.d.ts +9 -157
- package/dist/esm/services/db/identity-cache-dynamodb-service/types.js +0 -14
- package/dist/esm/services/db/identity-cache-dynamodb-service/types.js.map +1 -1
- package/package.json +2 -2
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +0 -646
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +0 -1
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +0 -325
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +0 -1
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +0 -1
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +0 -1286
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
- package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.d.ts +0 -26
- package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js +0 -252
- package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js.map +0 -1
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts +0 -1
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +0 -644
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +0 -1
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts +0 -1
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +0 -323
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +0 -1
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +0 -1
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +0 -1284
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
- package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.d.ts +0 -26
- package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js +0 -235
- package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js.map +0 -1
- /package/dist/cjs/__tests__/identity-cache/{identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts → identity-client.spec.d.ts} +0 -0
- /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
|
|
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:
|
|
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
|
-
|
|
60
|
+
mockGetIdentity.mockReset();
|
|
55
61
|
mockBuildAndSendMessage.mockReset();
|
|
56
62
|
mockSQSClientConstructor.mockReset();
|
|
57
63
|
mockedSQSClient.mockClear();
|
|
58
|
-
identity_gateway_service_js_1.IdentityGatewayService.
|
|
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('
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
293
|
-
expect(result.
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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.
|
|
310
|
-
expect(result.
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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.
|
|
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.
|
|
329
|
-
expect(result.
|
|
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.
|
|
361
|
-
if (result.
|
|
362
|
-
expect(result.
|
|
363
|
-
expect(result.
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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.
|
|
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
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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.
|
|
1221
|
-
expect(result.
|
|
1222
|
-
expect(result.
|
|
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.
|
|
1249
|
-
expect(result.
|
|
1250
|
-
expect(result.
|
|
1251
|
-
expect(
|
|
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.
|
|
1277
|
-
expect(result.
|
|
1278
|
-
expect(result.
|
|
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.
|
|
1305
|
-
expect(result.
|
|
1306
|
-
expect(result.
|
|
1307
|
-
expect(result.
|
|
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.
|
|
1332
|
-
expect(result.
|
|
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
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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.
|
|
1402
|
-
expect(result.
|
|
1403
|
-
expect(result.
|
|
1404
|
-
expect(
|
|
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
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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.
|
|
1469
|
-
expect(result.
|
|
1470
|
-
expect(result.
|
|
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
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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.
|
|
1579
|
-
expect(result.
|
|
1580
|
-
expect(
|
|
1581
|
-
|
|
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
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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.
|
|
1617
|
-
expect(result.
|
|
1618
|
-
expect(result.
|
|
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
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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.
|
|
1653
|
-
expect(result.
|
|
1654
|
-
expect(result.
|
|
1655
|
-
expect(result.
|
|
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
|
-
|
|
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.
|
|
1679
|
-
expect(result.
|
|
1680
|
-
expect(result.
|
|
1681
|
-
expect(result.
|
|
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
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
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.
|
|
1706
|
-
expect(
|
|
1707
|
-
|
|
1708
|
-
|
|
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.
|
|
1715
|
-
expect(result.
|
|
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
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
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
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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.
|
|
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
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
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.
|
|
1767
|
-
expect(
|
|
1768
|
-
|
|
1769
|
-
|
|
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
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
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.
|
|
1790
|
-
expect(result.
|
|
1791
|
-
expect(result.
|
|
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
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
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.
|
|
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
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
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.
|
|
1821
|
-
expect(result.
|
|
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
|
-
|
|
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.
|
|
1835
|
-
expect(result.
|
|
1836
|
-
expect(result.
|
|
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
|
-
|
|
1842
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.
|
|
1843
|
-
expect(result.
|
|
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
|
-
|
|
1852
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.
|
|
1853
|
-
expect(result.
|
|
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
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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.
|
|
1871
|
-
expect(result.
|
|
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.
|
|
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
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
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
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
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.
|
|
1913
|
-
expect(result.
|
|
1914
|
-
expect(result.
|
|
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
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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.
|
|
1945
|
-
expect(result.
|
|
1946
|
-
expect(result.
|
|
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
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
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.
|
|
1972
|
-
expect(result.
|
|
1973
|
-
expect(result.
|
|
1974
|
-
expect(result.
|
|
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
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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.
|
|
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
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
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.
|
|
2030
|
-
expect(result.
|
|
2031
|
-
expect(result.
|
|
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
|
-
|
|
2048
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.
|
|
2049
|
-
expect(result.
|
|
2050
|
-
expect(result.
|
|
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
|
-
|
|
2065
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.
|
|
2066
|
-
expect(result.
|
|
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
|
-
|
|
2085
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.
|
|
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
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
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.
|
|
2112
|
-
expect(result.
|
|
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.
|
|
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
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
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.
|
|
2138
|
-
expect(
|
|
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
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
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.
|
|
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
|
});
|