@squiz/db-lib 1.71.3 → 1.73.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +17 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.d.ts +62 -9
- package/lib/dynamodb/AbstractDynamoDbRepository.d.ts.map +1 -1
- package/lib/dynamodb/AbstractDynamoDbRepository.js +162 -19
- package/lib/dynamodb/AbstractDynamoDbRepository.js.map +1 -1
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts.map +1 -1
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.js +581 -31
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.js.map +1 -1
- package/package.json +2 -2
- package/src/dynamodb/AbstractDynamoDbRepository.spec.ts +642 -37
- package/src/dynamodb/AbstractDynamoDbRepository.ts +206 -31
- package/tsconfig.tsbuildinfo +1 -1
@@ -193,13 +193,11 @@ describe('AbstractRepository', () => {
|
|
193
193
|
},
|
194
194
|
ConditionExpression: `attribute_exists(pk)`,
|
195
195
|
};
|
196
|
-
const partialItemWithKeyFields = {
|
197
|
-
name: 'foo',
|
198
|
-
};
|
199
196
|
const updateItem = {
|
197
|
+
name: 'foo',
|
200
198
|
country: 'au-updated',
|
201
199
|
};
|
202
|
-
const result = await repository.updateItem(
|
200
|
+
const result = await repository.updateItem(updateItem);
|
203
201
|
expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.UpdateCommand, input);
|
204
202
|
expect(result).toEqual(new TestItem({
|
205
203
|
name: 'foo',
|
@@ -208,19 +206,43 @@ describe('AbstractRepository', () => {
|
|
208
206
|
data: {},
|
209
207
|
}));
|
210
208
|
});
|
211
|
-
it('should
|
209
|
+
it('should not trigger update request if the input attributes are same as in the existing item', async () => {
|
212
210
|
ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
|
213
211
|
$metadata: {
|
214
212
|
httpStatusCode: 200,
|
215
213
|
},
|
214
|
+
Item: {
|
215
|
+
name: 'foo',
|
216
|
+
age: 99,
|
217
|
+
country: 'au',
|
218
|
+
data: {},
|
219
|
+
},
|
216
220
|
});
|
217
|
-
|
221
|
+
ddbClientMock.on(lib_dynamodb_1.UpdateCommand).rejects(new Error('updateItem() called when not expected'));
|
222
|
+
// update input attributes are same as in the existing item
|
223
|
+
const updateItem = {
|
218
224
|
name: 'foo',
|
225
|
+
country: 'au',
|
219
226
|
};
|
227
|
+
const result = await repository.updateItem(updateItem);
|
228
|
+
expect(result).toEqual(new TestItem({
|
229
|
+
name: 'foo',
|
230
|
+
age: 99,
|
231
|
+
country: 'au',
|
232
|
+
data: {},
|
233
|
+
}));
|
234
|
+
});
|
235
|
+
it('should return undefined if item does does not exist', async () => {
|
236
|
+
ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
|
237
|
+
$metadata: {
|
238
|
+
httpStatusCode: 200,
|
239
|
+
},
|
240
|
+
});
|
220
241
|
const updateItem = {
|
242
|
+
name: 'foo',
|
221
243
|
country: 'au-updated',
|
222
244
|
};
|
223
|
-
const result = await repository.updateItem(
|
245
|
+
const result = await repository.updateItem(updateItem);
|
224
246
|
expect(result).toEqual(undefined);
|
225
247
|
});
|
226
248
|
it('should return undefined if update cmd conditional check fails', async () => {
|
@@ -239,13 +261,11 @@ describe('AbstractRepository', () => {
|
|
239
261
|
$metadata: {},
|
240
262
|
message: 'not found',
|
241
263
|
}));
|
242
|
-
const partialItemWithKeyFields = {
|
243
|
-
name: 'foo',
|
244
|
-
};
|
245
264
|
const updateItem = {
|
265
|
+
name: 'foo',
|
246
266
|
country: 'au-updated',
|
247
267
|
};
|
248
|
-
const result = await repository.updateItem(
|
268
|
+
const result = await repository.updateItem(updateItem);
|
249
269
|
expect(result).toEqual(undefined);
|
250
270
|
});
|
251
271
|
it('should throw error update data has invalid data', async () => {
|
@@ -260,13 +280,11 @@ describe('AbstractRepository', () => {
|
|
260
280
|
data: {},
|
261
281
|
},
|
262
282
|
});
|
263
|
-
const partialItemWithKeyFields = {
|
264
|
-
name: 'foo',
|
265
|
-
};
|
266
283
|
const updateItem = {
|
284
|
+
name: 'foo',
|
267
285
|
country: 61, // should be "string" type
|
268
286
|
};
|
269
|
-
await expect(repository.updateItem(
|
287
|
+
await expect(repository.updateItem(updateItem)).rejects.toEqual(new Error('Invalid "country"'));
|
270
288
|
});
|
271
289
|
it('should throw error if excess column in input', async () => {
|
272
290
|
ddbClientMock.on(lib_dynamodb_1.GetCommand).resolves({
|
@@ -280,23 +298,19 @@ describe('AbstractRepository', () => {
|
|
280
298
|
data: {},
|
281
299
|
},
|
282
300
|
});
|
283
|
-
const partialItemWithKeyFields = {
|
284
|
-
name: 'foo',
|
285
|
-
};
|
286
301
|
const updateItem = {
|
302
|
+
name: 'foo',
|
287
303
|
country: 'au-updated',
|
288
304
|
extra: '',
|
289
305
|
};
|
290
|
-
await expect(repository.updateItem(
|
306
|
+
await expect(repository.updateItem(updateItem)).rejects.toEqual(new __1.InvalidDbSchemaError('Excess properties in entity test-item-entity: extra'));
|
291
307
|
});
|
292
308
|
it('should throw error if input does not includes key field(s)', async () => {
|
293
|
-
const partialItemWithKeyFields = {
|
294
|
-
age: 99,
|
295
|
-
};
|
296
309
|
const updateItem = {
|
310
|
+
age: 99,
|
297
311
|
country: 'au-updated', // should be "string" type
|
298
312
|
};
|
299
|
-
await expect(repository.updateItem(
|
313
|
+
await expect(repository.updateItem(updateItem)).rejects.toEqual(new __1.MissingKeyValuesError('Key field "name" must be specified in the input item in entity test-item-entity'));
|
300
314
|
});
|
301
315
|
});
|
302
316
|
describe('getItem()', () => {
|
@@ -385,6 +399,435 @@ describe('AbstractRepository', () => {
|
|
385
399
|
await expect(repository.getItem(partialItem)).rejects.toEqual(new InvalidDataFormatError_1.InvalidDataFormatError(`Field 'data2' defined as JSON String has a non-string data`));
|
386
400
|
});
|
387
401
|
});
|
402
|
+
describe('getItems()', () => {
|
403
|
+
it('should use BatchGetItem to get result', async () => {
|
404
|
+
ddbClientMock.on(lib_dynamodb_1.BatchGetCommand).resolves({
|
405
|
+
$metadata: {
|
406
|
+
httpStatusCode: 200,
|
407
|
+
},
|
408
|
+
Responses: {
|
409
|
+
[TABLE_NAME]: [
|
410
|
+
{
|
411
|
+
name: 'foo',
|
412
|
+
age: 99,
|
413
|
+
country: 'au',
|
414
|
+
data: {},
|
415
|
+
data2: '{"foo":"bar","num":123}',
|
416
|
+
},
|
417
|
+
{
|
418
|
+
name: 'foo2',
|
419
|
+
age: 999,
|
420
|
+
country: 'au',
|
421
|
+
data: {},
|
422
|
+
data2: '{"foo":"bar","num":123}',
|
423
|
+
},
|
424
|
+
],
|
425
|
+
},
|
426
|
+
});
|
427
|
+
const input = {
|
428
|
+
RequestItems: {
|
429
|
+
[TABLE_NAME]: {
|
430
|
+
Keys: [
|
431
|
+
{ pk: 'test_item#foo', sk: '#meta' },
|
432
|
+
{ pk: 'test_item#foo2', sk: '#meta' },
|
433
|
+
],
|
434
|
+
},
|
435
|
+
},
|
436
|
+
};
|
437
|
+
const requestItems = [{ name: 'foo' }, { name: 'foo2' }];
|
438
|
+
const result = await repository.getItems(requestItems);
|
439
|
+
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.BatchGetCommand, input);
|
440
|
+
expect(result).toEqual([
|
441
|
+
new TestItem({
|
442
|
+
name: 'foo',
|
443
|
+
age: 99,
|
444
|
+
country: 'au',
|
445
|
+
data: {},
|
446
|
+
data2: {
|
447
|
+
foo: 'bar',
|
448
|
+
num: 123,
|
449
|
+
},
|
450
|
+
}),
|
451
|
+
new TestItem({
|
452
|
+
name: 'foo2',
|
453
|
+
age: 999,
|
454
|
+
country: 'au',
|
455
|
+
data: {},
|
456
|
+
data2: {
|
457
|
+
foo: 'bar',
|
458
|
+
num: 123,
|
459
|
+
},
|
460
|
+
}),
|
461
|
+
]);
|
462
|
+
});
|
463
|
+
it('should retry if unprocessed keys returned', async () => {
|
464
|
+
const input1 = {
|
465
|
+
RequestItems: {
|
466
|
+
[TABLE_NAME]: {
|
467
|
+
Keys: [
|
468
|
+
{ pk: 'test_item#foo', sk: '#meta' },
|
469
|
+
{ pk: 'test_item#foo2', sk: '#meta' },
|
470
|
+
],
|
471
|
+
},
|
472
|
+
},
|
473
|
+
};
|
474
|
+
const input2 = {
|
475
|
+
RequestItems: {
|
476
|
+
[TABLE_NAME]: {
|
477
|
+
Keys: [{ pk: 'test_item#foo2', sk: '#meta' }],
|
478
|
+
},
|
479
|
+
},
|
480
|
+
};
|
481
|
+
ddbClientMock.on(lib_dynamodb_1.BatchGetCommand, input1).resolves({
|
482
|
+
$metadata: {
|
483
|
+
httpStatusCode: 200,
|
484
|
+
},
|
485
|
+
Responses: {
|
486
|
+
[TABLE_NAME]: [
|
487
|
+
{
|
488
|
+
name: 'foo',
|
489
|
+
age: 99,
|
490
|
+
country: 'au',
|
491
|
+
data: {},
|
492
|
+
data2: '{"foo":"bar","num":123}',
|
493
|
+
},
|
494
|
+
],
|
495
|
+
},
|
496
|
+
UnprocessedKeys: {
|
497
|
+
[TABLE_NAME]: {
|
498
|
+
Keys: [{ pk: 'test_item#foo2', sk: '#meta' }],
|
499
|
+
},
|
500
|
+
},
|
501
|
+
});
|
502
|
+
ddbClientMock.on(lib_dynamodb_1.BatchGetCommand, input2).resolves({
|
503
|
+
$metadata: {
|
504
|
+
httpStatusCode: 200,
|
505
|
+
},
|
506
|
+
Responses: {
|
507
|
+
[TABLE_NAME]: [
|
508
|
+
{
|
509
|
+
name: 'foo2',
|
510
|
+
age: 999,
|
511
|
+
country: 'au',
|
512
|
+
data: {},
|
513
|
+
data2: '{"foo":"bar","num":123}',
|
514
|
+
},
|
515
|
+
],
|
516
|
+
},
|
517
|
+
UnprocessedKeys: {},
|
518
|
+
});
|
519
|
+
const requestItems = [{ name: 'foo' }, { name: 'foo2' }];
|
520
|
+
const result = await repository.getItems(requestItems);
|
521
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(1, lib_dynamodb_1.BatchGetCommand, input1);
|
522
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.BatchGetCommand, input2);
|
523
|
+
expect(result).toEqual([
|
524
|
+
new TestItem({
|
525
|
+
name: 'foo',
|
526
|
+
age: 99,
|
527
|
+
country: 'au',
|
528
|
+
data: {},
|
529
|
+
data2: {
|
530
|
+
foo: 'bar',
|
531
|
+
num: 123,
|
532
|
+
},
|
533
|
+
}),
|
534
|
+
new TestItem({
|
535
|
+
name: 'foo2',
|
536
|
+
age: 999,
|
537
|
+
country: 'au',
|
538
|
+
data: {},
|
539
|
+
data2: {
|
540
|
+
foo: 'bar',
|
541
|
+
num: 123,
|
542
|
+
},
|
543
|
+
}),
|
544
|
+
]);
|
545
|
+
});
|
546
|
+
it('should fail after max retries for unprocessed keys', async () => {
|
547
|
+
const input1 = {
|
548
|
+
RequestItems: {
|
549
|
+
[TABLE_NAME]: {
|
550
|
+
Keys: [
|
551
|
+
{ pk: 'test_item#foo', sk: '#meta' },
|
552
|
+
{ pk: 'test_item#foo2', sk: '#meta' },
|
553
|
+
],
|
554
|
+
},
|
555
|
+
},
|
556
|
+
};
|
557
|
+
const input2 = {
|
558
|
+
RequestItems: {
|
559
|
+
[TABLE_NAME]: {
|
560
|
+
Keys: [{ pk: 'test_item#foo2', sk: '#meta' }],
|
561
|
+
},
|
562
|
+
},
|
563
|
+
};
|
564
|
+
ddbClientMock.on(lib_dynamodb_1.BatchGetCommand, input1).resolves({
|
565
|
+
$metadata: {
|
566
|
+
httpStatusCode: 200,
|
567
|
+
},
|
568
|
+
Responses: {
|
569
|
+
[TABLE_NAME]: [
|
570
|
+
{
|
571
|
+
name: 'foo',
|
572
|
+
age: 99,
|
573
|
+
country: 'au',
|
574
|
+
data: {},
|
575
|
+
data2: '{"foo":"bar","num":123}',
|
576
|
+
},
|
577
|
+
],
|
578
|
+
},
|
579
|
+
UnprocessedKeys: {
|
580
|
+
[TABLE_NAME]: {
|
581
|
+
Keys: [{ pk: 'test_item#foo2', sk: '#meta' }],
|
582
|
+
},
|
583
|
+
},
|
584
|
+
});
|
585
|
+
ddbClientMock.on(lib_dynamodb_1.BatchGetCommand, input2).resolves({
|
586
|
+
$metadata: {
|
587
|
+
httpStatusCode: 200,
|
588
|
+
},
|
589
|
+
Responses: {},
|
590
|
+
UnprocessedKeys: {
|
591
|
+
[TABLE_NAME]: {
|
592
|
+
Keys: [{ pk: 'test_item#foo2', sk: '#meta' }],
|
593
|
+
},
|
594
|
+
},
|
595
|
+
});
|
596
|
+
const requestItems = [{ name: 'foo' }, { name: 'foo2' }];
|
597
|
+
await expect(repository.getItems(requestItems)).rejects.toEqual(new Error('Maximum allowed retries exceeded for unprocessed items'));
|
598
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(1, lib_dynamodb_1.BatchGetCommand, input1);
|
599
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.BatchGetCommand, input2);
|
600
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(3, lib_dynamodb_1.BatchGetCommand, input2);
|
601
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(4, lib_dynamodb_1.BatchGetCommand, input2);
|
602
|
+
});
|
603
|
+
it('should request BatchGetItem in batch of 100 items to get result', async () => {
|
604
|
+
ddbClientMock.on(lib_dynamodb_1.BatchGetCommand).resolves({
|
605
|
+
$metadata: {
|
606
|
+
httpStatusCode: 200,
|
607
|
+
},
|
608
|
+
});
|
609
|
+
const requestItems = [];
|
610
|
+
for (let i = 0; i < 120; i++) {
|
611
|
+
requestItems.push({ name: `foo${i}` });
|
612
|
+
}
|
613
|
+
// keys for first batch request
|
614
|
+
const keys1 = [];
|
615
|
+
for (let i = 0; i < 100; i++) {
|
616
|
+
keys1.push({ pk: `test_item#foo${i}`, sk: '#meta' });
|
617
|
+
}
|
618
|
+
// keys for second batch request
|
619
|
+
const keys2 = [];
|
620
|
+
for (let i = 100; i < 120; i++) {
|
621
|
+
keys2.push({ pk: `test_item#foo${i}`, sk: '#meta' });
|
622
|
+
}
|
623
|
+
const input1 = {
|
624
|
+
RequestItems: {
|
625
|
+
[TABLE_NAME]: {
|
626
|
+
Keys: keys1,
|
627
|
+
},
|
628
|
+
},
|
629
|
+
};
|
630
|
+
const input2 = {
|
631
|
+
RequestItems: {
|
632
|
+
[TABLE_NAME]: {
|
633
|
+
Keys: keys2,
|
634
|
+
},
|
635
|
+
},
|
636
|
+
};
|
637
|
+
await repository.getItems(requestItems);
|
638
|
+
expect(ddbClientMock).toHaveReceivedCommandTimes(lib_dynamodb_1.BatchGetCommand, 2);
|
639
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(1, lib_dynamodb_1.BatchGetCommand, input1);
|
640
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.BatchGetCommand, input2);
|
641
|
+
});
|
642
|
+
it('should throw error if any input item does not includes key field(s)', async () => {
|
643
|
+
const requestItems = [{ name: 'foo' }, { age: 22 }];
|
644
|
+
await expect(repository.getItems(requestItems)).rejects.toEqual(new __1.MissingKeyValuesError('Key field "name" must be specified in the input item in entity test-item-entity'));
|
645
|
+
});
|
646
|
+
});
|
647
|
+
describe('deleteItems()', () => {
|
648
|
+
it('should use batchWrite() to get result', async () => {
|
649
|
+
ddbClientMock.on(lib_dynamodb_1.BatchWriteCommand).resolves({
|
650
|
+
$metadata: {
|
651
|
+
httpStatusCode: 200,
|
652
|
+
},
|
653
|
+
ItemCollectionMetrics: {
|
654
|
+
[TABLE_NAME]: [{}],
|
655
|
+
},
|
656
|
+
});
|
657
|
+
const input = {
|
658
|
+
RequestItems: {
|
659
|
+
[TABLE_NAME]: [
|
660
|
+
{
|
661
|
+
DeleteRequest: {
|
662
|
+
Key: { pk: 'test_item#foo', sk: '#meta' },
|
663
|
+
},
|
664
|
+
},
|
665
|
+
{
|
666
|
+
DeleteRequest: {
|
667
|
+
Key: { pk: 'test_item#foo2', sk: '#meta' },
|
668
|
+
},
|
669
|
+
},
|
670
|
+
],
|
671
|
+
},
|
672
|
+
};
|
673
|
+
const requestItems = [{ name: 'foo' }, { name: 'foo2' }];
|
674
|
+
await repository.deleteItems(requestItems);
|
675
|
+
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.BatchWriteCommand, input);
|
676
|
+
});
|
677
|
+
it('should use re-try if unprocessed items returned', async () => {
|
678
|
+
const input1 = {
|
679
|
+
RequestItems: {
|
680
|
+
[TABLE_NAME]: [
|
681
|
+
{
|
682
|
+
DeleteRequest: {
|
683
|
+
Key: { pk: 'test_item#foo', sk: '#meta' },
|
684
|
+
},
|
685
|
+
},
|
686
|
+
{
|
687
|
+
DeleteRequest: {
|
688
|
+
Key: { pk: 'test_item#foo2', sk: '#meta' },
|
689
|
+
},
|
690
|
+
},
|
691
|
+
],
|
692
|
+
},
|
693
|
+
};
|
694
|
+
const input2 = {
|
695
|
+
RequestItems: {
|
696
|
+
[TABLE_NAME]: [
|
697
|
+
{
|
698
|
+
DeleteRequest: {
|
699
|
+
Key: { pk: 'test_item#foo2', sk: '#meta' },
|
700
|
+
},
|
701
|
+
},
|
702
|
+
],
|
703
|
+
},
|
704
|
+
};
|
705
|
+
ddbClientMock.on(lib_dynamodb_1.BatchWriteCommand, input1).resolves({
|
706
|
+
$metadata: {
|
707
|
+
httpStatusCode: 200,
|
708
|
+
},
|
709
|
+
UnprocessedItems: {
|
710
|
+
[TABLE_NAME]: [
|
711
|
+
{
|
712
|
+
DeleteRequest: {
|
713
|
+
Key: { pk: 'test_item#foo2', sk: '#meta' },
|
714
|
+
},
|
715
|
+
},
|
716
|
+
],
|
717
|
+
},
|
718
|
+
});
|
719
|
+
ddbClientMock.on(lib_dynamodb_1.BatchWriteCommand, input2).resolves({
|
720
|
+
$metadata: {
|
721
|
+
httpStatusCode: 200,
|
722
|
+
},
|
723
|
+
UnprocessedItems: {},
|
724
|
+
});
|
725
|
+
const requestItems = [{ name: 'foo' }, { name: 'foo2' }];
|
726
|
+
await repository.deleteItems(requestItems);
|
727
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(1, lib_dynamodb_1.BatchWriteCommand, input1);
|
728
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.BatchWriteCommand, input2);
|
729
|
+
});
|
730
|
+
it('should fail after max number of retries', async () => {
|
731
|
+
const input1 = {
|
732
|
+
RequestItems: {
|
733
|
+
[TABLE_NAME]: [
|
734
|
+
{
|
735
|
+
DeleteRequest: {
|
736
|
+
Key: { pk: 'test_item#foo', sk: '#meta' },
|
737
|
+
},
|
738
|
+
},
|
739
|
+
{
|
740
|
+
DeleteRequest: {
|
741
|
+
Key: { pk: 'test_item#foo2', sk: '#meta' },
|
742
|
+
},
|
743
|
+
},
|
744
|
+
],
|
745
|
+
},
|
746
|
+
};
|
747
|
+
const input2 = {
|
748
|
+
RequestItems: {
|
749
|
+
[TABLE_NAME]: [
|
750
|
+
{
|
751
|
+
DeleteRequest: {
|
752
|
+
Key: { pk: 'test_item#foo2', sk: '#meta' },
|
753
|
+
},
|
754
|
+
},
|
755
|
+
],
|
756
|
+
},
|
757
|
+
};
|
758
|
+
ddbClientMock.on(lib_dynamodb_1.BatchWriteCommand).resolves({
|
759
|
+
$metadata: {
|
760
|
+
httpStatusCode: 200,
|
761
|
+
},
|
762
|
+
UnprocessedItems: {
|
763
|
+
[TABLE_NAME]: [
|
764
|
+
{
|
765
|
+
DeleteRequest: {
|
766
|
+
Key: { pk: 'test_item#foo2', sk: '#meta' },
|
767
|
+
},
|
768
|
+
},
|
769
|
+
],
|
770
|
+
},
|
771
|
+
});
|
772
|
+
const requestItems = [{ name: 'foo' }, { name: 'foo2' }];
|
773
|
+
await expect(repository.deleteItems(requestItems)).rejects.toEqual(new Error('Maximum allowed retries exceeded for unprocessed items'));
|
774
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(1, lib_dynamodb_1.BatchWriteCommand, input1);
|
775
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.BatchWriteCommand, input2);
|
776
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(3, lib_dynamodb_1.BatchWriteCommand, input2);
|
777
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(4, lib_dynamodb_1.BatchWriteCommand, input2);
|
778
|
+
});
|
779
|
+
it('should request batchWrite in batch of 25 items to get result', async () => {
|
780
|
+
ddbClientMock.on(lib_dynamodb_1.BatchWriteCommand).resolves({
|
781
|
+
$metadata: {
|
782
|
+
httpStatusCode: 200,
|
783
|
+
},
|
784
|
+
});
|
785
|
+
const requestItems = [];
|
786
|
+
for (let i = 0; i < 30; i++) {
|
787
|
+
requestItems.push({ name: `foo${i}` });
|
788
|
+
}
|
789
|
+
// keys for first batch request
|
790
|
+
const keys1 = [];
|
791
|
+
for (let i = 0; i < 25; i++) {
|
792
|
+
keys1.push({ pk: `test_item#foo${i}`, sk: '#meta' });
|
793
|
+
}
|
794
|
+
// keys for second batch request
|
795
|
+
const keys2 = [];
|
796
|
+
for (let i = 25; i < 30; i++) {
|
797
|
+
keys2.push({ pk: `test_item#foo${i}`, sk: '#meta' });
|
798
|
+
}
|
799
|
+
const input1 = {
|
800
|
+
RequestItems: {
|
801
|
+
[TABLE_NAME]: keys1.map((key) => {
|
802
|
+
return {
|
803
|
+
DeleteRequest: {
|
804
|
+
Key: key,
|
805
|
+
},
|
806
|
+
};
|
807
|
+
}),
|
808
|
+
},
|
809
|
+
};
|
810
|
+
const input2 = {
|
811
|
+
RequestItems: {
|
812
|
+
[TABLE_NAME]: keys2.map((key) => {
|
813
|
+
return {
|
814
|
+
DeleteRequest: {
|
815
|
+
Key: key,
|
816
|
+
},
|
817
|
+
};
|
818
|
+
}),
|
819
|
+
},
|
820
|
+
};
|
821
|
+
await repository.deleteItems(requestItems);
|
822
|
+
expect(ddbClientMock).toHaveReceivedCommandTimes(lib_dynamodb_1.BatchWriteCommand, 2);
|
823
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(1, lib_dynamodb_1.BatchWriteCommand, input1);
|
824
|
+
expect(ddbClientMock).toHaveReceivedNthCommandWith(2, lib_dynamodb_1.BatchWriteCommand, input2);
|
825
|
+
});
|
826
|
+
it('should throw error if any input item does not includes key field(s)', async () => {
|
827
|
+
const requestItems = [{ name: 'foo' }, { age: 22 }];
|
828
|
+
await expect(repository.deleteItems(requestItems)).rejects.toEqual(new __1.MissingKeyValuesError('Key field "name" must be specified in the input item in entity test-item-entity'));
|
829
|
+
});
|
830
|
+
});
|
388
831
|
describe('queryItems()', () => {
|
389
832
|
it('should return the items if found', async () => {
|
390
833
|
ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
|
@@ -462,8 +905,7 @@ describe('AbstractRepository', () => {
|
|
462
905
|
},
|
463
906
|
};
|
464
907
|
const partialItem = { name: 'foo' };
|
465
|
-
const
|
466
|
-
const _result = await repository.queryItems(partialItem, useSortKey);
|
908
|
+
const _result = await repository.queryItems(partialItem, { useSortKey: true });
|
467
909
|
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
|
468
910
|
});
|
469
911
|
it('should return the items if found when using gsi index', async () => {
|
@@ -500,7 +942,7 @@ describe('AbstractRepository', () => {
|
|
500
942
|
};
|
501
943
|
const partialItem = { country: 'au' };
|
502
944
|
const useSortKey = false;
|
503
|
-
const result = await repository.queryItems(partialItem, useSortKey, index);
|
945
|
+
const result = await repository.queryItems(partialItem, { useSortKey, index });
|
504
946
|
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
|
505
947
|
expect(result).toEqual([
|
506
948
|
new TestItem({
|
@@ -539,13 +981,122 @@ describe('AbstractRepository', () => {
|
|
539
981
|
};
|
540
982
|
const partialItem = { age: 99, country: 'au' };
|
541
983
|
const useSortKey = true;
|
542
|
-
const _result = await repository.queryItems(partialItem, useSortKey, index);
|
984
|
+
const _result = await repository.queryItems(partialItem, { useSortKey, index });
|
985
|
+
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
|
986
|
+
});
|
987
|
+
it('should set input query correctly when "filter - begins_with" query option is set', async () => {
|
988
|
+
ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
|
989
|
+
$metadata: {
|
990
|
+
httpStatusCode: 200,
|
991
|
+
},
|
992
|
+
});
|
993
|
+
const input = {
|
994
|
+
TableName: TABLE_NAME,
|
995
|
+
KeyConditionExpression: '#pkName = :pkValue AND begins_with(#skName, :skValue)',
|
996
|
+
ExpressionAttributeNames: {
|
997
|
+
'#pkName': 'pk',
|
998
|
+
'#skName': 'sk',
|
999
|
+
},
|
1000
|
+
ExpressionAttributeValues: {
|
1001
|
+
':pkValue': 'test_item#foo',
|
1002
|
+
':skValue': 'keyword-x',
|
1003
|
+
},
|
1004
|
+
};
|
1005
|
+
const partialItem = { name: 'foo' };
|
1006
|
+
const queryOptions = {
|
1007
|
+
filter: {
|
1008
|
+
type: 'begins_with',
|
1009
|
+
keyword: 'keyword-x',
|
1010
|
+
},
|
1011
|
+
};
|
1012
|
+
await repository.queryItems(partialItem, queryOptions);
|
1013
|
+
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
|
1014
|
+
});
|
1015
|
+
it('should throw error invalid "filter" query option is set', async () => {
|
1016
|
+
ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
|
1017
|
+
$metadata: {
|
1018
|
+
httpStatusCode: 200,
|
1019
|
+
},
|
1020
|
+
});
|
1021
|
+
const partialItem = { name: 'foo' };
|
1022
|
+
const queryOptions = {
|
1023
|
+
filter: {
|
1024
|
+
type: 'invalid-type',
|
1025
|
+
keyword: 'keyword-x',
|
1026
|
+
},
|
1027
|
+
};
|
1028
|
+
await expect(repository.queryItems(partialItem, queryOptions)).rejects.toEqual(new Error(`Invalid query filter type: invalid-type`));
|
1029
|
+
});
|
1030
|
+
it('should set input query correctly when "limit" query option is set', async () => {
|
1031
|
+
ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
|
1032
|
+
$metadata: {
|
1033
|
+
httpStatusCode: 200,
|
1034
|
+
},
|
1035
|
+
});
|
1036
|
+
const input = {
|
1037
|
+
TableName: TABLE_NAME,
|
1038
|
+
KeyConditionExpression: '#pkName = :pkValue',
|
1039
|
+
ExpressionAttributeNames: {
|
1040
|
+
'#pkName': 'pk',
|
1041
|
+
},
|
1042
|
+
ExpressionAttributeValues: {
|
1043
|
+
':pkValue': 'test_item#foo',
|
1044
|
+
},
|
1045
|
+
Limit: 33,
|
1046
|
+
};
|
1047
|
+
const partialItem = { name: 'foo' };
|
1048
|
+
const queryOptions = { limit: 33 };
|
1049
|
+
await repository.queryItems(partialItem, queryOptions);
|
1050
|
+
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
|
1051
|
+
});
|
1052
|
+
it('should set input query correctly when "order asc" query option is set', async () => {
|
1053
|
+
ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
|
1054
|
+
$metadata: {
|
1055
|
+
httpStatusCode: 200,
|
1056
|
+
},
|
1057
|
+
});
|
1058
|
+
const input = {
|
1059
|
+
TableName: TABLE_NAME,
|
1060
|
+
KeyConditionExpression: '#pkName = :pkValue',
|
1061
|
+
ExpressionAttributeNames: {
|
1062
|
+
'#pkName': 'pk',
|
1063
|
+
},
|
1064
|
+
ExpressionAttributeValues: {
|
1065
|
+
':pkValue': 'test_item#foo',
|
1066
|
+
},
|
1067
|
+
ScanIndexForward: true,
|
1068
|
+
};
|
1069
|
+
const partialItem = { name: 'foo' };
|
1070
|
+
const queryOptions = { order: 'asc' };
|
1071
|
+
await repository.queryItems(partialItem, queryOptions);
|
1072
|
+
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
|
1073
|
+
});
|
1074
|
+
it('should set input query correctly when "order desc" query option is set', async () => {
|
1075
|
+
ddbClientMock.on(lib_dynamodb_1.QueryCommand).resolves({
|
1076
|
+
$metadata: {
|
1077
|
+
httpStatusCode: 200,
|
1078
|
+
},
|
1079
|
+
});
|
1080
|
+
const input = {
|
1081
|
+
TableName: TABLE_NAME,
|
1082
|
+
KeyConditionExpression: '#pkName = :pkValue',
|
1083
|
+
ExpressionAttributeNames: {
|
1084
|
+
'#pkName': 'pk',
|
1085
|
+
},
|
1086
|
+
ExpressionAttributeValues: {
|
1087
|
+
':pkValue': 'test_item#foo',
|
1088
|
+
},
|
1089
|
+
ScanIndexForward: false,
|
1090
|
+
};
|
1091
|
+
const partialItem = { name: 'foo' };
|
1092
|
+
const queryOptions = { order: 'desc' };
|
1093
|
+
await repository.queryItems(partialItem, queryOptions);
|
543
1094
|
expect(ddbClientMock).toHaveReceivedCommandWith(lib_dynamodb_1.QueryCommand, input);
|
544
1095
|
});
|
545
1096
|
it('should throw error for invalid index query', async () => {
|
546
1097
|
const index = 'undefined-index';
|
547
1098
|
const partialItem = { age: 99, country: 'au' };
|
548
|
-
await expect(repository.queryItems(partialItem,
|
1099
|
+
await expect(repository.queryItems(partialItem, { index })).rejects.toEqual(new __1.MissingKeyValuesError(`Table index '${index}' not defined on entity test-item-entity`));
|
549
1100
|
});
|
550
1101
|
it('should throw error for missing key fields', async () => {
|
551
1102
|
const partialItem = { age: 99, country: 'au' };
|
@@ -555,13 +1106,13 @@ describe('AbstractRepository', () => {
|
|
555
1106
|
const partialItem = { name: 'foo' };
|
556
1107
|
const useSortKey = false;
|
557
1108
|
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`));
|
1109
|
+
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
1110
|
});
|
560
1111
|
it('should throw error for missing key fields with "useSortKey" param true when using index', async () => {
|
561
1112
|
const partialItem = { country: 'au' };
|
562
1113
|
const useSortKey = true;
|
563
1114
|
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`));
|
1115
|
+
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
1116
|
});
|
566
1117
|
});
|
567
1118
|
describe('deleteItem()', () => {
|
@@ -628,7 +1179,6 @@ describe('AbstractRepository', () => {
|
|
628
1179
|
await repository.deleteItem({ name: 'foo' }, transaction);
|
629
1180
|
await repository.updateItem({
|
630
1181
|
name: 'foo2',
|
631
|
-
}, {
|
632
1182
|
age: 55,
|
633
1183
|
}, transaction);
|
634
1184
|
return await repository.createItem({
|