@squiz/db-lib 1.71.2 → 1.71.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/AbstractRepository.d.ts +2 -0
  3. package/lib/AbstractRepository.d.ts.map +1 -0
  4. package/lib/AbstractRepository.integration.spec.d.ts +1 -0
  5. package/lib/AbstractRepository.integration.spec.d.ts.map +1 -0
  6. package/lib/AbstractRepository.integration.spec.js +118 -0
  7. package/lib/AbstractRepository.integration.spec.js.map +1 -0
  8. package/lib/AbstractRepository.js +187 -0
  9. package/lib/AbstractRepository.js.map +1 -0
  10. package/lib/ConnectionManager.d.ts +1 -0
  11. package/lib/ConnectionManager.d.ts.map +1 -0
  12. package/lib/ConnectionManager.js +58 -0
  13. package/lib/ConnectionManager.js.map +1 -0
  14. package/lib/Migrator.d.ts +1 -0
  15. package/lib/Migrator.d.ts.map +1 -0
  16. package/lib/Migrator.js +160 -0
  17. package/lib/Migrator.js.map +1 -0
  18. package/lib/PostgresErrorCodes.d.ts +1 -0
  19. package/lib/PostgresErrorCodes.d.ts.map +1 -0
  20. package/lib/PostgresErrorCodes.js +274 -0
  21. package/lib/PostgresErrorCodes.js.map +1 -0
  22. package/lib/Repositories.d.ts +1 -0
  23. package/lib/Repositories.d.ts.map +1 -0
  24. package/lib/Repositories.js +3 -0
  25. package/lib/Repositories.js.map +1 -0
  26. package/lib/dynamodb/AbstractDynamoDbRepository.d.ts +1 -0
  27. package/lib/dynamodb/AbstractDynamoDbRepository.d.ts.map +1 -0
  28. package/lib/dynamodb/AbstractDynamoDbRepository.js +367 -0
  29. package/lib/dynamodb/AbstractDynamoDbRepository.js.map +1 -0
  30. package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts +1 -0
  31. package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts.map +1 -0
  32. package/lib/dynamodb/AbstractDynamoDbRepository.spec.js +698 -0
  33. package/lib/dynamodb/AbstractDynamoDbRepository.spec.js.map +1 -0
  34. package/lib/dynamodb/DynamoDbManager.d.ts +1 -0
  35. package/lib/dynamodb/DynamoDbManager.d.ts.map +1 -0
  36. package/lib/dynamodb/DynamoDbManager.js +66 -0
  37. package/lib/dynamodb/DynamoDbManager.js.map +1 -0
  38. package/lib/dynamodb/getDynamoDbOptions.d.ts +1 -0
  39. package/lib/dynamodb/getDynamoDbOptions.d.ts.map +1 -0
  40. package/lib/dynamodb/getDynamoDbOptions.js +15 -0
  41. package/lib/dynamodb/getDynamoDbOptions.js.map +1 -0
  42. package/lib/error/DuplicateItemError.d.ts +1 -0
  43. package/lib/error/DuplicateItemError.d.ts.map +1 -0
  44. package/lib/error/DuplicateItemError.js +12 -0
  45. package/lib/error/DuplicateItemError.js.map +1 -0
  46. package/lib/error/InvalidDataFormatError.d.ts +1 -0
  47. package/lib/error/InvalidDataFormatError.d.ts.map +1 -0
  48. package/lib/error/InvalidDataFormatError.js +12 -0
  49. package/lib/error/InvalidDataFormatError.js.map +1 -0
  50. package/lib/error/InvalidDbSchemaError.d.ts +1 -0
  51. package/lib/error/InvalidDbSchemaError.d.ts.map +1 -0
  52. package/lib/error/InvalidDbSchemaError.js +12 -0
  53. package/lib/error/InvalidDbSchemaError.js.map +1 -0
  54. package/lib/error/MissingKeyValuesError.d.ts +1 -0
  55. package/lib/error/MissingKeyValuesError.d.ts.map +1 -0
  56. package/lib/error/MissingKeyValuesError.js +12 -0
  57. package/lib/error/MissingKeyValuesError.js.map +1 -0
  58. package/lib/error/TransactionError.d.ts +1 -0
  59. package/lib/error/TransactionError.d.ts.map +1 -0
  60. package/lib/error/TransactionError.js +12 -0
  61. package/lib/error/TransactionError.js.map +1 -0
  62. package/lib/getConnectionInfo.d.ts +1 -0
  63. package/lib/getConnectionInfo.d.ts.map +1 -0
  64. package/lib/getConnectionInfo.js +30 -0
  65. package/lib/getConnectionInfo.js.map +1 -0
  66. package/lib/index.d.ts +1 -0
  67. package/lib/index.d.ts.map +1 -0
  68. package/lib/index.js +33 -70416
  69. package/lib/index.js.map +1 -7
  70. package/package.json +5 -5
  71. package/src/AbstractRepository.ts +26 -20
  72. package/src/dynamodb/AbstractDynamoDbRepository.ts +1 -1
  73. package/src/dynamodb/getDynamoDbOptions.ts +1 -1
  74. package/tsconfig.json +5 -2
  75. package/tsconfig.tsbuildinfo +1 -1
  76. package/build.js +0 -31
@@ -0,0 +1,698 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const AbstractDynamoDbRepository_1 = require("./AbstractDynamoDbRepository");
7
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
8
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
9
+ const DynamoDbManager_1 = require("./DynamoDbManager");
10
+ const __1 = require("..");
11
+ const aws_sdk_client_mock_1 = require("aws-sdk-client-mock");
12
+ require("aws-sdk-client-mock-jest");
13
+ const client_dynamodb_2 = require("@aws-sdk/client-dynamodb");
14
+ const crypto_1 = __importDefault(require("crypto"));
15
+ const InvalidDataFormatError_1 = require("../error/InvalidDataFormatError");
16
+ const ddbClientMock = (0, aws_sdk_client_mock_1.mockClient)(lib_dynamodb_1.DynamoDBDocumentClient);
17
+ const ddbDoc = lib_dynamodb_1.DynamoDBDocument.from(new client_dynamodb_2.DynamoDB({}));
18
+ class TestItem {
19
+ constructor(data = {}) {
20
+ var _a, _b, _c, _d, _e;
21
+ this.name = (_a = data.name) !== null && _a !== void 0 ? _a : 'default name';
22
+ this.age = (_b = data.age) !== null && _b !== void 0 ? _b : 0;
23
+ this.country = (_c = data.country) !== null && _c !== void 0 ? _c : 'default country';
24
+ this.data = (_d = data.data) !== null && _d !== void 0 ? _d : {};
25
+ this.data2 = (_e = data.data2) !== null && _e !== void 0 ? _e : {};
26
+ if (typeof this.name !== 'string') {
27
+ throw Error('Invalid "name"');
28
+ }
29
+ if (typeof this.age !== 'number') {
30
+ throw Error('Invalid "age"');
31
+ }
32
+ if (typeof this.country !== 'string') {
33
+ throw Error('Invalid "country"');
34
+ }
35
+ if (typeof this.data !== 'object' || Array.isArray(this.data)) {
36
+ throw Error('Invalid "data"');
37
+ }
38
+ }
39
+ }
40
+ const TABLE_NAME = 'test-table';
41
+ const TEST_ITEM_ENTITY_NAME = 'test-item-entity';
42
+ const TEST_ITEM_ENTITY_DEFINITION = {
43
+ keys: {
44
+ pk: {
45
+ format: 'test_item#{name}',
46
+ attributeName: 'pk',
47
+ },
48
+ sk: {
49
+ format: '#meta',
50
+ attributeName: 'sk',
51
+ },
52
+ },
53
+ indexes: {
54
+ 'gsi1_pk-gsi1_sk-index': {
55
+ pk: {
56
+ format: 'country#{country}',
57
+ attributeName: 'gsi1_pk',
58
+ },
59
+ sk: {
60
+ format: 'age#{age}',
61
+ attributeName: 'gsi1_sk',
62
+ },
63
+ },
64
+ },
65
+ // field to be stored as JSON string
66
+ fieldsAsJsonString: ['data2'],
67
+ };
68
+ class TestItemRepository extends AbstractDynamoDbRepository_1.AbstractDynamoDbRepository {
69
+ constructor(tableName, dbManager) {
70
+ super(tableName, dbManager, TEST_ITEM_ENTITY_NAME, TEST_ITEM_ENTITY_DEFINITION, TestItem);
71
+ }
72
+ }
73
+ const ddbManager = new DynamoDbManager_1.DynamoDbManager(ddbDoc, (dbManager) => {
74
+ return {
75
+ testItem: new TestItemRepository(TABLE_NAME, dbManager),
76
+ };
77
+ });
78
+ // Test start ////////////////////////////////////
79
+ describe('AbstractRepository', () => {
80
+ let repository;
81
+ beforeEach(() => {
82
+ ddbClientMock.reset();
83
+ repository = new TestItemRepository(TABLE_NAME, ddbManager);
84
+ });
85
+ describe('createItem()', () => {
86
+ it('should create and return the item object if valid input', async () => {
87
+ ddbClientMock.on(lib_dynamodb_1.PutCommand).resolves({
88
+ $metadata: {
89
+ httpStatusCode: 200,
90
+ },
91
+ });
92
+ const input = {
93
+ TableName: TABLE_NAME,
94
+ Item: {
95
+ pk: 'test_item#foo',
96
+ sk: '#meta',
97
+ gsi1_pk: 'country#au',
98
+ gsi1_sk: 'age#99',
99
+ name: 'foo',
100
+ age: 99,
101
+ country: 'au',
102
+ data: {},
103
+ // "data2" property is defined to be stored as JSON string
104
+ data2: '{"foo":"bar","num":123}',
105
+ },
106
+ ConditionExpression: `attribute_not_exists(pk)`,
107
+ };
108
+ const item = {
109
+ name: 'foo',
110
+ age: 99,
111
+ country: 'au',
112
+ data: {},
113
+ data2: {
114
+ foo: 'bar',
115
+ num: 123,
116
+ },
117
+ };
118
+ const result = await repository.createItem(item);
119
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.PutCommand, input);
120
+ expect(result).toEqual(new TestItem({
121
+ name: 'foo',
122
+ age: 99,
123
+ country: 'au',
124
+ data: {},
125
+ data2: {
126
+ foo: 'bar',
127
+ num: 123,
128
+ },
129
+ }));
130
+ });
131
+ it('should throw error if invalid input', async () => {
132
+ const item = {
133
+ name: 'foo',
134
+ age: 99,
135
+ country: 'au',
136
+ data: [], // should be non-array object
137
+ };
138
+ await expect(repository.createItem(item)).rejects.toEqual(new Error('Invalid "data"'));
139
+ });
140
+ it('should throw error if excess column in input', async () => {
141
+ const item = {
142
+ name: 'foo',
143
+ age: 99,
144
+ country: 'au',
145
+ data: {},
146
+ extraColumn: '123',
147
+ extraColumn2: '',
148
+ };
149
+ await expect(repository.createItem(item)).rejects.toEqual(new __1.InvalidDbSchemaError('Excess properties in entity test-item-entity: extraColumn, extraColumn2'));
150
+ });
151
+ it('should throw error if input does not includes key field(s)', async () => {
152
+ const partialItem = {
153
+ age: 99,
154
+ country: 'au',
155
+ data: {},
156
+ };
157
+ await expect(repository.createItem(partialItem)).rejects.toEqual(new __1.MissingKeyValuesError('Key field "name" must be specified in the input item in entity test-item-entity'));
158
+ });
159
+ });
160
+ describe('updateItem()', () => {
161
+ it('should update and return the item object if valid input', async () => {
162
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
163
+ $metadata: {
164
+ httpStatusCode: 200,
165
+ },
166
+ Item: {
167
+ name: 'foo',
168
+ age: 99,
169
+ country: 'au',
170
+ data: {},
171
+ },
172
+ });
173
+ ddbClientMock.on(lib_dynamodb_1.UpdateCommand).resolves({
174
+ $metadata: {
175
+ httpStatusCode: 200,
176
+ },
177
+ Attributes: {
178
+ name: 'foo',
179
+ age: 99,
180
+ country: 'au-updated',
181
+ data: {},
182
+ },
183
+ });
184
+ const input = {
185
+ TableName: TABLE_NAME,
186
+ Key: { pk: 'test_item#foo', sk: '#meta' },
187
+ UpdateExpression: 'SET #country = :country',
188
+ ExpressionAttributeNames: {
189
+ '#country': 'country',
190
+ },
191
+ ExpressionAttributeValues: {
192
+ ':country': 'au-updated',
193
+ },
194
+ ConditionExpression: `attribute_exists(pk)`,
195
+ };
196
+ const partialItemWithKeyFields = {
197
+ name: 'foo',
198
+ };
199
+ const updateItem = {
200
+ country: 'au-updated',
201
+ };
202
+ const result = await repository.updateItem(partialItemWithKeyFields, updateItem);
203
+ expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.UpdateCommand, input);
204
+ expect(result).toEqual(new TestItem({
205
+ name: 'foo',
206
+ age: 99,
207
+ country: 'au-updated',
208
+ data: {},
209
+ }));
210
+ });
211
+ it('should undefined if item does does not exist', async () => {
212
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
213
+ $metadata: {
214
+ httpStatusCode: 200,
215
+ },
216
+ });
217
+ const partialItemWithKeyFields = {
218
+ name: 'foo',
219
+ };
220
+ const updateItem = {
221
+ country: 'au-updated',
222
+ };
223
+ const result = await repository.updateItem(partialItemWithKeyFields, updateItem);
224
+ expect(result).toEqual(undefined);
225
+ });
226
+ it('should return undefined if update cmd conditional check fails', async () => {
227
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
228
+ $metadata: {
229
+ httpStatusCode: 200,
230
+ },
231
+ Item: {
232
+ name: 'foo',
233
+ age: 99,
234
+ country: 'au',
235
+ data: {},
236
+ },
237
+ });
238
+ ddbClientMock.on(lib_dynamodb_1.UpdateCommand).rejects(new client_dynamodb_1.ConditionalCheckFailedException({
239
+ $metadata: {},
240
+ message: 'not found',
241
+ }));
242
+ const partialItemWithKeyFields = {
243
+ name: 'foo',
244
+ };
245
+ const updateItem = {
246
+ country: 'au-updated',
247
+ };
248
+ const result = await repository.updateItem(partialItemWithKeyFields, updateItem);
249
+ expect(result).toEqual(undefined);
250
+ });
251
+ it('should throw error update data has invalid data', async () => {
252
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
253
+ $metadata: {
254
+ httpStatusCode: 200,
255
+ },
256
+ Item: {
257
+ name: 'foo',
258
+ age: 99,
259
+ country: 'au',
260
+ data: {},
261
+ },
262
+ });
263
+ const partialItemWithKeyFields = {
264
+ name: 'foo',
265
+ };
266
+ const updateItem = {
267
+ country: 61, // should be "string" type
268
+ };
269
+ await expect(repository.updateItem(partialItemWithKeyFields, updateItem)).rejects.toEqual(new Error('Invalid "country"'));
270
+ });
271
+ it('should throw error if excess column in input', async () => {
272
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
273
+ $metadata: {
274
+ httpStatusCode: 200,
275
+ },
276
+ Item: {
277
+ name: 'foo',
278
+ age: 99,
279
+ country: 'au',
280
+ data: {},
281
+ },
282
+ });
283
+ const partialItemWithKeyFields = {
284
+ name: 'foo',
285
+ };
286
+ const updateItem = {
287
+ country: 'au-updated',
288
+ extra: '',
289
+ };
290
+ await expect(repository.updateItem(partialItemWithKeyFields, updateItem)).rejects.toEqual(new __1.InvalidDbSchemaError('Excess properties in entity test-item-entity: extra'));
291
+ });
292
+ it('should throw error if input does not includes key field(s)', async () => {
293
+ const partialItemWithKeyFields = {
294
+ age: 99,
295
+ };
296
+ const updateItem = {
297
+ country: 'au-updated', // should be "string" type
298
+ };
299
+ await expect(repository.updateItem(partialItemWithKeyFields, updateItem)).rejects.toEqual(new __1.MissingKeyValuesError('Key field "name" must be specified in the input item in entity test-item-entity'));
300
+ });
301
+ });
302
+ describe('getItem()', () => {
303
+ it('should return the item object if found', async () => {
304
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
305
+ $metadata: {
306
+ httpStatusCode: 200,
307
+ },
308
+ Item: {
309
+ name: 'foo',
310
+ age: 99,
311
+ country: 'au',
312
+ data: {},
313
+ data2: '{"foo":"bar","num":123}',
314
+ },
315
+ });
316
+ const input = {
317
+ TableName: TABLE_NAME,
318
+ Key: { pk: 'test_item#foo', sk: '#meta' },
319
+ };
320
+ const partialItem = { name: 'foo' };
321
+ const result = await repository.getItem(partialItem);
322
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.GetCommand, input);
323
+ expect(result).toEqual(new TestItem({
324
+ name: 'foo',
325
+ age: 99,
326
+ country: 'au',
327
+ data: {},
328
+ data2: {
329
+ foo: 'bar',
330
+ num: 123,
331
+ },
332
+ }));
333
+ });
334
+ it('should return undefined if item not found', async () => {
335
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
336
+ $metadata: {
337
+ httpStatusCode: 200,
338
+ },
339
+ });
340
+ const input = {
341
+ TableName: TABLE_NAME,
342
+ Key: { pk: 'test_item#foo', sk: '#meta' },
343
+ };
344
+ const partialItem = { name: 'foo' };
345
+ const result = await repository.getItem(partialItem);
346
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.GetCommand, input);
347
+ expect(result).toEqual(undefined);
348
+ });
349
+ it('should throw error if item schema validation fails', async () => {
350
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
351
+ $metadata: {
352
+ httpStatusCode: 200,
353
+ },
354
+ Item: {
355
+ name: 'foo',
356
+ age: '99',
357
+ country: 'au',
358
+ data: {},
359
+ },
360
+ });
361
+ const partialItem = { name: 'foo' };
362
+ await expect(repository.getItem(partialItem)).rejects.toEqual(new Error('Invalid "age"'));
363
+ });
364
+ it('should throw error if input does not includes key field(s)', async () => {
365
+ const partialItem = { age: 99 };
366
+ await expect(repository.getItem(partialItem)).rejects.toEqual(new __1.MissingKeyValuesError('Key field "name" must be specified in the input item in entity test-item-entity'));
367
+ });
368
+ it('should throw error if JSON string field has non-string data', async () => {
369
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
370
+ $metadata: {
371
+ httpStatusCode: 200,
372
+ },
373
+ Item: {
374
+ name: 'foo',
375
+ age: 99,
376
+ country: 'au',
377
+ data: {},
378
+ data2: {
379
+ foo: 'bar',
380
+ num: 123,
381
+ },
382
+ },
383
+ });
384
+ const partialItem = { name: 'foo' };
385
+ await expect(repository.getItem(partialItem)).rejects.toEqual(new InvalidDataFormatError_1.InvalidDataFormatError(`Field 'data2' defined as JSON String has a non-string data`));
386
+ });
387
+ });
388
+ describe('queryItems()', () => {
389
+ it('should return the items if found', async () => {
390
+ ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
391
+ $metadata: {
392
+ httpStatusCode: 200,
393
+ },
394
+ Items: [
395
+ {
396
+ name: 'foo',
397
+ age: 99,
398
+ country: 'au',
399
+ data: {},
400
+ },
401
+ ],
402
+ });
403
+ const input = {
404
+ TableName: TABLE_NAME,
405
+ KeyConditionExpression: '#pkName = :pkValue',
406
+ ExpressionAttributeNames: {
407
+ '#pkName': 'pk',
408
+ },
409
+ ExpressionAttributeValues: {
410
+ ':pkValue': 'test_item#foo',
411
+ },
412
+ };
413
+ const partialItem = { name: 'foo' };
414
+ const result = await repository.queryItems(partialItem);
415
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
416
+ expect(result).toEqual([
417
+ new TestItem({
418
+ name: 'foo',
419
+ age: 99,
420
+ country: 'au',
421
+ data: {},
422
+ }),
423
+ ]);
424
+ });
425
+ it('should return empty array if no items found', async () => {
426
+ ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
427
+ $metadata: {
428
+ httpStatusCode: 200,
429
+ },
430
+ });
431
+ const input = {
432
+ TableName: TABLE_NAME,
433
+ KeyConditionExpression: '#pkName = :pkValue',
434
+ ExpressionAttributeNames: {
435
+ '#pkName': 'pk',
436
+ },
437
+ ExpressionAttributeValues: {
438
+ ':pkValue': 'test_item#foo',
439
+ },
440
+ };
441
+ const partialItem = { name: 'foo' };
442
+ const result = await repository.queryItems(partialItem);
443
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
444
+ expect(result).toEqual([]);
445
+ });
446
+ it('should use sort key in query if "useSortKey" param is true', async () => {
447
+ ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
448
+ $metadata: {
449
+ httpStatusCode: 200,
450
+ },
451
+ });
452
+ const input = {
453
+ TableName: TABLE_NAME,
454
+ KeyConditionExpression: '#pkName = :pkValue AND #skName = :skValue',
455
+ ExpressionAttributeNames: {
456
+ '#pkName': 'pk',
457
+ '#skName': 'sk',
458
+ },
459
+ ExpressionAttributeValues: {
460
+ ':pkValue': 'test_item#foo',
461
+ ':skValue': '#meta',
462
+ },
463
+ };
464
+ const partialItem = { name: 'foo' };
465
+ const useSortKey = true;
466
+ const _result = await repository.queryItems(partialItem, useSortKey);
467
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
468
+ });
469
+ it('should return the items if found when using gsi index', async () => {
470
+ ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
471
+ $metadata: {
472
+ httpStatusCode: 200,
473
+ },
474
+ Items: [
475
+ {
476
+ name: 'foo',
477
+ age: 99,
478
+ country: 'au',
479
+ data: {},
480
+ },
481
+ {
482
+ name: 'fox',
483
+ age: 11,
484
+ country: 'au',
485
+ data: {},
486
+ },
487
+ ],
488
+ });
489
+ const index = 'gsi1_pk-gsi1_sk-index';
490
+ const input = {
491
+ TableName: TABLE_NAME,
492
+ IndexName: index,
493
+ KeyConditionExpression: '#pkName = :pkValue',
494
+ ExpressionAttributeNames: {
495
+ '#pkName': 'gsi1_pk',
496
+ },
497
+ ExpressionAttributeValues: {
498
+ ':pkValue': 'country#au',
499
+ },
500
+ };
501
+ const partialItem = { country: 'au' };
502
+ const useSortKey = false;
503
+ const result = await repository.queryItems(partialItem, useSortKey, index);
504
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
505
+ expect(result).toEqual([
506
+ new TestItem({
507
+ name: 'foo',
508
+ age: 99,
509
+ country: 'au',
510
+ data: {},
511
+ }),
512
+ new TestItem({
513
+ name: 'fox',
514
+ age: 11,
515
+ country: 'au',
516
+ data: {},
517
+ }),
518
+ ]);
519
+ });
520
+ it('should use sort key in query if "useSortKey" param is true when using gsi index', async () => {
521
+ ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
522
+ $metadata: {
523
+ httpStatusCode: 200,
524
+ },
525
+ });
526
+ const index = 'gsi1_pk-gsi1_sk-index';
527
+ const input = {
528
+ TableName: TABLE_NAME,
529
+ IndexName: index,
530
+ KeyConditionExpression: '#pkName = :pkValue AND #skName = :skValue',
531
+ ExpressionAttributeNames: {
532
+ '#pkName': 'gsi1_pk',
533
+ '#skName': 'gsi1_sk',
534
+ },
535
+ ExpressionAttributeValues: {
536
+ ':pkValue': 'country#au',
537
+ ':skValue': 'age#99',
538
+ },
539
+ };
540
+ const partialItem = { age: 99, country: 'au' };
541
+ const useSortKey = true;
542
+ const _result = await repository.queryItems(partialItem, useSortKey, index);
543
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
544
+ });
545
+ it('should throw error for invalid index query', async () => {
546
+ const index = 'undefined-index';
547
+ const partialItem = { age: 99, country: 'au' };
548
+ await expect(repository.queryItems(partialItem, false, index)).rejects.toEqual(new __1.MissingKeyValuesError(`Table index '${index}' not defined on entity test-item-entity`));
549
+ });
550
+ it('should throw error for missing key fields', async () => {
551
+ const partialItem = { age: 99, country: 'au' };
552
+ await expect(repository.queryItems(partialItem)).rejects.toEqual(new __1.MissingKeyValuesError(`Key field "name" must be specified in the input item in entity test-item-entity`));
553
+ });
554
+ it('should throw error for missing key fields when using index', async () => {
555
+ const partialItem = { name: 'foo' };
556
+ const useSortKey = false;
557
+ const index = 'gsi1_pk-gsi1_sk-index';
558
+ await expect(repository.queryItems(partialItem, useSortKey, index)).rejects.toEqual(new __1.MissingKeyValuesError(`Key field "country" must be specified in the input item in entity test-item-entity`));
559
+ });
560
+ it('should throw error for missing key fields with "useSortKey" param true when using index', async () => {
561
+ const partialItem = { country: 'au' };
562
+ const useSortKey = true;
563
+ const index = 'gsi1_pk-gsi1_sk-index';
564
+ await expect(repository.queryItems(partialItem, useSortKey, index)).rejects.toEqual(new __1.MissingKeyValuesError(`Key field "age" must be specified in the input item in entity test-item-entity`));
565
+ });
566
+ });
567
+ describe('deleteItem()', () => {
568
+ it('should return 1 when item is found and deleted', async () => {
569
+ ddbClientMock.on(lib_dynamodb_1.DeleteCommand).resolves({
570
+ $metadata: {
571
+ httpStatusCode: 200,
572
+ },
573
+ });
574
+ const input = {
575
+ TableName: TABLE_NAME,
576
+ Key: { pk: 'test_item#foo', sk: '#meta' },
577
+ };
578
+ const partialItem = { name: 'foo' };
579
+ const result = await repository.deleteItem(partialItem);
580
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.DeleteCommand, input);
581
+ expect(result).toBe(1);
582
+ });
583
+ it('should return 0 when item is not found', async () => {
584
+ ddbClientMock.on(lib_dynamodb_1.DeleteCommand).rejects(new client_dynamodb_1.ConditionalCheckFailedException({
585
+ $metadata: {},
586
+ message: 'not found',
587
+ }));
588
+ const input = {
589
+ TableName: TABLE_NAME,
590
+ Key: { pk: 'test_item#foo', sk: '#meta' },
591
+ };
592
+ const partialItem = { name: 'foo' };
593
+ const result = await repository.deleteItem(partialItem);
594
+ expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.DeleteCommand, input);
595
+ expect(result).toBe(0);
596
+ });
597
+ it('should throw error if request fails', async () => {
598
+ const partialItem = { name: 'foo' };
599
+ ddbClientMock.on(lib_dynamodb_1.DeleteCommand).rejects('some other error');
600
+ await expect(repository.deleteItem(partialItem)).rejects.toEqual(new Error('some other error'));
601
+ });
602
+ it('should throw error if input does not includes key field(s)', async () => {
603
+ const partialItem = { age: 99 };
604
+ await expect(repository.deleteItem(partialItem)).rejects.toEqual(new __1.MissingKeyValuesError('Key field "name" must be specified in the input item in entity test-item-entity'));
605
+ });
606
+ });
607
+ describe('Writer fns with transaction - DynamoDbManager', () => {
608
+ it('should execute the multiple transaction write request in a single request', async () => {
609
+ const spy = jest.spyOn(crypto_1.default, 'randomUUID');
610
+ spy.mockImplementation(() => 'some-token');
611
+ ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
612
+ $metadata: {
613
+ httpStatusCode: 200,
614
+ },
615
+ Item: {
616
+ name: 'foo2',
617
+ age: 99,
618
+ country: 'au',
619
+ data: {},
620
+ },
621
+ });
622
+ ddbClientMock.on(lib_dynamodb_1.TransactWriteCommand).resolves({
623
+ $metadata: {
624
+ httpStatusCode: 200,
625
+ },
626
+ });
627
+ const result = await ddbManager.executeInTransaction(async (transaction) => {
628
+ await repository.deleteItem({ name: 'foo' }, transaction);
629
+ await repository.updateItem({
630
+ name: 'foo2',
631
+ }, {
632
+ age: 55,
633
+ }, transaction);
634
+ return await repository.createItem({
635
+ name: 'foo3',
636
+ age: 11,
637
+ country: 'au',
638
+ data: {},
639
+ }, transaction);
640
+ });
641
+ const input = {
642
+ ClientRequestToken: 'some-token',
643
+ TransactItems: [
644
+ {
645
+ Delete: {
646
+ Key: {
647
+ pk: 'test_item#foo',
648
+ sk: '#meta',
649
+ },
650
+ TableName: 'test-table',
651
+ },
652
+ },
653
+ {
654
+ Update: {
655
+ ConditionExpression: 'attribute_exists(pk)',
656
+ ExpressionAttributeNames: {
657
+ '#age': 'age',
658
+ },
659
+ ExpressionAttributeValues: {
660
+ ':age': 55,
661
+ },
662
+ Key: {
663
+ pk: 'test_item#foo2',
664
+ sk: '#meta',
665
+ },
666
+ TableName: 'test-table',
667
+ UpdateExpression: 'SET #age = :age',
668
+ },
669
+ },
670
+ {
671
+ Put: {
672
+ ConditionExpression: 'attribute_not_exists(pk)',
673
+ Item: {
674
+ age: 11,
675
+ country: 'au',
676
+ data: {},
677
+ gsi1_pk: 'country#au',
678
+ gsi1_sk: 'age#11',
679
+ name: 'foo3',
680
+ pk: 'test_item#foo3',
681
+ sk: '#meta',
682
+ },
683
+ TableName: 'test-table',
684
+ },
685
+ },
686
+ ],
687
+ };
688
+ expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.TransactWriteCommand, input);
689
+ expect(result).toEqual({
690
+ name: 'foo3',
691
+ age: 11,
692
+ country: 'au',
693
+ data: {},
694
+ });
695
+ });
696
+ });
697
+ });
698
+ //# sourceMappingURL=AbstractDynamoDbRepository.spec.js.map