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