4
4
* @group unit/idempotency/all
5
5
*/
6
6
import { createHash , Hash } from 'crypto' ;
7
- import { EnvironmentVariablesService } from '../../../src/EnvironmentVariablesService' ;
8
7
import { IdempotencyRecord , PersistenceLayer } from '../../../src/persistence' ;
9
8
import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus' ;
10
9
@@ -16,7 +15,10 @@ const cryptoUpdateMock = jest.fn();
16
15
const cryptoDigestMock = jest . fn ( ) ;
17
16
const mockDigest = 'hashDigest' ;
18
17
19
- describe ( 'Class: Persistence Layer' , ( ) => {
18
+ describe ( 'Class: PersistenceLayer' , ( ) => {
19
+
20
+ const dummyData = 'someData' ;
21
+ const idempotentFunctionName = 'foo' ;
20
22
21
23
const deleteRecord = jest . fn ( ) ;
22
24
const getRecord = jest . fn ( ) ;
@@ -34,102 +36,146 @@ describe('Class: Persistence Layer', ()=> {
34
36
protected _updateRecord = updateRecord ;
35
37
}
36
38
37
- describe ( 'Method: saveInProgress' , ( ) => {
38
- beforeEach ( ( ) => {
39
- putRecord . mockClear ( ) ;
40
- ( createHash as jest . MockedFunction < typeof createHash > ) . mockReturnValue (
41
- {
42
- update : cryptoUpdateMock ,
43
- digest : cryptoDigestMock . mockReturnValue ( mockDigest )
44
- } as unknown as Hash
45
- ) ;
39
+ describe ( 'Method: configure' , ( ) => {
40
+
41
+ test ( 'when called without options it maintains the default value for the key prefix' , ( ) => {
42
+
43
+ // Prepare
44
+ const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
45
+ persistenceLayer . configure ( ) ;
46
+
47
+ expect ( persistenceLayer ) . toEqual ( expect . objectContaining ( {
48
+ idempotencyKeyPrefix : process . env . AWS_LAMBDA_FUNCTION_NAME ,
49
+ } ) ) ;
50
+
46
51
} ) ;
47
52
48
- test ( 'When called, it saves an IN_PROGRESS idempotency record via _putRecord()' , async ( ) => {
49
- const data = 'someData' ;
53
+ test ( 'when called with an empty option object it maintains the default value for the key prefix' , ( ) => {
54
+
55
+ // Prepare
50
56
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
57
+ persistenceLayer . configure ( { } ) ;
51
58
52
- await persistenceLayer . saveInProgress ( data ) ;
59
+ expect ( persistenceLayer ) . toEqual ( expect . objectContaining ( {
60
+ idempotencyKeyPrefix : process . env . AWS_LAMBDA_FUNCTION_NAME ,
61
+ } ) ) ;
53
62
54
- const savedIdempotencyRecord : IdempotencyRecord = putRecord . mock . calls [ 0 ] [ 0 ] ;
55
- expect ( savedIdempotencyRecord . getStatus ( ) ) . toBe ( IdempotencyRecordStatus . INPROGRESS ) ;
56
63
} ) ;
57
64
58
- test ( 'When called, it creates an idempotency key from the function name and a digest of the md5 hash of the data' , async ( ) => {
59
- const data = 'someData' ;
60
- const lambdaFunctionName = 'LambdaName' ;
61
- jest . spyOn ( EnvironmentVariablesService . prototype , 'getLambdaFunctionName' ) . mockReturnValue ( lambdaFunctionName ) ;
65
+ test ( 'when called with an empty string as functionName it maintains the default value for the key prefix' , ( ) => {
62
66
63
- const functionName = 'functionName' ;
67
+ // Prepare
68
+ const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
69
+ persistenceLayer . configure ( { functionName : '' } ) ;
64
70
65
- const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest ;
71
+ expect ( persistenceLayer ) . toEqual ( expect . objectContaining ( {
72
+ idempotencyKeyPrefix : process . env . AWS_LAMBDA_FUNCTION_NAME ,
73
+ } ) ) ;
74
+
75
+ } ) ;
76
+
77
+ test ( 'when called with a valid functionName it concatenates the key prefix correctly' , ( ) => {
78
+
79
+ // Prepare
80
+ const expectedIdempotencyKeyPrefix = `${ process . env . AWS_LAMBDA_FUNCTION_NAME } .${ idempotentFunctionName } ` ;
66
81
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
67
- persistenceLayer . configure ( functionName ) ;
82
+ persistenceLayer . configure ( { functionName : idempotentFunctionName } ) ;
68
83
69
- await persistenceLayer . saveInProgress ( data ) ;
84
+ expect ( persistenceLayer ) . toEqual ( expect . objectContaining ( {
85
+ idempotencyKeyPrefix : expectedIdempotencyKeyPrefix
86
+ } ) ) ;
70
87
71
- const savedIdempotencyRecord : IdempotencyRecord = putRecord . mock . calls [ 0 ] [ 0 ] ;
88
+ } ) ;
72
89
73
- expect ( createHash ) . toHaveBeenCalledWith (
74
- expect . stringMatching ( 'md5' ) ,
75
- ) ;
76
- expect ( cryptoUpdateMock ) . toHaveBeenCalledWith ( expect . stringMatching ( data ) ) ;
77
- expect ( cryptoDigestMock ) . toHaveBeenCalledWith (
78
- expect . stringMatching ( 'base64' )
90
+ } ) ;
91
+
92
+ describe ( 'Method: saveInProgress' , ( ) => {
93
+
94
+ beforeEach ( ( ) => {
95
+ putRecord . mockClear ( ) ;
96
+ ( createHash as jest . MockedFunction < typeof createHash > ) . mockReturnValue (
97
+ {
98
+ update : cryptoUpdateMock ,
99
+ digest : cryptoDigestMock . mockReturnValue ( mockDigest )
100
+ } as unknown as Hash
79
101
) ;
80
- expect ( savedIdempotencyRecord . idempotencyKey ) . toEqual ( expectedIdempotencyKey ) ;
102
+
81
103
} ) ;
82
104
83
- test ( 'When called without a function name, it creates an idempotency key from the Lambda name only and a digest of the md5 hash of the data' , async ( ) => {
84
- const data = 'someData' ;
85
- const lambdaFunctionName = 'LambdaName' ;
86
- jest . spyOn ( EnvironmentVariablesService . prototype , 'getLambdaFunctionName' ) . mockReturnValue ( lambdaFunctionName ) ;
105
+ test ( 'when called, it saves an IN_PROGRESS idempotency record via _putRecord()' , async ( ) => {
87
106
88
- const expectedIdempotencyKey = lambdaFunctionName + '.' + '#' + mockDigest ;
107
+ // Prepare
89
108
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
90
- persistenceLayer . configure ( ) ;
91
109
92
- await persistenceLayer . saveInProgress ( data ) ;
110
+ // Act
111
+ await persistenceLayer . saveInProgress ( dummyData ) ;
93
112
113
+ // Assess
94
114
const savedIdempotencyRecord : IdempotencyRecord = putRecord . mock . calls [ 0 ] [ 0 ] ;
115
+ expect ( savedIdempotencyRecord . getStatus ( ) ) . toBe ( IdempotencyRecordStatus . INPROGRESS ) ;
116
+
117
+ } ) ;
95
118
119
+ test ( 'when called, it creates an idempotency key from the function name and a digest of the md5 hash of the data' , async ( ) => {
120
+
121
+ // Prepare
122
+ const expectedIdempotencyKey = `${ process . env . AWS_LAMBDA_FUNCTION_NAME } .${ idempotentFunctionName } #${ mockDigest } ` ;
123
+ const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
124
+ persistenceLayer . configure ( { functionName : idempotentFunctionName } ) ;
125
+
126
+ // Act
127
+ await persistenceLayer . saveInProgress ( dummyData ) ;
128
+
129
+ // Assess
130
+ const savedIdempotencyRecord : IdempotencyRecord = putRecord . mock . calls [ 0 ] [ 0 ] ;
96
131
expect ( createHash ) . toHaveBeenCalledWith (
97
132
expect . stringMatching ( 'md5' ) ,
98
133
) ;
99
- expect ( cryptoUpdateMock ) . toHaveBeenCalledWith ( expect . stringMatching ( data ) ) ;
134
+ expect ( cryptoUpdateMock ) . toHaveBeenCalledWith ( expect . stringMatching ( dummyData ) ) ;
100
135
expect ( cryptoDigestMock ) . toHaveBeenCalledWith (
101
136
expect . stringMatching ( 'base64' )
102
137
) ;
103
138
expect ( savedIdempotencyRecord . idempotencyKey ) . toEqual ( expectedIdempotencyKey ) ;
139
+
104
140
} ) ;
105
141
106
- test ( 'When called, it sets the expiry timestamp to one hour in the future' , async ( ) => {
142
+ test ( 'when called, it sets the expiry timestamp to one hour in the future' , async ( ) => {
143
+
144
+ // Prepare
107
145
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
108
- const data = 'someData' ;
109
146
const currentMillisTime = 3000 ;
110
147
const currentSeconds = currentMillisTime / 1000 ;
111
148
const oneHourSeconds = 60 * 60 ;
112
149
jest . spyOn ( Date , 'now' ) . mockReturnValue ( currentMillisTime ) ;
113
150
114
- await persistenceLayer . saveInProgress ( data ) ;
151
+ // Act
152
+ await persistenceLayer . saveInProgress ( dummyData ) ;
115
153
154
+ // Assess
116
155
const savedIdempotencyRecord : IdempotencyRecord = putRecord . mock . calls [ 0 ] [ 0 ] ;
117
156
expect ( savedIdempotencyRecord . expiryTimestamp ) . toEqual ( currentSeconds + oneHourSeconds ) ;
118
157
119
158
} ) ;
120
159
121
- test ( 'When called without data, it logs a warning' , async ( ) => {
122
- const consoleSpy = jest . spyOn ( console , 'warn' ) ;
160
+ test ( 'when called without data, it logs a warning' , async ( ) => {
161
+
162
+ // Prepare
163
+ const consoleSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => ( { } ) ) ;
123
164
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
124
165
166
+ // Act
125
167
await persistenceLayer . saveInProgress ( '' ) ;
168
+
169
+ // Assess
126
170
expect ( consoleSpy ) . toHaveBeenCalled ( ) ;
171
+
127
172
} ) ;
128
173
129
174
} ) ;
130
175
131
- describe ( 'Method: saveSuccess' , ( ) => {
132
- beforeEach ( ( ) => {
176
+ describe ( 'Method: saveSuccess' , ( ) => {
177
+
178
+ beforeEach ( ( ) => {
133
179
updateRecord . mockClear ( ) ;
134
180
( createHash as jest . MockedFunction < typeof createHash > ) . mockReturnValue (
135
181
{
@@ -139,64 +185,69 @@ describe('Class: Persistence Layer', ()=> {
139
185
) ;
140
186
} ) ;
141
187
142
- test ( 'When called, it updates the idempotency record status to COMPLETED' , async ( ) => {
143
- const data = 'someData' ;
188
+ test ( 'when called, it updates the idempotency record status to COMPLETED' , async ( ) => {
189
+
190
+ // Prepare
144
191
const result = { } ;
145
192
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
146
193
147
- await persistenceLayer . saveSuccess ( data , result ) ;
194
+ // Act
195
+ await persistenceLayer . saveSuccess ( dummyData , result ) ;
148
196
197
+ // Assess
149
198
const savedIdempotencyRecord : IdempotencyRecord = updateRecord . mock . calls [ 0 ] [ 0 ] ;
150
199
expect ( savedIdempotencyRecord . getStatus ( ) ) . toBe ( IdempotencyRecordStatus . COMPLETED ) ;
151
200
152
201
} ) ;
153
202
154
- test ( 'When called, it generates the idempotency key from the function name and a digest of the md5 hash of the data' , async ( ) => {
155
- const data = 'someData' ;
156
- const result = { } ;
157
- const lambdaFunctionName = 'LambdaName' ;
158
- jest . spyOn ( EnvironmentVariablesService . prototype , 'getLambdaFunctionName' ) . mockReturnValue ( lambdaFunctionName ) ;
159
-
160
- const functionName = 'functionName' ;
203
+ test ( 'when called, it generates the idempotency key from the function name and a digest of the md5 hash of the data' , async ( ) => {
161
204
162
- const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest ;
205
+ // Prepare
206
+ const result = { } ;
207
+ const expectedIdempotencyKey = `${ process . env . AWS_LAMBDA_FUNCTION_NAME } .${ idempotentFunctionName } #${ mockDigest } ` ;
163
208
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
164
- persistenceLayer . configure ( functionName ) ;
209
+ persistenceLayer . configure ( { functionName : idempotentFunctionName } ) ;
165
210
166
- await persistenceLayer . saveSuccess ( data , result ) ;
211
+ // Act
212
+ await persistenceLayer . saveSuccess ( dummyData , result ) ;
167
213
214
+ // Assess
168
215
const savedIdempotencyRecord : IdempotencyRecord = updateRecord . mock . calls [ 0 ] [ 0 ] ;
169
-
170
216
expect ( createHash ) . toHaveBeenCalledWith (
171
217
expect . stringMatching ( 'md5' ) ,
172
218
) ;
173
- expect ( cryptoUpdateMock ) . toHaveBeenCalledWith ( expect . stringMatching ( data ) ) ;
219
+ expect ( cryptoUpdateMock ) . toHaveBeenCalledWith ( expect . stringMatching ( dummyData ) ) ;
174
220
expect ( cryptoDigestMock ) . toHaveBeenCalledWith (
175
221
expect . stringMatching ( 'base64' )
176
222
) ;
177
223
expect ( savedIdempotencyRecord . idempotencyKey ) . toEqual ( expectedIdempotencyKey ) ;
224
+
178
225
} ) ;
179
226
180
- test ( 'When called, it sets the expiry timestamp to one hour in the future' , async ( ) => {
227
+ test ( 'when called, it sets the expiry timestamp to one hour in the future' , async ( ) => {
228
+
229
+ // Prepare
181
230
const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
182
- const data = 'someData' ;
183
231
const result = { } ;
184
232
const currentMillisTime = 3000 ;
185
233
const currentSeconds = currentMillisTime / 1000 ;
186
234
const oneHourSeconds = 60 * 60 ;
187
235
jest . spyOn ( Date , 'now' ) . mockReturnValue ( currentMillisTime ) ;
188
236
189
- await persistenceLayer . saveSuccess ( data , result ) ;
237
+ // Act
238
+ await persistenceLayer . saveSuccess ( dummyData , result ) ;
190
239
240
+ // Assess
191
241
const savedIdempotencyRecord : IdempotencyRecord = updateRecord . mock . calls [ 0 ] [ 0 ] ;
192
242
expect ( savedIdempotencyRecord . expiryTimestamp ) . toEqual ( currentSeconds + oneHourSeconds ) ;
193
243
194
244
} ) ;
195
245
196
246
} ) ;
197
247
198
- describe ( 'Method: getRecord' , ( ) => {
199
- beforeEach ( ( ) => {
248
+ describe ( 'Method: getRecord' , ( ) => {
249
+
250
+ beforeEach ( ( ) => {
200
251
putRecord . mockClear ( ) ;
201
252
( createHash as jest . MockedFunction < typeof createHash > ) . mockReturnValue (
202
253
{
@@ -205,24 +256,26 @@ describe('Class: Persistence Layer', ()=> {
205
256
} as unknown as Hash
206
257
) ;
207
258
} ) ;
208
- test ( 'When called, it gets the record for the idempotency key for the data passed in' , ( ) => {
209
- const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
210
- const data = 'someData' ;
211
- const lambdaFunctionName = 'LambdaName' ;
212
- jest . spyOn ( EnvironmentVariablesService . prototype , 'getLambdaFunctionName' ) . mockReturnValue ( lambdaFunctionName ) ;
213
259
214
- const functionName = 'functionName' ;
215
- const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest ;
216
- persistenceLayer . configure ( functionName ) ;
260
+ test ( 'when called, it gets the record for the idempotency key for the data passed in' , ( ) => {
217
261
218
- persistenceLayer . getRecord ( data ) ;
262
+ // Prepare
263
+ const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
264
+ const expectedIdempotencyKey = `${ process . env . AWS_LAMBDA_FUNCTION_NAME } .${ idempotentFunctionName } #${ mockDigest } ` ;
265
+ persistenceLayer . configure ( { functionName : idempotentFunctionName } ) ;
266
+
267
+ // Act
268
+ persistenceLayer . getRecord ( dummyData ) ;
219
269
270
+ // Assess
220
271
expect ( getRecord ) . toHaveBeenCalledWith ( expectedIdempotencyKey ) ;
272
+
221
273
} ) ;
222
274
} ) ;
223
275
224
- describe ( 'Method: deleteRecord' , ( ) => {
225
- beforeEach ( ( ) => {
276
+ describe ( 'Method: deleteRecord' , ( ) => {
277
+
278
+ beforeEach ( ( ) => {
226
279
putRecord . mockClear ( ) ;
227
280
( createHash as jest . MockedFunction < typeof createHash > ) . mockReturnValue (
228
281
{
@@ -232,20 +285,20 @@ describe('Class: Persistence Layer', ()=> {
232
285
) ;
233
286
} ) ;
234
287
235
- test ( 'When called, it deletes the record with the idempotency key for the data passed in' , ( ) => {
236
- const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
237
- const data = 'someData' ;
238
- const lambdaFunctionName = 'LambdaName' ;
239
- jest . spyOn ( EnvironmentVariablesService . prototype , 'getLambdaFunctionName' ) . mockReturnValue ( lambdaFunctionName ) ;
288
+ test ( 'when called, it deletes the record with the idempotency key for the data passed in' , ( ) => {
240
289
241
- const functionName = 'functionName' ;
242
- const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest ;
243
- persistenceLayer . configure ( functionName ) ;
290
+ // Prepare
291
+ const persistenceLayer : PersistenceLayer = new PersistenceLayerTestClass ( ) ;
292
+ const expectedIdempotencyKey = `${ process . env . AWS_LAMBDA_FUNCTION_NAME } .${ idempotentFunctionName } #${ mockDigest } ` ;
293
+ persistenceLayer . configure ( { functionName : idempotentFunctionName } ) ;
244
294
245
- persistenceLayer . deleteRecord ( data ) ;
295
+ // Act
296
+ persistenceLayer . deleteRecord ( dummyData ) ;
297
+
298
+ // Assess
246
299
const deletedIdempotencyRecord : IdempotencyRecord = deleteRecord . mock . calls [ 0 ] [ 0 ] ;
247
-
248
300
expect ( deletedIdempotencyRecord . idempotencyKey ) . toEqual ( expectedIdempotencyKey ) ;
301
+
249
302
} ) ;
250
303
} ) ;
251
304
} ) ;
0 commit comments