@squiz/db-lib 1.71.2 → 1.71.3
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 +8 -0
- package/lib/AbstractRepository.d.ts +2 -0
- package/lib/AbstractRepository.d.ts.map +1 -0
- package/lib/AbstractRepository.integration.spec.d.ts +1 -0
- package/lib/AbstractRepository.integration.spec.d.ts.map +1 -0
- package/lib/AbstractRepository.integration.spec.js +118 -0
- package/lib/AbstractRepository.integration.spec.js.map +1 -0
- package/lib/AbstractRepository.js +187 -0
- package/lib/AbstractRepository.js.map +1 -0
- package/lib/ConnectionManager.d.ts +1 -0
- package/lib/ConnectionManager.d.ts.map +1 -0
- package/lib/ConnectionManager.js +58 -0
- package/lib/ConnectionManager.js.map +1 -0
- package/lib/Migrator.d.ts +1 -0
- package/lib/Migrator.d.ts.map +1 -0
- package/lib/Migrator.js +160 -0
- package/lib/Migrator.js.map +1 -0
- package/lib/PostgresErrorCodes.d.ts +1 -0
- package/lib/PostgresErrorCodes.d.ts.map +1 -0
- package/lib/PostgresErrorCodes.js +274 -0
- package/lib/PostgresErrorCodes.js.map +1 -0
- package/lib/Repositories.d.ts +1 -0
- package/lib/Repositories.d.ts.map +1 -0
- package/lib/Repositories.js +3 -0
- package/lib/Repositories.js.map +1 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.d.ts +1 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.d.ts.map +1 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.js +367 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.js.map +1 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts +1 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts.map +1 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.js +698 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.js.map +1 -0
- package/lib/dynamodb/DynamoDbManager.d.ts +1 -0
- package/lib/dynamodb/DynamoDbManager.d.ts.map +1 -0
- package/lib/dynamodb/DynamoDbManager.js +66 -0
- package/lib/dynamodb/DynamoDbManager.js.map +1 -0
- package/lib/dynamodb/getDynamoDbOptions.d.ts +1 -0
- package/lib/dynamodb/getDynamoDbOptions.d.ts.map +1 -0
- package/lib/dynamodb/getDynamoDbOptions.js +15 -0
- package/lib/dynamodb/getDynamoDbOptions.js.map +1 -0
- package/lib/error/DuplicateItemError.d.ts +1 -0
- package/lib/error/DuplicateItemError.d.ts.map +1 -0
- package/lib/error/DuplicateItemError.js +12 -0
- package/lib/error/DuplicateItemError.js.map +1 -0
- package/lib/error/InvalidDataFormatError.d.ts +1 -0
- package/lib/error/InvalidDataFormatError.d.ts.map +1 -0
- package/lib/error/InvalidDataFormatError.js +12 -0
- package/lib/error/InvalidDataFormatError.js.map +1 -0
- package/lib/error/InvalidDbSchemaError.d.ts +1 -0
- package/lib/error/InvalidDbSchemaError.d.ts.map +1 -0
- package/lib/error/InvalidDbSchemaError.js +12 -0
- package/lib/error/InvalidDbSchemaError.js.map +1 -0
- package/lib/error/MissingKeyValuesError.d.ts +1 -0
- package/lib/error/MissingKeyValuesError.d.ts.map +1 -0
- package/lib/error/MissingKeyValuesError.js +12 -0
- package/lib/error/MissingKeyValuesError.js.map +1 -0
- package/lib/error/TransactionError.d.ts +1 -0
- package/lib/error/TransactionError.d.ts.map +1 -0
- package/lib/error/TransactionError.js +12 -0
- package/lib/error/TransactionError.js.map +1 -0
- package/lib/getConnectionInfo.d.ts +1 -0
- package/lib/getConnectionInfo.d.ts.map +1 -0
- package/lib/getConnectionInfo.js +30 -0
- package/lib/getConnectionInfo.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +33 -70416
- package/lib/index.js.map +1 -7
- package/package.json +5 -5
- package/src/AbstractRepository.ts +26 -20
- package/src/dynamodb/AbstractDynamoDbRepository.ts +1 -1
- package/src/dynamodb/getDynamoDbOptions.ts +1 -1
- package/tsconfig.json +5 -2
- package/tsconfig.tsbuildinfo +1 -1
- 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
|