@squiz/db-lib 1.71.3 → 1.73.0
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/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({
|