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