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