@adtrackify/at-service-common 4.0.13 → 4.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +92 -349
- 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-cache-hit.spec.js +138 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-cache-hit.spec.js.map +1 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-cache-miss.spec.js +153 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-cache-miss.spec.js.map +1 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-constructor.spec.js +72 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-constructor.spec.js.map +1 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-edge-cases.spec.js +89 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-edge-cases.spec.js.map +1 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js +136 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js.map +1 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-stale-cache.spec.js +224 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-stale-cache.spec.js.map +1 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-test-setup.d.ts +28 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-test-setup.js +90 -0
- package/dist/cjs/__tests__/identity-cache/identity-gateway-test-setup.js.map +1 -0
- 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 +14 -14
- package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +122 -330
- 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 +8 -158
- 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 +92 -349
- 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-cache-hit.spec.d.ts +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-cache-hit.spec.js +136 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-cache-hit.spec.js.map +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-cache-miss.spec.d.ts +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-cache-miss.spec.js +151 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-cache-miss.spec.js.map +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-constructor.spec.d.ts +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-constructor.spec.js +70 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-constructor.spec.js.map +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-edge-cases.spec.d.ts +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-edge-cases.spec.js +87 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-edge-cases.spec.js.map +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-enqueue-async.spec.d.ts +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js +134 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-enqueue-async.spec.js.map +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-stale-cache.spec.d.ts +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-stale-cache.spec.js +222 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-stale-cache.spec.js.map +1 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-test-setup.d.ts +28 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-test-setup.js +83 -0
- package/dist/esm/__tests__/identity-cache/identity-gateway-test-setup.js.map +1 -0
- 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 +14 -14
- package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +125 -330
- 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 +8 -158
- 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.js +0 -1286
- package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
- package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js +0 -2170
- package/dist/cjs/__tests__/identity-cache/identity-gateway-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.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.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.js +0 -1284
- package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +0 -1
- package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js +0 -2168
- package/dist/esm/__tests__/identity-cache/identity-gateway-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 → identity-gateway-cache-hit.spec.d.ts} +0 -0
- /package/dist/cjs/__tests__/identity-cache/{identity-cache-dynamodb-service.spec.d.ts → identity-gateway-cache-miss.spec.d.ts} +0 -0
- /package/dist/cjs/__tests__/identity-cache/{identity-gateway-service.spec.d.ts → identity-gateway-constructor.spec.d.ts} +0 -0
- /package/dist/{esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts → cjs/__tests__/identity-cache/identity-gateway-edge-cases.spec.d.ts} +0 -0
- /package/dist/{esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts → cjs/__tests__/identity-cache/identity-gateway-enqueue-async.spec.d.ts} +0 -0
- /package/dist/{esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts → cjs/__tests__/identity-cache/identity-gateway-stale-cache.spec.d.ts} +0 -0
- /package/dist/esm/__tests__/identity-cache/{identity-gateway-service.spec.d.ts → identity-client.spec.d.ts} +0 -0
|
@@ -1,2170 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const mockInvokeFunction = jest.fn();
|
|
4
|
-
const mockBuildAndSendMessage = jest.fn();
|
|
5
|
-
const mockSQSClientConstructor = jest.fn();
|
|
6
|
-
jest.mock('../../clients/index.js', () => ({
|
|
7
|
-
DynamoDbClient: {
|
|
8
|
-
safeGet: jest.fn(),
|
|
9
|
-
safePut: jest.fn(),
|
|
10
|
-
safeDelete: jest.fn(),
|
|
11
|
-
safeBatchGet: jest.fn(),
|
|
12
|
-
safeBatchWrite: jest.fn(),
|
|
13
|
-
safeQueryByGSI: jest.fn(),
|
|
14
|
-
batchWrite: jest.fn(),
|
|
15
|
-
},
|
|
16
|
-
LambdaInvokeClient: jest.fn().mockImplementation(() => ({
|
|
17
|
-
invokeFunction: mockInvokeFunction,
|
|
18
|
-
})),
|
|
19
|
-
SQSClient: jest.fn().mockImplementation((_region, _accountId, _queueName) => {
|
|
20
|
-
mockSQSClientConstructor(_region, _accountId, _queueName);
|
|
21
|
-
return {
|
|
22
|
-
buildAndSendMessage: mockBuildAndSendMessage,
|
|
23
|
-
queueUrl: `https://sqs.${_region}.amazonaws.com/${_accountId}/${_queueName}`,
|
|
24
|
-
};
|
|
25
|
-
}),
|
|
26
|
-
}));
|
|
27
|
-
jest.mock('../../helpers/index.js', () => ({
|
|
28
|
-
Logger: {
|
|
29
|
-
debug: jest.fn(),
|
|
30
|
-
info: jest.fn(),
|
|
31
|
-
warn: jest.fn(),
|
|
32
|
-
error: jest.fn(),
|
|
33
|
-
},
|
|
34
|
-
}));
|
|
35
|
-
const identity_gateway_service_js_1 = require("../../services/db/identity-cache-dynamodb-service/identity-gateway-service.js");
|
|
36
|
-
const types_js_1 = require("../../services/db/identity-cache-dynamodb-service/types.js");
|
|
37
|
-
const index_js_1 = require("../../clients/index.js");
|
|
38
|
-
const index_js_2 = require("../../helpers/index.js");
|
|
39
|
-
const mockedDynamoDbClient = index_js_1.DynamoDbClient;
|
|
40
|
-
const mockedLogger = index_js_2.Logger;
|
|
41
|
-
const mockedSQSClient = index_js_1.SQSClient;
|
|
42
|
-
describe('IdentityGatewayService new features', () => {
|
|
43
|
-
const pixelId = 'pixel123';
|
|
44
|
-
const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
jest.clearAllMocks();
|
|
47
|
-
mockedDynamoDbClient.safeGet.mockReset();
|
|
48
|
-
mockedDynamoDbClient.safePut.mockReset();
|
|
49
|
-
mockedDynamoDbClient.safeDelete.mockReset();
|
|
50
|
-
mockedDynamoDbClient.safeBatchGet.mockReset();
|
|
51
|
-
mockedDynamoDbClient.safeBatchWrite.mockReset();
|
|
52
|
-
mockedDynamoDbClient.safeQueryByGSI.mockReset();
|
|
53
|
-
mockedDynamoDbClient.batchWrite.mockReset();
|
|
54
|
-
mockInvokeFunction.mockReset();
|
|
55
|
-
mockBuildAndSendMessage.mockReset();
|
|
56
|
-
mockSQSClientConstructor.mockReset();
|
|
57
|
-
mockedSQSClient.mockClear();
|
|
58
|
-
identity_gateway_service_js_1.IdentityGatewayService.lambdaInvokeClient = undefined;
|
|
59
|
-
identity_gateway_service_js_1.IdentityGatewayService.sqsClient = undefined;
|
|
60
|
-
identity_gateway_service_js_1.IdentityGatewayService.sqsQueueUrl = undefined;
|
|
61
|
-
});
|
|
62
|
-
describe('configure()', () => {
|
|
63
|
-
it('should configure with direct queue URL', () => {
|
|
64
|
-
const options = {
|
|
65
|
-
asyncUpdateQueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
|
|
66
|
-
};
|
|
67
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure(options);
|
|
68
|
-
expect(identity_gateway_service_js_1.IdentityGatewayService.sqsQueueUrl).toBe(options.asyncUpdateQueueUrl);
|
|
69
|
-
});
|
|
70
|
-
it('should configure with region/accountId/queueName', () => {
|
|
71
|
-
const options = {
|
|
72
|
-
awsRegion: 'us-east-1',
|
|
73
|
-
awsAccountId: '123456789',
|
|
74
|
-
asyncUpdateQueueName: 'identity-async-queue',
|
|
75
|
-
};
|
|
76
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure(options);
|
|
77
|
-
expect(mockSQSClientConstructor).toHaveBeenCalledWith('us-east-1', '123456789', 'identity-async-queue');
|
|
78
|
-
expect(identity_gateway_service_js_1.IdentityGatewayService.sqsClient).toBeDefined();
|
|
79
|
-
expect(identity_gateway_service_js_1.IdentityGatewayService.sqsQueueUrl).toBeDefined();
|
|
80
|
-
});
|
|
81
|
-
it('should fallback to environment variable for queue name', () => {
|
|
82
|
-
const originalEnv = process.env.IDENTIFY_ASYNC_UPDATE_QUEUE_NAME;
|
|
83
|
-
process.env.IDENTIFY_ASYNC_UPDATE_QUEUE_NAME = 'env-queue-name';
|
|
84
|
-
try {
|
|
85
|
-
const options = {
|
|
86
|
-
awsRegion: 'us-west-2',
|
|
87
|
-
awsAccountId: '987654321',
|
|
88
|
-
};
|
|
89
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure(options);
|
|
90
|
-
expect(mockSQSClientConstructor).toHaveBeenCalledWith('us-west-2', '987654321', 'env-queue-name');
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
if (originalEnv !== undefined) {
|
|
94
|
-
process.env.IDENTIFY_ASYNC_UPDATE_QUEUE_NAME = originalEnv;
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
delete process.env.IDENTIFY_ASYNC_UPDATE_QUEUE_NAME;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
it('should not configure SQS when region/accountId provided but no queue name', () => {
|
|
102
|
-
const originalEnv = process.env.IDENTIFY_ASYNC_UPDATE_QUEUE_NAME;
|
|
103
|
-
delete process.env.IDENTIFY_ASYNC_UPDATE_QUEUE_NAME;
|
|
104
|
-
try {
|
|
105
|
-
const options = {
|
|
106
|
-
awsRegion: 'us-east-1',
|
|
107
|
-
awsAccountId: '123456789',
|
|
108
|
-
};
|
|
109
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure(options);
|
|
110
|
-
expect(mockSQSClientConstructor).not.toHaveBeenCalled();
|
|
111
|
-
expect(identity_gateway_service_js_1.IdentityGatewayService.sqsClient).toBeUndefined();
|
|
112
|
-
}
|
|
113
|
-
finally {
|
|
114
|
-
if (originalEnv !== undefined) {
|
|
115
|
-
process.env.IDENTIFY_ASYNC_UPDATE_QUEUE_NAME = originalEnv;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
it('should not configure SQS when only region provided', () => {
|
|
120
|
-
const options = {
|
|
121
|
-
awsRegion: 'us-east-1',
|
|
122
|
-
};
|
|
123
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure(options);
|
|
124
|
-
expect(mockSQSClientConstructor).not.toHaveBeenCalled();
|
|
125
|
-
expect(identity_gateway_service_js_1.IdentityGatewayService.sqsClient).toBeUndefined();
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
describe('enqueueAsyncIdentityUpdate()', () => {
|
|
129
|
-
const testIdentity = {
|
|
130
|
-
identityId: 'identity456',
|
|
131
|
-
traits: { emails: ['test@email.com'] },
|
|
132
|
-
};
|
|
133
|
-
const testContext = {
|
|
134
|
-
identity: testIdentity,
|
|
135
|
-
};
|
|
136
|
-
it('should return error result when SQS client is not configured', async () => {
|
|
137
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
|
|
138
|
-
pixelId,
|
|
139
|
-
identity: testIdentity,
|
|
140
|
-
context: testContext,
|
|
141
|
-
reason: 'test reason',
|
|
142
|
-
eventId: 'event123',
|
|
143
|
-
});
|
|
144
|
-
expect(result.success).toBe(false);
|
|
145
|
-
expect(result.error).toBe('SQS client not configured');
|
|
146
|
-
expect(result.messageId).toBeUndefined();
|
|
147
|
-
expect(mockedLogger.warn).toHaveBeenCalledWith('IdentityGateway - enqueueAsyncIdentityUpdate: SQS client not configured', expect.objectContaining({
|
|
148
|
-
pixelId,
|
|
149
|
-
identityId: testIdentity.identityId,
|
|
150
|
-
reason: 'test reason',
|
|
151
|
-
eventId: 'event123',
|
|
152
|
-
}));
|
|
153
|
-
});
|
|
154
|
-
it('should successfully enqueue message when SQS client is configured', async () => {
|
|
155
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
156
|
-
awsRegion: 'us-east-1',
|
|
157
|
-
awsAccountId: '123456789',
|
|
158
|
-
asyncUpdateQueueName: 'test-queue',
|
|
159
|
-
});
|
|
160
|
-
mockBuildAndSendMessage.mockResolvedValueOnce({
|
|
161
|
-
MessageId: 'sqs-message-id-123',
|
|
162
|
-
});
|
|
163
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
|
|
164
|
-
pixelId,
|
|
165
|
-
identity: testIdentity,
|
|
166
|
-
context: testContext,
|
|
167
|
-
reason: 'stale cache traits',
|
|
168
|
-
eventId: 'event456',
|
|
169
|
-
});
|
|
170
|
-
expect(result.success).toBe(true);
|
|
171
|
-
expect(result.messageId).toBe('sqs-message-id-123');
|
|
172
|
-
expect(result.error).toBeUndefined();
|
|
173
|
-
expect(mockBuildAndSendMessage).toHaveBeenCalledWith('identify.updateAsync', expect.objectContaining({
|
|
174
|
-
pixelId,
|
|
175
|
-
incomingIdentityId: testIdentity.identityId,
|
|
176
|
-
context: testContext,
|
|
177
|
-
}));
|
|
178
|
-
expect(mockedLogger.debug).toHaveBeenCalledWith('IdentityGateway - enqueueAsyncIdentityUpdate: message sent', expect.objectContaining({
|
|
179
|
-
pixelId,
|
|
180
|
-
identityId: testIdentity.identityId,
|
|
181
|
-
reason: 'stale cache traits',
|
|
182
|
-
eventId: 'event456',
|
|
183
|
-
sqsMessageId: 'sqs-message-id-123',
|
|
184
|
-
}));
|
|
185
|
-
});
|
|
186
|
-
it('should fail open with logging when SQS send fails', async () => {
|
|
187
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
188
|
-
awsRegion: 'us-east-1',
|
|
189
|
-
awsAccountId: '123456789',
|
|
190
|
-
asyncUpdateQueueName: 'test-queue',
|
|
191
|
-
});
|
|
192
|
-
mockBuildAndSendMessage.mockRejectedValueOnce(new Error('SQS send failed'));
|
|
193
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
|
|
194
|
-
pixelId,
|
|
195
|
-
identity: testIdentity,
|
|
196
|
-
context: testContext,
|
|
197
|
-
reason: 'test reason',
|
|
198
|
-
eventId: 'event789',
|
|
199
|
-
});
|
|
200
|
-
expect(result.success).toBe(false);
|
|
201
|
-
expect(result.error).toBe('SQS send failed');
|
|
202
|
-
expect(result.messageId).toBeUndefined();
|
|
203
|
-
expect(mockedLogger.warn).toHaveBeenCalledWith('IdentityGateway - enqueueAsyncIdentityUpdate: failed to enqueue (failing open)', expect.objectContaining({
|
|
204
|
-
error: expect.any(Error),
|
|
205
|
-
errorMessage: 'SQS send failed',
|
|
206
|
-
pixelId,
|
|
207
|
-
identityId: testIdentity.identityId,
|
|
208
|
-
reason: 'test reason',
|
|
209
|
-
eventId: 'event789',
|
|
210
|
-
}));
|
|
211
|
-
});
|
|
212
|
-
it('should build message shape matching IdentifyAsyncUpdateMessage interface', async () => {
|
|
213
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
214
|
-
awsRegion: 'us-east-1',
|
|
215
|
-
awsAccountId: '123456789',
|
|
216
|
-
asyncUpdateQueueName: 'test-queue',
|
|
217
|
-
});
|
|
218
|
-
mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'test-id' });
|
|
219
|
-
const identity = {
|
|
220
|
-
identityId: 'id-xyz',
|
|
221
|
-
traits: { emails: ['user@example.com'], userIds: ['user-1'] },
|
|
222
|
-
};
|
|
223
|
-
const context = { identity };
|
|
224
|
-
await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
|
|
225
|
-
pixelId: 'pixel-abc',
|
|
226
|
-
identity,
|
|
227
|
-
context,
|
|
228
|
-
reason: 'test',
|
|
229
|
-
});
|
|
230
|
-
const messageArg = mockBuildAndSendMessage.mock.calls[0][1];
|
|
231
|
-
expect(messageArg).toEqual({
|
|
232
|
-
pixelId: 'pixel-abc',
|
|
233
|
-
incomingIdentityId: 'id-xyz',
|
|
234
|
-
context,
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
it('should return error when identity has no identityId', async () => {
|
|
238
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
239
|
-
awsRegion: 'us-east-1',
|
|
240
|
-
awsAccountId: '123456789',
|
|
241
|
-
asyncUpdateQueueName: 'test-queue',
|
|
242
|
-
});
|
|
243
|
-
const identityWithoutId = {
|
|
244
|
-
traits: { emails: ['anon@example.com'] },
|
|
245
|
-
};
|
|
246
|
-
const context = { identity: identityWithoutId };
|
|
247
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
|
|
248
|
-
pixelId,
|
|
249
|
-
identity: identityWithoutId,
|
|
250
|
-
context,
|
|
251
|
-
reason: 'anonymous identity',
|
|
252
|
-
});
|
|
253
|
-
expect(result.success).toBe(false);
|
|
254
|
-
expect(result.error).toBe('Missing required pixelId or identityId');
|
|
255
|
-
expect(mockBuildAndSendMessage).not.toHaveBeenCalled();
|
|
256
|
-
});
|
|
257
|
-
it('should handle optional eventId parameter', async () => {
|
|
258
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
259
|
-
awsRegion: 'us-east-1',
|
|
260
|
-
awsAccountId: '123456789',
|
|
261
|
-
asyncUpdateQueueName: 'test-queue',
|
|
262
|
-
});
|
|
263
|
-
mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'test-id' });
|
|
264
|
-
await identity_gateway_service_js_1.IdentityGatewayService.enqueueAsyncIdentityUpdate({
|
|
265
|
-
pixelId,
|
|
266
|
-
identity: testIdentity,
|
|
267
|
-
context: testContext,
|
|
268
|
-
reason: 'test',
|
|
269
|
-
});
|
|
270
|
-
expect(mockedLogger.debug).toHaveBeenCalledWith('IdentityGateway - enqueueAsyncIdentityUpdate: message sent', expect.objectContaining({
|
|
271
|
-
eventId: undefined,
|
|
272
|
-
}));
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
describe('getIdentityWithCachingV2() with skipCacheWrite option', () => {
|
|
276
|
-
const incomingIdentity = {
|
|
277
|
-
identityId: 'identity456',
|
|
278
|
-
traits: { emails: ['test@email.com'] },
|
|
279
|
-
};
|
|
280
|
-
describe('default behavior (no options)', () => {
|
|
281
|
-
it('should write to cache when Neptune returns new identity', async () => {
|
|
282
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
283
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
284
|
-
statusCode: 200,
|
|
285
|
-
body: JSON.stringify({
|
|
286
|
-
identity: {
|
|
287
|
-
identityId: 'resolved-identity',
|
|
288
|
-
traits: { emails: ['test@email.com', 'resolved@email.com'] },
|
|
289
|
-
},
|
|
290
|
-
}),
|
|
291
|
-
});
|
|
292
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
293
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
294
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
describe('skipCacheWrite: false', () => {
|
|
298
|
-
it('should write to cache (same as default)', async () => {
|
|
299
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
300
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
301
|
-
statusCode: 200,
|
|
302
|
-
body: JSON.stringify({
|
|
303
|
-
identity: {
|
|
304
|
-
identityId: 'resolved-identity',
|
|
305
|
-
traits: { emails: ['test@email.com'] },
|
|
306
|
-
},
|
|
307
|
-
}),
|
|
308
|
-
});
|
|
309
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn, { skipCacheWrite: false });
|
|
310
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
311
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
describe('skipCacheWrite: true', () => {
|
|
315
|
-
it('should NOT write to cache on Neptune write-back path', async () => {
|
|
316
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
317
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
318
|
-
statusCode: 200,
|
|
319
|
-
body: JSON.stringify({
|
|
320
|
-
identity: {
|
|
321
|
-
identityId: 'resolved-identity',
|
|
322
|
-
traits: { emails: ['test@email.com', 'resolved@email.com'] },
|
|
323
|
-
},
|
|
324
|
-
}),
|
|
325
|
-
});
|
|
326
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn, { skipCacheWrite: true });
|
|
327
|
-
expect(result.identity?.identityId).toBe('resolved-identity');
|
|
328
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
329
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
330
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
331
|
-
});
|
|
332
|
-
it('should enqueue async update on address update path (never writes sync)', async () => {
|
|
333
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
334
|
-
awsRegion: 'us-east-1',
|
|
335
|
-
awsAccountId: '123456789',
|
|
336
|
-
asyncUpdateQueueName: 'test-queue',
|
|
337
|
-
});
|
|
338
|
-
mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'test-msg-id' });
|
|
339
|
-
const cachedResponse = {
|
|
340
|
-
pk: `identity#${pixelId}#identity456`,
|
|
341
|
-
pixelId,
|
|
342
|
-
identityId: 'identity456',
|
|
343
|
-
response: {
|
|
344
|
-
identityId: 'identity456',
|
|
345
|
-
traits: {
|
|
346
|
-
emails: ['test@email.com'],
|
|
347
|
-
addresses: [{ city: 'Old City', upa: 1704067200000 }],
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
updatedAt: new Date().toISOString(),
|
|
351
|
-
};
|
|
352
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
353
|
-
const identityWithNewAddress = {
|
|
354
|
-
identityId: 'identity456',
|
|
355
|
-
traits: {
|
|
356
|
-
emails: ['test@email.com'],
|
|
357
|
-
addresses: [{ city: 'New City', upa: 1717200000000 }],
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, identityWithNewAddress, lambdaArn, { skipCacheWrite: true });
|
|
361
|
-
if (result.summary.outcome === types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC) {
|
|
362
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
363
|
-
expect(result.summary.didEnqueueAsync).toBe(true);
|
|
364
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
365
|
-
expect(mockBuildAndSendMessage).toHaveBeenCalled();
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
it('should still return resolved identity when skipCacheWrite is true', async () => {
|
|
369
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
370
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
371
|
-
statusCode: 200,
|
|
372
|
-
body: JSON.stringify({
|
|
373
|
-
identity: {
|
|
374
|
-
identityId: 'neptune-resolved-id',
|
|
375
|
-
traits: { emails: ['test@email.com', 'new@email.com'] },
|
|
376
|
-
},
|
|
377
|
-
}),
|
|
378
|
-
});
|
|
379
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn, { skipCacheWrite: true });
|
|
380
|
-
expect(result.identity).toBeDefined();
|
|
381
|
-
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' });
|
|
1136
|
-
const cachedResponse = {
|
|
1137
|
-
pk: `identity#${pixelId}#identity456`,
|
|
1138
|
-
pixelId,
|
|
1139
|
-
identityId: 'identity456',
|
|
1140
|
-
response: {
|
|
1141
|
-
identityId: 'identity456',
|
|
1142
|
-
traits: {
|
|
1143
|
-
emails: ['test@email.com'],
|
|
1144
|
-
addresses: [],
|
|
1145
|
-
},
|
|
1146
|
-
},
|
|
1147
|
-
updatedAt: new Date().toISOString(),
|
|
1148
|
-
};
|
|
1149
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1150
|
-
const incomingWithNonIpAddress = {
|
|
1151
|
-
identityId: 'identity456',
|
|
1152
|
-
traits: {
|
|
1153
|
-
emails: ['test@email.com'],
|
|
1154
|
-
addresses: [{ firstName: 'John', city: 'NonIPCity' }],
|
|
1155
|
-
},
|
|
1156
|
-
};
|
|
1157
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingWithNonIpAddress, lambdaArn);
|
|
1158
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
|
|
1159
|
-
expect(result.summary.didEnqueueAsync).toBe(true);
|
|
1160
|
-
expect(mockBuildAndSendMessage).toHaveBeenCalled();
|
|
1161
|
-
});
|
|
1162
|
-
});
|
|
1163
|
-
});
|
|
1164
|
-
describe('refactored flow: merge and staleness computation', () => {
|
|
1165
|
-
describe('mergeAndComputeIdentityStaleness behavior', () => {
|
|
1166
|
-
it('should merge incoming traits with cached traits on cache hit', async () => {
|
|
1167
|
-
const cachedResponse = {
|
|
1168
|
-
pk: `identity#${pixelId}#merge-test`,
|
|
1169
|
-
pixelId,
|
|
1170
|
-
identityId: 'merge-test',
|
|
1171
|
-
response: {
|
|
1172
|
-
identityId: 'merge-test',
|
|
1173
|
-
traits: {
|
|
1174
|
-
emails: ['cached@email.com'],
|
|
1175
|
-
phones: ['555-cached'],
|
|
1176
|
-
},
|
|
1177
|
-
},
|
|
1178
|
-
updatedAt: new Date().toISOString(),
|
|
1179
|
-
};
|
|
1180
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1181
|
-
const incomingIdentity = {
|
|
1182
|
-
identityId: 'merge-test',
|
|
1183
|
-
traits: {
|
|
1184
|
-
emails: ['cached@email.com'],
|
|
1185
|
-
},
|
|
1186
|
-
};
|
|
1187
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1188
|
-
expect(result.identity?.traits?.emails).toContain('cached@email.com');
|
|
1189
|
-
expect(result.identity?.traits?.phones).toContain('555-cached');
|
|
1190
|
-
});
|
|
1191
|
-
it('should compute staleness after cache hit', async () => {
|
|
1192
|
-
const cachedResponse = {
|
|
1193
|
-
pk: `identity#${pixelId}#staleness-compute`,
|
|
1194
|
-
pixelId,
|
|
1195
|
-
identityId: 'staleness-compute',
|
|
1196
|
-
response: {
|
|
1197
|
-
identityId: 'staleness-compute',
|
|
1198
|
-
traits: {
|
|
1199
|
-
emails: ['old@email.com'],
|
|
1200
|
-
},
|
|
1201
|
-
},
|
|
1202
|
-
updatedAt: new Date().toISOString(),
|
|
1203
|
-
};
|
|
1204
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1205
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1206
|
-
statusCode: 200,
|
|
1207
|
-
body: JSON.stringify({
|
|
1208
|
-
identity: {
|
|
1209
|
-
identityId: 'staleness-compute',
|
|
1210
|
-
traits: { emails: ['old@email.com', 'new@email.com'] },
|
|
1211
|
-
},
|
|
1212
|
-
}),
|
|
1213
|
-
});
|
|
1214
|
-
const incomingIdentity = {
|
|
1215
|
-
identityId: 'staleness-compute',
|
|
1216
|
-
traits: {
|
|
1217
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1218
|
-
},
|
|
1219
|
-
};
|
|
1220
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1221
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
1222
|
-
expect(result.summary.staleTraits).toContain('emails');
|
|
1223
|
-
});
|
|
1224
|
-
});
|
|
1225
|
-
describe('deep equality check', () => {
|
|
1226
|
-
it('should treat as cache hit when merged identity is deep equal to cached', async () => {
|
|
1227
|
-
const cachedResponse = {
|
|
1228
|
-
pk: `identity#${pixelId}#deep-equal`,
|
|
1229
|
-
pixelId,
|
|
1230
|
-
identityId: 'deep-equal',
|
|
1231
|
-
response: {
|
|
1232
|
-
identityId: 'deep-equal',
|
|
1233
|
-
traits: {
|
|
1234
|
-
emails: ['same@email.com'],
|
|
1235
|
-
phones: ['555-1234'],
|
|
1236
|
-
},
|
|
1237
|
-
},
|
|
1238
|
-
updatedAt: new Date().toISOString(),
|
|
1239
|
-
};
|
|
1240
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1241
|
-
const incomingIdentity = {
|
|
1242
|
-
identityId: 'deep-equal',
|
|
1243
|
-
traits: {
|
|
1244
|
-
emails: ['same@email.com'],
|
|
1245
|
-
phones: ['555-1234'],
|
|
1246
|
-
},
|
|
1247
|
-
};
|
|
1248
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1249
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
|
|
1250
|
-
expect(result.summary.didCallNeptune).toBe(false);
|
|
1251
|
-
expect(mockInvokeFunction).not.toHaveBeenCalled();
|
|
1252
|
-
});
|
|
1253
|
-
it('should strip address timestamps before deep equality comparison', async () => {
|
|
1254
|
-
const now = Date.now();
|
|
1255
|
-
const cachedResponse = {
|
|
1256
|
-
pk: `identity#${pixelId}#timestamp-strip`,
|
|
1257
|
-
pixelId,
|
|
1258
|
-
identityId: 'timestamp-strip',
|
|
1259
|
-
response: {
|
|
1260
|
-
identityId: 'timestamp-strip',
|
|
1261
|
-
traits: {
|
|
1262
|
-
emails: ['test@email.com'],
|
|
1263
|
-
addresses: [{ city: 'NYC', cra: now - 10000, upa: now - 5000 }],
|
|
1264
|
-
},
|
|
1265
|
-
},
|
|
1266
|
-
updatedAt: new Date().toISOString(),
|
|
1267
|
-
};
|
|
1268
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1269
|
-
const incomingIdentity = {
|
|
1270
|
-
identityId: 'timestamp-strip',
|
|
1271
|
-
traits: {
|
|
1272
|
-
emails: ['test@email.com'],
|
|
1273
|
-
addresses: [{ city: 'NYC', cra: now, upa: now }],
|
|
1274
|
-
},
|
|
1275
|
-
};
|
|
1276
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1277
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT);
|
|
1278
|
-
expect(result.summary.didCallNeptune).toBe(false);
|
|
1279
|
-
});
|
|
1280
|
-
});
|
|
1281
|
-
describe('volatile-only override', () => {
|
|
1282
|
-
it('should treat VOLATILE_ONLY + not deep equal as cache hit', async () => {
|
|
1283
|
-
const cachedResponse = {
|
|
1284
|
-
pk: `identity#${pixelId}#volatile-override`,
|
|
1285
|
-
pixelId,
|
|
1286
|
-
identityId: 'volatile-override',
|
|
1287
|
-
response: {
|
|
1288
|
-
identityId: 'volatile-override',
|
|
1289
|
-
traits: {
|
|
1290
|
-
emails: ['same@email.com'],
|
|
1291
|
-
},
|
|
1292
|
-
},
|
|
1293
|
-
updatedAt: new Date().toISOString(),
|
|
1294
|
-
};
|
|
1295
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1296
|
-
const incomingIdentity = {
|
|
1297
|
-
identityId: 'volatile-override',
|
|
1298
|
-
traits: {
|
|
1299
|
-
emails: ['same@email.com'],
|
|
1300
|
-
ipAddress: '10.0.0.1',
|
|
1301
|
-
userAgent: 'Mozilla/5.0 (New)',
|
|
1302
|
-
},
|
|
1303
|
-
};
|
|
1304
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1305
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.VOLATILE_ONLY_CACHE_HIT);
|
|
1306
|
-
expect(result.summary.didCallNeptune).toBe(false);
|
|
1307
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1308
|
-
});
|
|
1309
|
-
it('should return merged identity for VOLATILE_ONLY tier', async () => {
|
|
1310
|
-
const cachedResponse = {
|
|
1311
|
-
pk: `identity#${pixelId}#volatile-merged`,
|
|
1312
|
-
pixelId,
|
|
1313
|
-
identityId: 'volatile-merged',
|
|
1314
|
-
response: {
|
|
1315
|
-
identityId: 'volatile-merged',
|
|
1316
|
-
traits: {
|
|
1317
|
-
emails: ['test@email.com'],
|
|
1318
|
-
phones: ['555-cached'],
|
|
1319
|
-
},
|
|
1320
|
-
},
|
|
1321
|
-
updatedAt: new Date().toISOString(),
|
|
1322
|
-
};
|
|
1323
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1324
|
-
const incomingIdentity = {
|
|
1325
|
-
identityId: 'volatile-merged',
|
|
1326
|
-
traits: {
|
|
1327
|
-
emails: ['test@email.com'],
|
|
1328
|
-
ipAddress: '10.0.0.1',
|
|
1329
|
-
},
|
|
1330
|
-
};
|
|
1331
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1332
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.VOLATILE_ONLY_CACHE_HIT);
|
|
1333
|
-
expect(result.identity?.traits?.emails).toContain('test@email.com');
|
|
1334
|
-
expect(result.identity?.traits?.phones).toContain('555-cached');
|
|
1335
|
-
expect(result.identity?.traits?.ipAddress).toBe('10.0.0.1');
|
|
1336
|
-
});
|
|
1337
|
-
});
|
|
1338
|
-
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
|
-
it('should set skipNeptune=false for IDENTITY_STALE tier', async () => {
|
|
1373
|
-
const cachedResponse = {
|
|
1374
|
-
pk: `identity#${pixelId}#call-neptune`,
|
|
1375
|
-
pixelId,
|
|
1376
|
-
identityId: 'call-neptune',
|
|
1377
|
-
response: {
|
|
1378
|
-
identityId: 'call-neptune',
|
|
1379
|
-
traits: {
|
|
1380
|
-
emails: ['old@email.com'],
|
|
1381
|
-
},
|
|
1382
|
-
},
|
|
1383
|
-
updatedAt: new Date().toISOString(),
|
|
1384
|
-
};
|
|
1385
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1386
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1387
|
-
statusCode: 200,
|
|
1388
|
-
body: JSON.stringify({
|
|
1389
|
-
identity: {
|
|
1390
|
-
identityId: 'call-neptune',
|
|
1391
|
-
traits: { emails: ['old@email.com', 'new@email.com'] },
|
|
1392
|
-
},
|
|
1393
|
-
}),
|
|
1394
|
-
});
|
|
1395
|
-
const incomingIdentity = {
|
|
1396
|
-
identityId: 'call-neptune',
|
|
1397
|
-
traits: {
|
|
1398
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1399
|
-
},
|
|
1400
|
-
};
|
|
1401
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1402
|
-
expect(result.summary.didSkipNeptune).toBe(false);
|
|
1403
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
1404
|
-
expect(mockInvokeFunction).toHaveBeenCalled();
|
|
1405
|
-
});
|
|
1406
|
-
});
|
|
1407
|
-
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
|
-
it('should set writeReason=stale_traits for IDENTITY_STALE tier', async () => {
|
|
1440
|
-
const cachedResponse = {
|
|
1441
|
-
pk: `identity#${pixelId}#stale-reason`,
|
|
1442
|
-
pixelId,
|
|
1443
|
-
identityId: 'stale-reason',
|
|
1444
|
-
response: {
|
|
1445
|
-
identityId: 'stale-reason',
|
|
1446
|
-
traits: {
|
|
1447
|
-
emails: ['old@email.com'],
|
|
1448
|
-
},
|
|
1449
|
-
},
|
|
1450
|
-
updatedAt: new Date().toISOString(),
|
|
1451
|
-
};
|
|
1452
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1453
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1454
|
-
statusCode: 200,
|
|
1455
|
-
body: JSON.stringify({
|
|
1456
|
-
identity: {
|
|
1457
|
-
identityId: 'stale-reason',
|
|
1458
|
-
traits: { emails: ['old@email.com', 'new@email.com'] },
|
|
1459
|
-
},
|
|
1460
|
-
}),
|
|
1461
|
-
});
|
|
1462
|
-
const incomingIdentity = {
|
|
1463
|
-
identityId: 'stale-reason',
|
|
1464
|
-
traits: {
|
|
1465
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1466
|
-
},
|
|
1467
|
-
};
|
|
1468
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1469
|
-
expect(result.summary.writeReason).toBeUndefined();
|
|
1470
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1471
|
-
});
|
|
1472
|
-
});
|
|
1473
|
-
describe('ADDRESS_UPDATE → enqueueAsyncIdentityUpdate path', () => {
|
|
1474
|
-
it('should enqueue async update instead of calling Neptune', async () => {
|
|
1475
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
1476
|
-
awsRegion: 'us-east-1',
|
|
1477
|
-
awsAccountId: '123456789',
|
|
1478
|
-
asyncUpdateQueueName: 'identity-async-queue',
|
|
1479
|
-
});
|
|
1480
|
-
mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'async-path-test' });
|
|
1481
|
-
const cachedResponse = {
|
|
1482
|
-
pk: `identity#${pixelId}#async-enqueue`,
|
|
1483
|
-
pixelId,
|
|
1484
|
-
identityId: 'async-enqueue',
|
|
1485
|
-
response: {
|
|
1486
|
-
identityId: 'async-enqueue',
|
|
1487
|
-
traits: {
|
|
1488
|
-
emails: ['test@email.com'],
|
|
1489
|
-
addresses: [],
|
|
1490
|
-
},
|
|
1491
|
-
},
|
|
1492
|
-
updatedAt: new Date().toISOString(),
|
|
1493
|
-
};
|
|
1494
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1495
|
-
const incomingIdentity = {
|
|
1496
|
-
identityId: 'async-enqueue',
|
|
1497
|
-
traits: {
|
|
1498
|
-
emails: ['test@email.com'],
|
|
1499
|
-
addresses: [{ firstName: 'Async', city: 'AsyncCity' }],
|
|
1500
|
-
},
|
|
1501
|
-
};
|
|
1502
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1503
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.ADDRESS_UPDATE_ASYNC);
|
|
1504
|
-
expect(result.summary.didEnqueueAsync).toBe(true);
|
|
1505
|
-
expect(result.summary.didCallNeptune).toBe(false);
|
|
1506
|
-
expect(mockBuildAndSendMessage).toHaveBeenCalledWith('identify.updateAsync', expect.objectContaining({
|
|
1507
|
-
pixelId,
|
|
1508
|
-
incomingIdentityId: 'async-enqueue',
|
|
1509
|
-
}));
|
|
1510
|
-
expect(mockInvokeFunction).not.toHaveBeenCalled();
|
|
1511
|
-
});
|
|
1512
|
-
it('should return merged identity immediately (no Neptune wait)', async () => {
|
|
1513
|
-
identity_gateway_service_js_1.IdentityGatewayService.configure({
|
|
1514
|
-
awsRegion: 'us-east-1',
|
|
1515
|
-
awsAccountId: '123456789',
|
|
1516
|
-
asyncUpdateQueueName: 'identity-async-queue',
|
|
1517
|
-
});
|
|
1518
|
-
mockBuildAndSendMessage.mockResolvedValueOnce({ MessageId: 'immediate-return' });
|
|
1519
|
-
const cachedResponse = {
|
|
1520
|
-
pk: `identity#${pixelId}#immediate`,
|
|
1521
|
-
pixelId,
|
|
1522
|
-
identityId: 'immediate',
|
|
1523
|
-
response: {
|
|
1524
|
-
identityId: 'immediate',
|
|
1525
|
-
traits: {
|
|
1526
|
-
emails: ['test@email.com'],
|
|
1527
|
-
phones: ['555-cached'],
|
|
1528
|
-
addresses: [],
|
|
1529
|
-
},
|
|
1530
|
-
},
|
|
1531
|
-
updatedAt: new Date().toISOString(),
|
|
1532
|
-
};
|
|
1533
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1534
|
-
const incomingIdentity = {
|
|
1535
|
-
identityId: 'immediate',
|
|
1536
|
-
traits: {
|
|
1537
|
-
emails: ['test@email.com'],
|
|
1538
|
-
addresses: [{ firstName: 'Immediate', city: 'ImmediateCity' }],
|
|
1539
|
-
},
|
|
1540
|
-
};
|
|
1541
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1542
|
-
expect(result.identity).toBeDefined();
|
|
1543
|
-
expect(result.identity?.identityId).toBe('immediate');
|
|
1544
|
-
expect(result.identity?.traits?.phones).toContain('555-cached');
|
|
1545
|
-
expect(result.identity?.traits?.addresses).toHaveLength(1);
|
|
1546
|
-
});
|
|
1547
|
-
});
|
|
1548
|
-
describe('IDENTITY_STALE → Neptune path', () => {
|
|
1549
|
-
it('should call Neptune for identity-defining trait changes', async () => {
|
|
1550
|
-
const cachedResponse = {
|
|
1551
|
-
pk: `identity#${pixelId}#neptune-path`,
|
|
1552
|
-
pixelId,
|
|
1553
|
-
identityId: 'neptune-path',
|
|
1554
|
-
response: {
|
|
1555
|
-
identityId: 'neptune-path',
|
|
1556
|
-
traits: {
|
|
1557
|
-
emails: ['old@email.com'],
|
|
1558
|
-
},
|
|
1559
|
-
},
|
|
1560
|
-
updatedAt: new Date().toISOString(),
|
|
1561
|
-
};
|
|
1562
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1563
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1564
|
-
statusCode: 200,
|
|
1565
|
-
body: JSON.stringify({
|
|
1566
|
-
identity: {
|
|
1567
|
-
identityId: 'neptune-path',
|
|
1568
|
-
traits: { emails: ['old@email.com', 'new@email.com'] },
|
|
1569
|
-
},
|
|
1570
|
-
}),
|
|
1571
|
-
});
|
|
1572
|
-
const incomingIdentity = {
|
|
1573
|
-
identityId: 'neptune-path',
|
|
1574
|
-
traits: {
|
|
1575
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1576
|
-
},
|
|
1577
|
-
};
|
|
1578
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1579
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
1580
|
-
expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
|
|
1581
|
-
pixelId,
|
|
1582
|
-
}));
|
|
1583
|
-
});
|
|
1584
|
-
it('should write Neptune result to cache when different from cached', async () => {
|
|
1585
|
-
const cachedResponse = {
|
|
1586
|
-
pk: `identity#${pixelId}#neptune-write`,
|
|
1587
|
-
pixelId,
|
|
1588
|
-
identityId: 'neptune-write',
|
|
1589
|
-
response: {
|
|
1590
|
-
identityId: 'neptune-write',
|
|
1591
|
-
traits: {
|
|
1592
|
-
emails: ['old@email.com'],
|
|
1593
|
-
},
|
|
1594
|
-
},
|
|
1595
|
-
updatedAt: new Date().toISOString(),
|
|
1596
|
-
};
|
|
1597
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1598
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1599
|
-
statusCode: 200,
|
|
1600
|
-
body: JSON.stringify({
|
|
1601
|
-
identity: {
|
|
1602
|
-
identityId: 'neptune-write',
|
|
1603
|
-
traits: {
|
|
1604
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1605
|
-
phones: ['555-neptune'],
|
|
1606
|
-
},
|
|
1607
|
-
},
|
|
1608
|
-
}),
|
|
1609
|
-
});
|
|
1610
|
-
const incomingIdentity = {
|
|
1611
|
-
identityId: 'neptune-write',
|
|
1612
|
-
traits: {
|
|
1613
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1614
|
-
},
|
|
1615
|
-
};
|
|
1616
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1617
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1618
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1619
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
1620
|
-
});
|
|
1621
|
-
it('should skip cache write when Neptune result matches cached', async () => {
|
|
1622
|
-
const cachedResponse = {
|
|
1623
|
-
pk: `identity#${pixelId}#neptune-skip-write`,
|
|
1624
|
-
pixelId,
|
|
1625
|
-
identityId: 'neptune-skip-write',
|
|
1626
|
-
response: {
|
|
1627
|
-
identityId: 'neptune-skip-write',
|
|
1628
|
-
traits: {
|
|
1629
|
-
emails: ['existing@email.com'],
|
|
1630
|
-
},
|
|
1631
|
-
},
|
|
1632
|
-
updatedAt: new Date().toISOString(),
|
|
1633
|
-
};
|
|
1634
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1635
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1636
|
-
statusCode: 200,
|
|
1637
|
-
body: JSON.stringify({
|
|
1638
|
-
identity: {
|
|
1639
|
-
identityId: 'neptune-skip-write',
|
|
1640
|
-
traits: {
|
|
1641
|
-
emails: ['existing@email.com'],
|
|
1642
|
-
},
|
|
1643
|
-
},
|
|
1644
|
-
}),
|
|
1645
|
-
});
|
|
1646
|
-
const incomingIdentity = {
|
|
1647
|
-
identityId: 'neptune-skip-write',
|
|
1648
|
-
traits: {
|
|
1649
|
-
emails: ['existing@email.com', 'thought-new@email.com'],
|
|
1650
|
-
},
|
|
1651
|
-
};
|
|
1652
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1653
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
1654
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1655
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1656
|
-
});
|
|
1657
|
-
it('should return NEPTUNE_RESOLUTION_FAILED when Neptune throws', async () => {
|
|
1658
|
-
const cachedResponse = {
|
|
1659
|
-
pk: `identity#${pixelId}#neptune-throws`,
|
|
1660
|
-
pixelId,
|
|
1661
|
-
identityId: 'neptune-throws',
|
|
1662
|
-
response: {
|
|
1663
|
-
identityId: 'neptune-throws',
|
|
1664
|
-
traits: {
|
|
1665
|
-
emails: ['old@email.com'],
|
|
1666
|
-
},
|
|
1667
|
-
},
|
|
1668
|
-
updatedAt: new Date().toISOString(),
|
|
1669
|
-
};
|
|
1670
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1671
|
-
mockInvokeFunction.mockRejectedValueOnce(new Error('Neptune invocation failed'));
|
|
1672
|
-
const incomingIdentity = {
|
|
1673
|
-
identityId: 'neptune-throws',
|
|
1674
|
-
traits: {
|
|
1675
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1676
|
-
},
|
|
1677
|
-
};
|
|
1678
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1679
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
1680
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1681
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
|
|
1682
|
-
expect(result.identity).toBeDefined();
|
|
1683
|
-
expect(result.identity?.identityId).toBe('neptune-throws');
|
|
1684
|
-
expect(result.identity?.traits?.emails).toEqual(['old@email.com', 'new@email.com']);
|
|
1685
|
-
});
|
|
1686
|
-
});
|
|
1687
|
-
});
|
|
1688
|
-
describe('resolveCacheMissViaNeptune helper', () => {
|
|
1689
|
-
describe('cache miss without discovered identity ID', () => {
|
|
1690
|
-
it('should call Neptune with incoming identity when no discovered ID', async () => {
|
|
1691
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1692
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1693
|
-
statusCode: 200,
|
|
1694
|
-
body: JSON.stringify({
|
|
1695
|
-
identity: {
|
|
1696
|
-
identityId: 'neptune-resolved-id',
|
|
1697
|
-
traits: { emails: ['test@email.com'] },
|
|
1698
|
-
},
|
|
1699
|
-
}),
|
|
1700
|
-
});
|
|
1701
|
-
const incomingIdentity = {
|
|
1702
|
-
identityId: 'incoming-id',
|
|
1703
|
-
traits: { emails: ['test@email.com'] },
|
|
1704
|
-
};
|
|
1705
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1706
|
-
expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
|
|
1707
|
-
pixelId,
|
|
1708
|
-
context: expect.objectContaining({
|
|
1709
|
-
identity: expect.objectContaining({
|
|
1710
|
-
identityId: 'incoming-id',
|
|
1711
|
-
}),
|
|
1712
|
-
}),
|
|
1713
|
-
}));
|
|
1714
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1715
|
-
expect(result.summary.hasDiscoveredIdentityId).toBeUndefined();
|
|
1716
|
-
expect(result.summary.writeReason).toBeUndefined();
|
|
1717
|
-
});
|
|
1718
|
-
it('should set state.didReadCache=true and state.didCallNeptune=true', async () => {
|
|
1719
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1720
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1721
|
-
statusCode: 200,
|
|
1722
|
-
body: JSON.stringify({
|
|
1723
|
-
identity: { identityId: 'resolved', traits: {} },
|
|
1724
|
-
}),
|
|
1725
|
-
});
|
|
1726
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'test', traits: {} }, lambdaArn);
|
|
1727
|
-
expect(result.summary.didReadCache).toBe(true);
|
|
1728
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
1729
|
-
});
|
|
1730
|
-
});
|
|
1731
|
-
describe('email pointer discovery with discovered identity ID', () => {
|
|
1732
|
-
it('should call Neptune with discovered identity ID when email lookup succeeds', async () => {
|
|
1733
|
-
const incomingNoId = {
|
|
1734
|
-
traits: { emails: ['discovered@email.com'] },
|
|
1735
|
-
};
|
|
1736
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
|
|
1737
|
-
pk: `email#${pixelId}#discovered@email.com`,
|
|
1738
|
-
identityId: 'discovered-identity-id',
|
|
1739
|
-
});
|
|
1740
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1741
|
-
statusCode: 200,
|
|
1742
|
-
body: JSON.stringify({
|
|
1743
|
-
identity: {
|
|
1744
|
-
identityId: 'discovered-identity-id',
|
|
1745
|
-
traits: { emails: ['discovered@email.com', 'other@email.com'] },
|
|
1746
|
-
},
|
|
1747
|
-
}),
|
|
1748
|
-
});
|
|
1749
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingNoId, lambdaArn);
|
|
1750
|
-
expect(result.identity?.identityId).toBe('discovered-identity-id');
|
|
1751
|
-
});
|
|
1752
|
-
it('should use incoming identity in Neptune call (not discovered ID)', async () => {
|
|
1753
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
|
|
1754
|
-
pk: `email#${pixelId}#test@email.com`,
|
|
1755
|
-
identityId: 'discovered-from-email',
|
|
1756
|
-
});
|
|
1757
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1758
|
-
statusCode: 200,
|
|
1759
|
-
body: JSON.stringify({
|
|
1760
|
-
identity: {
|
|
1761
|
-
identityId: 'neptune-resolved',
|
|
1762
|
-
traits: { emails: ['test@email.com'] },
|
|
1763
|
-
},
|
|
1764
|
-
}),
|
|
1765
|
-
});
|
|
1766
|
-
await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
|
|
1767
|
-
expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
|
|
1768
|
-
pixelId,
|
|
1769
|
-
context: expect.objectContaining({
|
|
1770
|
-
identity: expect.objectContaining({
|
|
1771
|
-
traits: { emails: ['test@email.com'] },
|
|
1772
|
-
}),
|
|
1773
|
-
}),
|
|
1774
|
-
}));
|
|
1775
|
-
});
|
|
1776
|
-
});
|
|
1777
|
-
describe('Neptune resolution success → write-back', () => {
|
|
1778
|
-
it('should write to cache when Neptune returns identity', async () => {
|
|
1779
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1780
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1781
|
-
statusCode: 200,
|
|
1782
|
-
body: JSON.stringify({
|
|
1783
|
-
identity: {
|
|
1784
|
-
identityId: 'neptune-success',
|
|
1785
|
-
traits: { emails: ['test@email.com'], phones: ['555-1234'] },
|
|
1786
|
-
},
|
|
1787
|
-
}),
|
|
1788
|
-
});
|
|
1789
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'incoming', traits: { emails: ['test@email.com'] } }, lambdaArn);
|
|
1790
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1791
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1792
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
1793
|
-
});
|
|
1794
|
-
it('should set resolvedIdentity to Neptune response', async () => {
|
|
1795
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1796
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1797
|
-
statusCode: 200,
|
|
1798
|
-
body: JSON.stringify({
|
|
1799
|
-
identity: {
|
|
1800
|
-
identityId: 'from-neptune',
|
|
1801
|
-
traits: { emails: ['neptune@email.com'] },
|
|
1802
|
-
},
|
|
1803
|
-
}),
|
|
1804
|
-
});
|
|
1805
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'original', traits: { emails: ['original@email.com'] } }, lambdaArn);
|
|
1806
|
-
expect(result.identity?.identityId).toBe('from-neptune');
|
|
1807
|
-
expect(result.identity?.traits?.emails).toContain('neptune@email.com');
|
|
1808
|
-
});
|
|
1809
|
-
it('should include traitChanges with "no cached identity" reason', async () => {
|
|
1810
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1811
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1812
|
-
statusCode: 200,
|
|
1813
|
-
body: JSON.stringify({
|
|
1814
|
-
identity: {
|
|
1815
|
-
identityId: 'new-identity',
|
|
1816
|
-
traits: { emails: ['new@email.com'] },
|
|
1817
|
-
},
|
|
1818
|
-
}),
|
|
1819
|
-
});
|
|
1820
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'incoming', traits: { emails: ['incoming@email.com'] } }, lambdaArn);
|
|
1821
|
-
expect(result.summary.traitChanges).toEqual({
|
|
1822
|
-
reason: { resolved: 'no cached identity' },
|
|
1823
|
-
});
|
|
1824
|
-
});
|
|
1825
|
-
});
|
|
1826
|
-
describe('Neptune resolution failure → fallback to incoming', () => {
|
|
1827
|
-
it('should fallback to incoming identity when Neptune fails', async () => {
|
|
1828
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1829
|
-
mockInvokeFunction.mockRejectedValueOnce(new Error('Neptune unavailable'));
|
|
1830
|
-
const incomingIdentity = {
|
|
1831
|
-
identityId: 'fallback-incoming',
|
|
1832
|
-
traits: { emails: ['fallback@email.com'] },
|
|
1833
|
-
};
|
|
1834
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
|
|
1835
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
|
|
1836
|
-
expect(result.summary.didFallbackToIncoming).toBe(true);
|
|
1837
|
-
expect(result.identity?.identityId).toBe('fallback-incoming');
|
|
1838
|
-
});
|
|
1839
|
-
it('should set didWriteCache=false when Neptune fails', async () => {
|
|
1840
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1841
|
-
mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda timeout'));
|
|
1842
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'test', traits: {} }, lambdaArn);
|
|
1843
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1844
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
1845
|
-
});
|
|
1846
|
-
it('should use identityForNeptune (with discovered ID) as fallback when available', async () => {
|
|
1847
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
|
|
1848
|
-
pk: `email#${pixelId}#test@email.com`,
|
|
1849
|
-
identityId: 'discovered-id',
|
|
1850
|
-
});
|
|
1851
|
-
mockInvokeFunction.mockRejectedValueOnce(new Error('Neptune error'));
|
|
1852
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
|
|
1853
|
-
expect(result.summary.didFallbackToIncoming).toBe(true);
|
|
1854
|
-
expect(result.identity?.identityId).toBe('discovered-id');
|
|
1855
|
-
expect(result.identity?.traits?.emails).toContain('test@email.com');
|
|
1856
|
-
});
|
|
1857
|
-
});
|
|
1858
|
-
describe('skipCacheWrite=true behavior', () => {
|
|
1859
|
-
it('should NOT write to cache when skipCacheWrite=true', async () => {
|
|
1860
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1861
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1862
|
-
statusCode: 200,
|
|
1863
|
-
body: JSON.stringify({
|
|
1864
|
-
identity: {
|
|
1865
|
-
identityId: 'skip-write-identity',
|
|
1866
|
-
traits: { emails: ['test@email.com'] },
|
|
1867
|
-
},
|
|
1868
|
-
}),
|
|
1869
|
-
});
|
|
1870
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'incoming', traits: { emails: ['test@email.com'] } }, lambdaArn, { skipCacheWrite: true });
|
|
1871
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1872
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
1873
|
-
expect(result.identity?.identityId).toBe('skip-write-identity');
|
|
1874
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1875
|
-
});
|
|
1876
|
-
it('should still call Neptune when skipCacheWrite=true', async () => {
|
|
1877
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
|
|
1878
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1879
|
-
statusCode: 200,
|
|
1880
|
-
body: JSON.stringify({
|
|
1881
|
-
identity: { identityId: 'resolved', traits: {} },
|
|
1882
|
-
}),
|
|
1883
|
-
});
|
|
1884
|
-
await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'test', traits: {} }, lambdaArn, { skipCacheWrite: true });
|
|
1885
|
-
expect(mockInvokeFunction).toHaveBeenCalled();
|
|
1886
|
-
});
|
|
1887
|
-
});
|
|
1888
|
-
});
|
|
1889
|
-
describe('resolveStaleViaNeptune helper', () => {
|
|
1890
|
-
describe('Neptune resolution success with changes → write-back', () => {
|
|
1891
|
-
it('should write to cache when Neptune returns different identity', async () => {
|
|
1892
|
-
const cachedResponse = {
|
|
1893
|
-
pk: `identity#${pixelId}#stale-changes`,
|
|
1894
|
-
pixelId,
|
|
1895
|
-
identityId: 'stale-changes',
|
|
1896
|
-
response: {
|
|
1897
|
-
identityId: 'stale-changes',
|
|
1898
|
-
traits: { emails: ['old@email.com'] },
|
|
1899
|
-
},
|
|
1900
|
-
updatedAt: new Date().toISOString(),
|
|
1901
|
-
};
|
|
1902
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1903
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1904
|
-
statusCode: 200,
|
|
1905
|
-
body: JSON.stringify({
|
|
1906
|
-
identity: {
|
|
1907
|
-
identityId: 'stale-changes',
|
|
1908
|
-
traits: { emails: ['old@email.com', 'new@email.com'], phones: ['555-new'] },
|
|
1909
|
-
},
|
|
1910
|
-
}),
|
|
1911
|
-
});
|
|
1912
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'stale-changes', traits: { emails: ['old@email.com', 'new@email.com'] } }, lambdaArn);
|
|
1913
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1914
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1915
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
1916
|
-
});
|
|
1917
|
-
it('should compute traitChanges between cached and Neptune identity', async () => {
|
|
1918
|
-
const cachedResponse = {
|
|
1919
|
-
pk: `identity#${pixelId}#trait-changes`,
|
|
1920
|
-
pixelId,
|
|
1921
|
-
identityId: 'trait-changes',
|
|
1922
|
-
response: {
|
|
1923
|
-
identityId: 'trait-changes',
|
|
1924
|
-
traits: {
|
|
1925
|
-
emails: ['old@email.com'],
|
|
1926
|
-
phones: ['555-old'],
|
|
1927
|
-
},
|
|
1928
|
-
},
|
|
1929
|
-
updatedAt: new Date().toISOString(),
|
|
1930
|
-
};
|
|
1931
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1932
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1933
|
-
statusCode: 200,
|
|
1934
|
-
body: JSON.stringify({
|
|
1935
|
-
identity: {
|
|
1936
|
-
identityId: 'trait-changes',
|
|
1937
|
-
traits: {
|
|
1938
|
-
emails: ['old@email.com', 'new@email.com'],
|
|
1939
|
-
phones: ['555-old', '555-new'],
|
|
1940
|
-
},
|
|
1941
|
-
},
|
|
1942
|
-
}),
|
|
1943
|
-
});
|
|
1944
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'trait-changes', traits: { emails: ['old@email.com', 'new@email.com'] } }, lambdaArn);
|
|
1945
|
-
expect(result.summary.traitChanges).toBeDefined();
|
|
1946
|
-
expect(result.summary.traitChanges?.emails).toBeDefined();
|
|
1947
|
-
});
|
|
1948
|
-
});
|
|
1949
|
-
describe('Neptune resolution success without changes → skip write', () => {
|
|
1950
|
-
it('should skip cache write when Neptune matches cached', async () => {
|
|
1951
|
-
const cachedResponse = {
|
|
1952
|
-
pk: `identity#${pixelId}#no-changes`,
|
|
1953
|
-
pixelId,
|
|
1954
|
-
identityId: 'no-changes',
|
|
1955
|
-
response: {
|
|
1956
|
-
identityId: 'no-changes',
|
|
1957
|
-
traits: { emails: ['same@email.com'] },
|
|
1958
|
-
},
|
|
1959
|
-
updatedAt: new Date().toISOString(),
|
|
1960
|
-
};
|
|
1961
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1962
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1963
|
-
statusCode: 200,
|
|
1964
|
-
body: JSON.stringify({
|
|
1965
|
-
identity: {
|
|
1966
|
-
identityId: 'no-changes',
|
|
1967
|
-
traits: { emails: ['same@email.com'] },
|
|
1968
|
-
},
|
|
1969
|
-
}),
|
|
1970
|
-
});
|
|
1971
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'no-changes', traits: { emails: ['same@email.com', 'thought-new@email.com'] } }, lambdaArn);
|
|
1972
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
1973
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
1974
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
1975
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
1976
|
-
});
|
|
1977
|
-
it('should still return Neptune identity even when skipping write', async () => {
|
|
1978
|
-
const cachedResponse = {
|
|
1979
|
-
pk: `identity#${pixelId}#return-identity`,
|
|
1980
|
-
pixelId,
|
|
1981
|
-
identityId: 'return-identity',
|
|
1982
|
-
response: {
|
|
1983
|
-
identityId: 'return-identity',
|
|
1984
|
-
traits: { emails: ['cached@email.com'] },
|
|
1985
|
-
},
|
|
1986
|
-
updatedAt: new Date().toISOString(),
|
|
1987
|
-
};
|
|
1988
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
1989
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
1990
|
-
statusCode: 200,
|
|
1991
|
-
body: JSON.stringify({
|
|
1992
|
-
identity: {
|
|
1993
|
-
identityId: 'return-identity',
|
|
1994
|
-
traits: { emails: ['cached@email.com'] },
|
|
1995
|
-
},
|
|
1996
|
-
}),
|
|
1997
|
-
});
|
|
1998
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'return-identity', traits: { emails: ['cached@email.com', 'extra@email.com'] } }, lambdaArn);
|
|
1999
|
-
expect(result.identity?.identityId).toBe('return-identity');
|
|
2000
|
-
});
|
|
2001
|
-
it('should strip address timestamps for comparison', async () => {
|
|
2002
|
-
const now = Date.now();
|
|
2003
|
-
const cachedResponse = {
|
|
2004
|
-
pk: `identity#${pixelId}#addr-timestamp`,
|
|
2005
|
-
pixelId,
|
|
2006
|
-
identityId: 'addr-timestamp',
|
|
2007
|
-
response: {
|
|
2008
|
-
identityId: 'addr-timestamp',
|
|
2009
|
-
traits: {
|
|
2010
|
-
emails: ['test@email.com'],
|
|
2011
|
-
addresses: [{ city: 'NYC', cra: now - 1000, upa: now - 500 }],
|
|
2012
|
-
},
|
|
2013
|
-
},
|
|
2014
|
-
updatedAt: new Date().toISOString(),
|
|
2015
|
-
};
|
|
2016
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
2017
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
2018
|
-
statusCode: 200,
|
|
2019
|
-
body: JSON.stringify({
|
|
2020
|
-
identity: {
|
|
2021
|
-
identityId: 'addr-timestamp',
|
|
2022
|
-
traits: {
|
|
2023
|
-
emails: ['test@email.com'],
|
|
2024
|
-
addresses: [{ city: 'NYC', cra: now, upa: now }],
|
|
2025
|
-
},
|
|
2026
|
-
},
|
|
2027
|
-
}),
|
|
2028
|
-
});
|
|
2029
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'addr-timestamp', traits: { emails: ['test@email.com', 'new@email.com'] } }, lambdaArn);
|
|
2030
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
2031
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
2032
|
-
});
|
|
2033
|
-
});
|
|
2034
|
-
describe('Neptune resolution failure', () => {
|
|
2035
|
-
it('should return NEPTUNE_RESOLUTION_FAILED when Neptune fails', async () => {
|
|
2036
|
-
const cachedResponse = {
|
|
2037
|
-
pk: `identity#${pixelId}#neptune-fail`,
|
|
2038
|
-
pixelId,
|
|
2039
|
-
identityId: 'neptune-fail',
|
|
2040
|
-
response: {
|
|
2041
|
-
identityId: 'neptune-fail',
|
|
2042
|
-
traits: { emails: ['cached@email.com'] },
|
|
2043
|
-
},
|
|
2044
|
-
updatedAt: new Date().toISOString(),
|
|
2045
|
-
};
|
|
2046
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
2047
|
-
mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda invocation failed'));
|
|
2048
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'neptune-fail', traits: { emails: ['cached@email.com', 'new@email.com'] } }, lambdaArn);
|
|
2049
|
-
expect(result.summary.didCallNeptune).toBe(true);
|
|
2050
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLUTION_FAILED);
|
|
2051
|
-
});
|
|
2052
|
-
it('should not write to cache when Neptune fails', async () => {
|
|
2053
|
-
const cachedResponse = {
|
|
2054
|
-
pk: `identity#${pixelId}#no-write-fail`,
|
|
2055
|
-
pixelId,
|
|
2056
|
-
identityId: 'no-write-fail',
|
|
2057
|
-
response: {
|
|
2058
|
-
identityId: 'no-write-fail',
|
|
2059
|
-
traits: { emails: ['test@email.com'] },
|
|
2060
|
-
},
|
|
2061
|
-
updatedAt: new Date().toISOString(),
|
|
2062
|
-
};
|
|
2063
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
2064
|
-
mockInvokeFunction.mockRejectedValueOnce(new Error('Network error'));
|
|
2065
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'no-write-fail', traits: { emails: ['test@email.com', 'extra@email.com'] } }, lambdaArn);
|
|
2066
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
2067
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
2068
|
-
});
|
|
2069
|
-
it('should still return merged identity when Neptune fails', async () => {
|
|
2070
|
-
const cachedResponse = {
|
|
2071
|
-
pk: `identity#${pixelId}#merged-on-fail`,
|
|
2072
|
-
pixelId,
|
|
2073
|
-
identityId: 'merged-on-fail',
|
|
2074
|
-
response: {
|
|
2075
|
-
identityId: 'merged-on-fail',
|
|
2076
|
-
traits: {
|
|
2077
|
-
emails: ['cached@email.com'],
|
|
2078
|
-
phones: ['555-cached'],
|
|
2079
|
-
},
|
|
2080
|
-
},
|
|
2081
|
-
updatedAt: new Date().toISOString(),
|
|
2082
|
-
};
|
|
2083
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
2084
|
-
mockInvokeFunction.mockRejectedValueOnce(new Error('Timeout'));
|
|
2085
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'merged-on-fail', traits: { emails: ['cached@email.com', 'new@email.com'] } }, lambdaArn);
|
|
2086
|
-
expect(result.identity).toBeDefined();
|
|
2087
|
-
});
|
|
2088
|
-
});
|
|
2089
|
-
describe('skipCacheWrite=true behavior', () => {
|
|
2090
|
-
it('should NOT write to cache when skipCacheWrite=true even with changes', async () => {
|
|
2091
|
-
const cachedResponse = {
|
|
2092
|
-
pk: `identity#${pixelId}#skip-stale`,
|
|
2093
|
-
pixelId,
|
|
2094
|
-
identityId: 'skip-stale',
|
|
2095
|
-
response: {
|
|
2096
|
-
identityId: 'skip-stale',
|
|
2097
|
-
traits: { emails: ['old@email.com'] },
|
|
2098
|
-
},
|
|
2099
|
-
updatedAt: new Date().toISOString(),
|
|
2100
|
-
};
|
|
2101
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
2102
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
2103
|
-
statusCode: 200,
|
|
2104
|
-
body: JSON.stringify({
|
|
2105
|
-
identity: {
|
|
2106
|
-
identityId: 'skip-stale',
|
|
2107
|
-
traits: { emails: ['old@email.com', 'new@email.com'], phones: ['555-new'] },
|
|
2108
|
-
},
|
|
2109
|
-
}),
|
|
2110
|
-
});
|
|
2111
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'skip-stale', traits: { emails: ['old@email.com', 'new@email.com'] } }, lambdaArn, { skipCacheWrite: true });
|
|
2112
|
-
expect(result.summary.didWriteCache).toBe(false);
|
|
2113
|
-
expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
|
|
2114
|
-
expect(result.summary.outcome).toBe(types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_RESOLVED);
|
|
2115
|
-
});
|
|
2116
|
-
it('should still call Neptune when skipCacheWrite=true', async () => {
|
|
2117
|
-
const cachedResponse = {
|
|
2118
|
-
pk: `identity#${pixelId}#call-neptune-skip`,
|
|
2119
|
-
pixelId,
|
|
2120
|
-
identityId: 'call-neptune-skip',
|
|
2121
|
-
response: {
|
|
2122
|
-
identityId: 'call-neptune-skip',
|
|
2123
|
-
traits: { emails: ['test@email.com'] },
|
|
2124
|
-
},
|
|
2125
|
-
updatedAt: new Date().toISOString(),
|
|
2126
|
-
};
|
|
2127
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
2128
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
2129
|
-
statusCode: 200,
|
|
2130
|
-
body: JSON.stringify({
|
|
2131
|
-
identity: {
|
|
2132
|
-
identityId: 'call-neptune-skip',
|
|
2133
|
-
traits: { emails: ['test@email.com', 'added@email.com'] },
|
|
2134
|
-
},
|
|
2135
|
-
}),
|
|
2136
|
-
});
|
|
2137
|
-
await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'call-neptune-skip', traits: { emails: ['test@email.com', 'added@email.com'] } }, lambdaArn, { skipCacheWrite: true });
|
|
2138
|
-
expect(mockInvokeFunction).toHaveBeenCalled();
|
|
2139
|
-
});
|
|
2140
|
-
it('should return Neptune identity when skipCacheWrite=true', async () => {
|
|
2141
|
-
const cachedResponse = {
|
|
2142
|
-
pk: `identity#${pixelId}#return-skip`,
|
|
2143
|
-
pixelId,
|
|
2144
|
-
identityId: 'return-skip',
|
|
2145
|
-
response: {
|
|
2146
|
-
identityId: 'return-skip',
|
|
2147
|
-
traits: { emails: ['cached@email.com'] },
|
|
2148
|
-
},
|
|
2149
|
-
updatedAt: new Date().toISOString(),
|
|
2150
|
-
};
|
|
2151
|
-
mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
|
|
2152
|
-
mockInvokeFunction.mockResolvedValueOnce({
|
|
2153
|
-
statusCode: 200,
|
|
2154
|
-
body: JSON.stringify({
|
|
2155
|
-
identity: {
|
|
2156
|
-
identityId: 'return-skip',
|
|
2157
|
-
traits: {
|
|
2158
|
-
emails: ['cached@email.com', 'neptune@email.com'],
|
|
2159
|
-
phones: ['555-neptune'],
|
|
2160
|
-
},
|
|
2161
|
-
},
|
|
2162
|
-
}),
|
|
2163
|
-
});
|
|
2164
|
-
const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, { identityId: 'return-skip', traits: { emails: ['cached@email.com', 'neptune@email.com'] } }, lambdaArn, { skipCacheWrite: true });
|
|
2165
|
-
expect(result.identity?.traits?.phones).toContain('555-neptune');
|
|
2166
|
-
});
|
|
2167
|
-
});
|
|
2168
|
-
});
|
|
2169
|
-
});
|
|
2170
|
-
//# sourceMappingURL=identity-gateway-service.spec.js.map
|