Skip to content

Commit fda6045

Browse files
Merge pull request #112 from shiftcode/#104-fix-batch-write-single-table-request
fix(batch-write-single-table-request): delete/put directly to params
2 parents 1f3a501 + 68e0150 commit fda6045

File tree

5 files changed

+215
-123
lines changed

5 files changed

+215
-123
lines changed

src/dynamo/batchget/batch-get-utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ export function batchGetItemsFetchAll(
2929
)
3030
}
3131

32-
export type ResponseWithUnprocessedKeys = DynamoDB.BatchGetItemOutput & { UnprocessedKeys: BatchGetRequestMap }
32+
export type BatchGetItemOutputWithUnprocessedKeys =
33+
DynamoDB.BatchGetItemOutput
34+
& { UnprocessedKeys: BatchGetRequestMap }
3335

34-
export function hasUnprocessedKeys(response: DynamoDB.BatchGetItemOutput): response is ResponseWithUnprocessedKeys {
36+
export function hasUnprocessedKeys(response: DynamoDB.BatchGetItemOutput): response is BatchGetItemOutputWithUnprocessedKeys {
3537
if (!response.UnprocessedKeys) {
3638
return false
3739
}
Lines changed: 120 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// tslint:disable:no-unnecessary-class
2+
13
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
24
import { of } from 'rxjs'
35
import { Organization } from '../../../../test/models'
@@ -7,38 +9,53 @@ import { BatchWriteSingleTableRequest } from './batch-write-single-table.request
79

810
describe('batch write single table request', () => {
911
const tableName = getTableName(Organization)
12+
const item: Organization = <Organization>{
13+
id: 'myId',
14+
createdAtDate: new Date(),
15+
name: 'myOrg',
16+
}
1017

11-
let item: Organization
1218
let dynamoRx: DynamoRx
1319
let request: BatchWriteSingleTableRequest<Organization>
1420

15-
let nextSpyFn: () => { value: number }
16-
const generatorMock = () => <any>{ next: nextSpyFn }
21+
describe('constructor', () => {
22+
it('should throw when no class was given', () => {
23+
expect(() => new BatchWriteSingleTableRequest(<any>null, <any>null)).toThrow()
24+
})
25+
it('should throw when class given is not @Model decorated', () => {
26+
class NoModel {}
27+
expect(() => new BatchWriteSingleTableRequest(<any>null, NoModel)).toThrow()
28+
})
1729

18-
beforeEach(() => {
19-
item = <any>{
20-
id: 'myId',
21-
createdAtDate: new Date(),
22-
name: 'myOrg',
23-
}
24-
nextSpyFn = jest.fn().mockImplementation(() => ({ value: 0 }))
30+
it('should initialize params', () => {
31+
request = new BatchWriteSingleTableRequest(<any>null, Organization)
32+
expect(request.params).toEqual({
33+
RequestItems: {
34+
[tableName]: [],
35+
},
36+
})
37+
})
2538
})
2639

2740
describe('correct params', () => {
2841
beforeEach(() => {
29-
dynamoRx = new DynamoRx()
3042
request = new BatchWriteSingleTableRequest(dynamoRx, Organization)
43+
})
44+
45+
it('returnConsumedCapacity', () => {
46+
request.returnConsumedCapacity('TOTAL')
47+
expect(request.params.ReturnConsumedCapacity).toBe('TOTAL')
48+
})
3149

32-
const output: DynamoDB.BatchWriteItemOutput = {}
33-
spyOn(dynamoRx, 'batchWriteItem').and.returnValue(of(output))
50+
it('returnItemCollectionMetrics', () => {
51+
request.returnItemCollectionMetrics('SIZE')
52+
expect(request.params.ReturnItemCollectionMetrics).toBe('SIZE')
3453
})
3554

36-
it('delete with complex primary key', async () => {
55+
it('delete with composite key', () => {
3756
request.delete([item])
38-
await request.exec(generatorMock).toPromise()
3957

40-
expect(dynamoRx.batchWriteItem).toHaveBeenCalledTimes(1)
41-
expect(dynamoRx.batchWriteItem).toHaveBeenCalledWith({
58+
expect(request.params).toEqual({
4259
RequestItems: {
4360
[tableName]: [
4461
{
@@ -52,15 +69,12 @@ describe('batch write single table request', () => {
5269
],
5370
},
5471
})
55-
expect(nextSpyFn).toHaveBeenCalledTimes(0)
5672
})
5773

5874
it('put object', async () => {
5975
request.put([item])
60-
await request.exec(generatorMock).toPromise()
6176

62-
expect(dynamoRx.batchWriteItem).toHaveBeenCalledTimes(1)
63-
expect(dynamoRx.batchWriteItem).toHaveBeenCalledWith({
77+
expect(request.params).toEqual({
6478
RequestItems: {
6579
[tableName]: [
6680
{
@@ -75,50 +89,103 @@ describe('batch write single table request', () => {
7589
],
7690
},
7791
})
78-
expect(nextSpyFn).toHaveBeenCalledTimes(0)
7992
})
8093

81-
it('delete >25 items in two requests', async () => {
82-
const twentyFiveItems = []
83-
for (let i = 0; i < 25; i++) {
84-
twentyFiveItems.push(item)
85-
}
86-
request.delete(twentyFiveItems)
94+
it('adding >25 items in first delete call throws', () => {
95+
const twentyFiveItems = new Array(30).map(() => item)
96+
expect(() => request.delete(twentyFiveItems)).toThrow()
97+
})
98+
99+
it('adding >25 items in second delete call throws', () => {
100+
const twentyFiveItems = new Array(25).map(() => item)
87101
request.delete(twentyFiveItems)
88-
await request.exec(generatorMock).toPromise()
89-
expect(dynamoRx.batchWriteItem).toHaveBeenCalledTimes(2)
90-
expect(nextSpyFn).toHaveBeenCalledTimes(0)
102+
expect(() => request.delete(twentyFiveItems)).toThrow()
103+
})
104+
105+
it('adding >25 items in first put call throws', () => {
106+
const twentyFiveItems = new Array(30).map(() => item)
107+
expect(() => request.put(twentyFiveItems)).toThrow()
108+
})
109+
110+
it('adding >25 items in second put call throws', () => {
111+
const twentyFiveItems = new Array(25).map(() => item)
112+
request.put(twentyFiveItems)
113+
expect(() => request.put(twentyFiveItems)).toThrow()
91114
})
92115
})
93116

94-
describe('correct backoff', () => {
117+
describe('Unprocessed items', () => {
118+
const output: DynamoDB.BatchWriteItemOutput = {
119+
UnprocessedItems: {
120+
[tableName]: [
121+
{
122+
PutRequest: {
123+
Item: {
124+
id: { S: 'myId' },
125+
createdAtDate: { S: item.createdAtDate.toISOString() },
126+
name: { S: 'myOrg' },
127+
},
128+
},
129+
},
130+
],
131+
},
132+
}
133+
134+
let generatorSpy: jasmine.Spy
135+
let nextFnSpy: jasmine.Spy
136+
let batchWriteItemSpy: jasmine.Spy
137+
95138
beforeEach(() => {
96-
dynamoRx = new DynamoRx()
139+
batchWriteItemSpy = jasmine.createSpy().and.returnValues(of(output), of(output), of({ MyResult: true }))
140+
nextFnSpy = jasmine.createSpy().and.returnValue({ value: 0 })
141+
dynamoRx = <DynamoRx>(<any>{ batchWriteItem: batchWriteItemSpy })
142+
generatorSpy = jasmine.createSpy().and.returnValue({ next: nextFnSpy })
143+
97144
request = new BatchWriteSingleTableRequest(dynamoRx, Organization)
145+
})
98146

99-
const output: DynamoDB.BatchWriteItemOutput = {
100-
UnprocessedItems: {
101-
[tableName]: [
102-
{
103-
PutRequest: {
104-
Item: {
105-
id: { S: 'myId' },
106-
createdAtDate: { S: item.createdAtDate.toISOString() },
107-
name: { S: 'myOrg' },
108-
},
109-
},
110-
},
111-
],
112-
},
113-
}
114-
spyOn(dynamoRx, 'batchWriteItem').and.returnValues(of(output), of({}))
147+
it('should retry when unprocessed items are returned', async () => {
148+
request.put([item])
149+
await request.exec(<any>generatorSpy).toPromise()
150+
151+
// only one instance of the generator should be created
152+
expect(generatorSpy).toHaveBeenCalledTimes(1)
153+
154+
expect(nextFnSpy).toHaveBeenCalledTimes(2)
155+
156+
expect(batchWriteItemSpy).toHaveBeenCalledTimes(3)
115157
})
116158

117-
it('should retry when capacity is exceeded', async () => {
159+
it('should keep other params in additional calls', async () => {
118160
request.put([item])
119-
await request.exec(generatorMock).toPromise()
120-
expect(dynamoRx.batchWriteItem).toHaveBeenCalledTimes(2)
121-
expect(nextSpyFn).toHaveBeenCalledTimes(1)
161+
request.returnConsumedCapacity('TOTAL')
162+
request.returnItemCollectionMetrics('SIZE')
163+
await request.exec(<any>generatorSpy).toPromise()
164+
165+
expect(batchWriteItemSpy).toHaveBeenCalledTimes(3)
166+
const paramsThirdCall = <DynamoDB.BatchWriteItemInput>batchWriteItemSpy.calls.all()[2].args[0]
167+
168+
expect(paramsThirdCall).toBeDefined()
169+
expect(paramsThirdCall.ReturnConsumedCapacity).toBe('TOTAL')
170+
expect(paramsThirdCall.ReturnItemCollectionMetrics).toBe('SIZE')
171+
})
172+
})
173+
174+
describe('exec / execFullResponse', () => {
175+
beforeEach(() => {
176+
dynamoRx = <DynamoRx>(<any>{ batchWriteItem: () => of({ myResponse: true }) })
177+
request = new BatchWriteSingleTableRequest(dynamoRx, Organization)
178+
request.delete([item])
179+
})
180+
181+
it('exec should return nothing', async () => {
182+
const response = await request.exec().toPromise()
183+
expect(response).toBeUndefined()
184+
})
185+
186+
it('execFullResponse should return BatchWriteItemOutput', async () => {
187+
const response = await request.execFullResponse().toPromise()
188+
expect(response).toEqual({ myResponse: true })
122189
})
123190
})
124191
})

0 commit comments

Comments
 (0)