1
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb' ;
2
+ import { DynamoDBProvider } from '../../src/DynamoDBProvider' ;
3
+ import { marshall } from '@aws-sdk/util-dynamodb' ;
4
+ import { toBase64 } from '@aws-sdk/util-base64-node' ;
5
+
6
+ const clientSpy = jest . spyOn ( DynamoDBClient . prototype , 'send' ) ;
7
+
8
+ describe ( 'Class: DynamoDBProvider' , ( ) => {
9
+
10
+ beforeEach ( ( ) => {
11
+ jest . clearAllMocks ( ) ;
12
+ } ) ;
13
+
14
+ describe ( 'Method: init' , ( ) => {
15
+
16
+ test ( 'when initialized, it sets the tableName correctly' , ( ) => {
17
+
18
+ // Prepare
19
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
20
+
21
+ // Act / Assess
22
+ expect ( provider . tableName ) . toEqual ( 'TestTable' ) ;
23
+
24
+ } ) ;
25
+
26
+ test ( 'when initialized, and no attributes are passed, it uses the default values' , ( ) => {
27
+
28
+ // Prepare
29
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
30
+
31
+ // Act / Assess
32
+ expect ( provider . keyAttr ) . toEqual ( 'id' ) ;
33
+ expect ( provider . sortAttr ) . toEqual ( 'sk' ) ;
34
+ expect ( provider . valueAttr ) . toEqual ( 'value' ) ;
35
+
36
+ } ) ;
37
+
38
+ test ( 'when initialized, and ALL attributes are passed, it overwrites ALL the default values' , ( ) => {
39
+
40
+ // Prepare
41
+ const provider = new DynamoDBProvider ( 'TestTable' , {
42
+ keyAttr : 'foo' ,
43
+ sortAttr : 'bar' ,
44
+ valueAttr : 'baz'
45
+ } ) ;
46
+
47
+ // Act / Assess
48
+ expect ( provider . keyAttr ) . toEqual ( 'foo' ) ;
49
+ expect ( provider . sortAttr ) . toEqual ( 'bar' ) ;
50
+ expect ( provider . valueAttr ) . toEqual ( 'baz' ) ;
51
+
52
+ } ) ;
53
+
54
+ test ( 'when initialized, and SOME attributes are passed, it PARTIALLY overwrites the correct default values' , ( ) => {
55
+
56
+ // Prepare
57
+ const provider = new DynamoDBProvider ( 'TestTable' , {
58
+ sortAttr : 'bar' ,
59
+ } ) ;
60
+
61
+ // Act / Assess
62
+ expect ( provider . keyAttr ) . toEqual ( 'id' ) ;
63
+ expect ( provider . sortAttr ) . toEqual ( 'bar' ) ;
64
+ expect ( provider . valueAttr ) . toEqual ( 'value' ) ;
65
+
66
+ } ) ;
67
+
68
+ } ) ;
69
+
70
+ describe ( 'Method: get' , ( ) => {
71
+
72
+ test ( 'when called, and the cache is empty, it returns a value from remote' , async ( ) => {
73
+
74
+ // Prepare
75
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
76
+ clientSpy . mockImplementation ( ( ) => ( { 'Item' : { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'value' : 'foo' } ) } } ) ) ;
77
+
78
+ // Act
79
+ const value = await provider . get ( 'my-parameter' ) ;
80
+
81
+ // Assess
82
+ expect ( value ) . toEqual ( 'foo' ) ;
83
+ expect ( clientSpy ) . toBeCalledWith ( expect . objectContaining ( {
84
+ input : expect . objectContaining ( {
85
+ TableName : 'TestTable' ,
86
+ Key : {
87
+ 'id' : {
88
+ S : 'my-parameter'
89
+ }
90
+ }
91
+ } )
92
+ } ) ) ;
93
+
94
+ } ) ;
95
+
96
+ test ( 'when called, and a non-expired value exists in the cache, it returns it' , async ( ) => {
97
+
98
+ // Prepare
99
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
100
+ clientSpy . mockImplementation ( ( ) => ( { 'Item' : { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'value' : 'foo' } ) } } ) ) ;
101
+ const ttl = new Date ( ) ;
102
+ provider . store . set ( [ 'my-parameter' , undefined ] . toString ( ) , { value : 'bar' , ttl : ttl . setSeconds ( ttl . getSeconds ( ) + 600 ) } ) ;
103
+
104
+ // Act
105
+ const value = await provider . get ( 'my-parameter' ) ;
106
+
107
+ // Assess
108
+ expect ( value ) . toEqual ( 'bar' ) ;
109
+ expect ( clientSpy ) . toBeCalledTimes ( 0 ) ;
110
+
111
+ } ) ;
112
+
113
+ test ( 'when called, and an expired value exists in the cache, it returns a value from remote' , async ( ) => {
114
+
115
+ // Prepare
116
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
117
+ clientSpy . mockImplementation ( ( ) => ( { 'Item' : { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'value' : 'foo' } ) } } ) ) ;
118
+ const ttl = new Date ( ) ;
119
+ provider . store . set ( [ 'my-parameter' , undefined ] . toString ( ) , { value : 'bar' , ttl : ttl . setSeconds ( ttl . getSeconds ( ) - 600 ) } ) ;
120
+
121
+ // Act
122
+ const value = await provider . get ( 'my-parameter' ) ;
123
+
124
+ // Assess
125
+ expect ( value ) . toEqual ( 'foo' ) ;
126
+ expect ( clientSpy ) . toBeCalledTimes ( 1 ) ;
127
+
128
+ } ) ;
129
+
130
+ test ( 'when called with custom sdkOptions, it uses them, and it returns a value from remote' , async ( ) => {
131
+
132
+ // Prepare
133
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
134
+ clientSpy . mockImplementation ( ( ) => ( { 'Item' : { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'value' : 'foo' } ) } } ) ) ;
135
+
136
+ // Act
137
+ const value = await provider . get ( 'my-parameter' , { sdkOptions : { Limit : 1 } } ) ;
138
+
139
+ // Assess
140
+ expect ( value ) . toEqual ( 'foo' ) ;
141
+ expect ( clientSpy ) . toBeCalledWith ( expect . objectContaining ( {
142
+ input : expect . objectContaining ( {
143
+ Limit : 1
144
+ } )
145
+ } ) ) ;
146
+
147
+ } ) ;
148
+
149
+ test ( 'when called with custom sdkOptions that should be overwritten, it use the correct ones, and it returns a value from remote' , async ( ) => {
150
+
151
+ // Prepare
152
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
153
+ clientSpy . mockImplementation ( ( ) => ( { 'Item' : { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'value' : 'foo' } ) } } ) ) ;
154
+
155
+ // Act
156
+ const value = await provider . get ( 'my-parameter' , { sdkOptions : { TableName : 'THIS_SHOULD_BE_OVERWRITTEN' , Limit : 1 } } ) ;
157
+
158
+ // Assess
159
+ expect ( value ) . toEqual ( 'foo' ) ;
160
+ expect ( clientSpy ) . toBeCalledWith ( expect . objectContaining ( {
161
+ input : expect . objectContaining ( {
162
+ TableName : 'TestTable' ,
163
+ Limit : 1
164
+ } )
165
+ } ) ) ;
166
+
167
+ } ) ;
168
+
169
+ test ( 'when called, and the parameter DOES NOT exist, it returns undefined' , async ( ) => {
170
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
171
+ clientSpy . mockImplementation ( ( ) => ( { } ) ) ;
172
+
173
+ // Act
174
+ const value = await provider . get ( 'my-parameter' ) ;
175
+
176
+ // Assess
177
+ expect ( value ) . toBeUndefined ( ) ;
178
+ expect ( clientSpy ) . toBeCalledTimes ( 1 ) ;
179
+ } ) ;
180
+
181
+ } ) ;
182
+
183
+ describe ( 'Method: getMultiple' , ( ) => {
184
+
185
+ test ( 'when called, and the cache is empty, it returns a values from remote' , async ( ) => {
186
+
187
+ // Prepare
188
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
189
+ clientSpy . mockImplementation ( ( ) => ( { 'Items' : [
190
+ { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'sk' : 'param-a' } ) , ...marshall ( { 'value' : 'foo' } ) } ,
191
+ { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'sk' : 'param-b' } ) , ...marshall ( { 'value' : 'bar' } ) }
192
+ ] } ) ) ;
193
+
194
+ // Act
195
+ const value = await provider . getMultiple ( 'my-path' ) ;
196
+
197
+ // Assess
198
+ expect ( value ) . toEqual ( {
199
+ 'param-a' : 'foo' ,
200
+ 'param-b' : 'bar'
201
+ } ) ;
202
+ expect ( clientSpy ) . toBeCalledWith ( expect . objectContaining ( {
203
+ input : expect . objectContaining ( {
204
+ TableName : 'TestTable' ,
205
+ ExpressionAttributeNames : {
206
+ '#keyAttr' : 'id'
207
+ } ,
208
+ ExpressionAttributeValues : {
209
+ ':path' : {
210
+ S : 'my-path'
211
+ }
212
+ }
213
+ } )
214
+ } ) ) ;
215
+
216
+ } ) ;
217
+
218
+ test ( 'when called with transform auto, it returns all the values transformed correctly ' , async ( ) => {
219
+
220
+ // Prepare
221
+ const encoder = new TextEncoder ( ) ;
222
+ const mockData = JSON . stringify ( { foo : 'bar' } ) ;
223
+ const mockBinary = toBase64 ( encoder . encode ( 'my-value' ) ) ;
224
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
225
+ clientSpy . mockImplementation ( ( ) => ( { 'Items' : [
226
+ { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'sk' : 'param-a.json' } ) , ...marshall ( { 'value' : mockData } ) } ,
227
+ { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'sk' : 'param-b.binary' } ) , ...marshall ( { 'value' : mockBinary } ) }
228
+ ] } ) ) ;
229
+
230
+ // Act
231
+ const value = await provider . getMultiple ( 'my-path' , { transform : 'auto' } ) ;
232
+
233
+ // Assess
234
+ expect ( value ) . toEqual ( {
235
+ 'param-a.json' : { foo : 'bar' } ,
236
+ 'param-b.binary' : 'my-value'
237
+ } ) ;
238
+
239
+ } ) ;
240
+
241
+ test ( 'when called, and NOT all the parameters are retrieved in a single request, it keeps querying until it returns all the values' , async ( ) => {
242
+
243
+ // Prepare
244
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
245
+ clientSpy
246
+ . mockImplementationOnce ( ( ) => ( {
247
+ 'Items' : [
248
+ { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'sk' : 'param-a' } ) , ...marshall ( { 'value' : 'foo' } ) } ,
249
+ ] ,
250
+ 'LastEvaluatedKey' : { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'sk' : 'param-a' } ) , ...marshall ( { 'value' : 'foo' } ) }
251
+ } ) )
252
+ . mockImplementationOnce ( ( ) => ( {
253
+ 'Items' : [
254
+ { ...marshall ( { 'id' : 'my-parameter' } ) , ...marshall ( { 'sk' : 'param-b' } ) , ...marshall ( { 'value' : 'bar' } ) }
255
+ ]
256
+ } ) ) ;
257
+
258
+ // Act
259
+ const value = await provider . getMultiple ( 'my-path' ) ;
260
+
261
+ // Assess
262
+ expect ( value ) . toEqual ( {
263
+ 'param-a' : 'foo' ,
264
+ 'param-b' : 'bar'
265
+ } ) ;
266
+ expect ( clientSpy ) . toBeCalledTimes ( 2 ) ;
267
+ // First Call
268
+ expect ( clientSpy ) . toBeCalledWith ( expect . objectContaining ( {
269
+ input : expect . objectContaining ( {
270
+ TableName : 'TestTable' ,
271
+ ExpressionAttributeNames : {
272
+ '#keyAttr' : 'id'
273
+ } ,
274
+ ExpressionAttributeValues : {
275
+ ':path' : {
276
+ S : 'my-path'
277
+ }
278
+ }
279
+ } )
280
+ } ) ) ;
281
+ // Second Call
282
+ expect ( clientSpy ) . toBeCalledWith ( expect . objectContaining ( {
283
+ input : expect . objectContaining ( {
284
+ TableName : 'TestTable' ,
285
+ ExpressionAttributeNames : {
286
+ '#keyAttr' : 'id'
287
+ } ,
288
+ ExpressionAttributeValues : {
289
+ ':path' : {
290
+ S : 'my-path'
291
+ }
292
+ } ,
293
+ ExclusiveStartKey : {
294
+ 'id' : {
295
+ S : 'my-parameter'
296
+ } ,
297
+ 'sk' : {
298
+ S : 'param-a'
299
+ } ,
300
+ 'value' : {
301
+ S : 'foo'
302
+ }
303
+ }
304
+ } )
305
+ } ) ) ;
306
+
307
+ } ) ;
308
+
309
+ test ( 'when called, and NO results are returned, it returns an empty object' , async ( ) => {
310
+
311
+ // Prepare
312
+ const provider = new DynamoDBProvider ( 'TestTable' ) ;
313
+ clientSpy
314
+ . mockImplementationOnce ( ( ) => ( {
315
+ 'Items' : [ ] ,
316
+ 'Count' : 0
317
+ } ) ) ;
318
+
319
+ // Act
320
+ const value = await provider . getMultiple ( 'my-path' ) ;
321
+
322
+ // Assess
323
+ expect ( value ) . toEqual ( { } ) ;
324
+ expect ( clientSpy ) . toBeCalledTimes ( 1 ) ;
325
+
326
+ } ) ;
327
+
328
+ } ) ;
329
+
330
+ } ) ;
0 commit comments