Skip to content

Commit 75ede4c

Browse files
committed
Add unit test for idempotency response hook.
1 parent 2f4a612 commit 75ede4c

File tree

1 file changed

+124
-50
lines changed
  • powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal

1 file changed

+124
-50
lines changed

powertools-idempotency/powertools-idempotency-core/src/test/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyAspectTest.java

Lines changed: 124 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,32 @@
1414

1515
package software.amazon.lambda.powertools.idempotency.internal;
1616

17-
import com.amazonaws.services.lambda.runtime.Context;
18-
import com.fasterxml.jackson.core.JsonProcessingException;
19-
import com.fasterxml.jackson.databind.JsonNode;
17+
import static java.time.temporal.ChronoUnit.SECONDS;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
import static org.mockito.ArgumentMatchers.any;
21+
import static org.mockito.Mockito.doReturn;
22+
import static org.mockito.Mockito.doThrow;
23+
import static org.mockito.Mockito.spy;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.verifyNoInteractions;
26+
import static org.mockito.Mockito.when;
27+
28+
import java.time.Instant;
29+
import java.util.OptionalInt;
30+
import java.util.OptionalLong;
31+
2032
import org.junit.jupiter.api.BeforeEach;
2133
import org.junit.jupiter.api.Test;
2234
import org.junitpioneer.jupiter.SetEnvironmentVariable;
2335
import org.mockito.ArgumentCaptor;
2436
import org.mockito.Mock;
2537
import org.mockito.MockitoAnnotations;
38+
39+
import com.amazonaws.services.lambda.runtime.Context;
40+
import com.fasterxml.jackson.core.JsonProcessingException;
41+
import com.fasterxml.jackson.databind.JsonNode;
42+
2643
import software.amazon.lambda.powertools.idempotency.Constants;
2744
import software.amazon.lambda.powertools.idempotency.Idempotency;
2845
import software.amazon.lambda.powertools.idempotency.IdempotencyConfig;
@@ -43,21 +60,6 @@
4360
import software.amazon.lambda.powertools.idempotency.persistence.DataRecord;
4461
import software.amazon.lambda.powertools.utilities.JsonConfig;
4562

46-
import java.time.Instant;
47-
import java.util.OptionalInt;
48-
import java.util.OptionalLong;
49-
50-
import static java.time.temporal.ChronoUnit.SECONDS;
51-
import static org.assertj.core.api.Assertions.assertThat;
52-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
53-
import static org.mockito.ArgumentMatchers.any;
54-
import static org.mockito.Mockito.doReturn;
55-
import static org.mockito.Mockito.doThrow;
56-
import static org.mockito.Mockito.spy;
57-
import static org.mockito.Mockito.verify;
58-
import static org.mockito.Mockito.verifyNoInteractions;
59-
import static org.mockito.Mockito.when;
60-
6163
public class IdempotencyAspectTest {
6264

6365
@Mock
@@ -77,8 +79,8 @@ public void firstCall_shouldPutInStore() {
7779
.withPersistenceStore(store)
7880
.withConfig(IdempotencyConfig.builder()
7981
.withEventKeyJMESPath("id")
80-
.build()
81-
).configure();
82+
.build())
83+
.configure();
8284

8385
IdempotencyEnabledFunction function = new IdempotencyEnabledFunction();
8486

@@ -103,27 +105,66 @@ public void firstCall_shouldPutInStore() {
103105
assertThat(resultCaptor.getValue()).isEqualTo(basket);
104106
}
105107

108+
@Test
109+
public void firstCall_shouldPutInStoreAndNotApplyResponseHook() {
110+
Idempotency.config()
111+
.withPersistenceStore(store)
112+
.withConfig(IdempotencyConfig.builder()
113+
.withEventKeyJMESPath("id")
114+
// This hook will add a second product to the basket. It should not run here. Only for
115+
// idempotent responses.
116+
.withResponseHook((responseData, dataRecord) -> {
117+
final Basket basket = (Basket) responseData;
118+
basket.add(new Product(42, "fake product 2", 12));
119+
return basket;
120+
})
121+
.build())
122+
.configure();
123+
124+
IdempotencyEnabledFunction function = new IdempotencyEnabledFunction();
125+
126+
when(context.getRemainingTimeInMillis()).thenReturn(30000);
127+
128+
Product p = new Product(42, "fake product", 12);
129+
Basket basket = function.handleRequest(p, context);
130+
assertThat(basket.getProducts()).hasSize(1); // Size should be 1 because response hook should not run
131+
assertThat(function.handlerCalled()).isTrue();
132+
133+
ArgumentCaptor<JsonNode> nodeCaptor = ArgumentCaptor.forClass(JsonNode.class);
134+
ArgumentCaptor<OptionalInt> expiryCaptor = ArgumentCaptor.forClass(OptionalInt.class);
135+
verify(store).saveInProgress(nodeCaptor.capture(), any(), expiryCaptor.capture());
136+
assertThat(nodeCaptor.getValue().get("id").asLong()).isEqualTo(p.getId());
137+
assertThat(nodeCaptor.getValue().get("name").asText()).isEqualTo(p.getName());
138+
assertThat(nodeCaptor.getValue().get("price").asDouble()).isEqualTo(p.getPrice());
139+
140+
assertThat(expiryCaptor.getValue().orElse(-1)).isEqualTo(30000);
141+
142+
ArgumentCaptor<Basket> resultCaptor = ArgumentCaptor.forClass(Basket.class);
143+
verify(store).saveSuccess(any(), resultCaptor.capture(), any());
144+
assertThat(resultCaptor.getValue()).isEqualTo(basket);
145+
}
146+
106147
@Test
107148
public void secondCall_notExpired_shouldGetFromStore() throws JsonProcessingException {
108149
// GIVEN
109150
Idempotency.config()
110151
.withPersistenceStore(store)
111152
.withConfig(IdempotencyConfig.builder()
112153
.withEventKeyJMESPath("id")
113-
.build()
114-
).configure();
154+
.build())
155+
.configure();
115156

116157
doThrow(IdempotencyItemAlreadyExistsException.class).when(store).saveInProgress(any(), any(), any());
117158

118159
Product p = new Product(42, "fake product", 12);
119160
Basket b = new Basket(p);
120-
DataRecord record = new DataRecord(
161+
DataRecord dr = new DataRecord(
121162
"42",
122163
DataRecord.Status.COMPLETED,
123164
Instant.now().plus(356, SECONDS).getEpochSecond(),
124165
JsonConfig.get().getObjectMapper().writer().writeValueAsString(b),
125166
null);
126-
doReturn(record).when(store).getRecord(any(), any());
167+
doReturn(dr).when(store).getRecord(any(), any());
127168

128169
// WHEN
129170
IdempotencyEnabledFunction function = new IdempotencyEnabledFunction();
@@ -141,19 +182,19 @@ public void secondCall_notExpired_shouldGetStringFromStore() {
141182
.withPersistenceStore(store)
142183
.withConfig(IdempotencyConfig.builder()
143184
.withEventKeyJMESPath("id")
144-
.build()
145-
).configure();
185+
.build())
186+
.configure();
146187

147188
doThrow(IdempotencyItemAlreadyExistsException.class).when(store).saveInProgress(any(), any(), any());
148189

149190
Product p = new Product(42, "fake product", 12);
150-
DataRecord record = new DataRecord(
191+
DataRecord dr = new DataRecord(
151192
"42",
152193
DataRecord.Status.COMPLETED,
153194
Instant.now().plus(356, SECONDS).getEpochSecond(),
154195
p.getName(),
155196
null);
156-
doReturn(record).when(store).getRecord(any(), any());
197+
doReturn(dr).when(store).getRecord(any(), any());
157198

158199
// WHEN
159200
IdempotencyStringFunction function = new IdempotencyStringFunction();
@@ -164,6 +205,41 @@ public void secondCall_notExpired_shouldGetStringFromStore() {
164205
assertThat(function.handlerCalled()).isFalse();
165206
}
166207

208+
@Test
209+
public void secondCall_notExpired_shouldGetStringFromStoreWithResponseHook() {
210+
// GIVEN
211+
final String RESPONSE_HOOK_SUFFIX = " ResponseHook";
212+
Idempotency.config()
213+
.withPersistenceStore(store)
214+
.withConfig(IdempotencyConfig.builder()
215+
.withEventKeyJMESPath("id")
216+
.withResponseHook((responseData, dataRecord) -> {
217+
responseData += RESPONSE_HOOK_SUFFIX;
218+
return responseData;
219+
})
220+
.build())
221+
.configure();
222+
223+
doThrow(IdempotencyItemAlreadyExistsException.class).when(store).saveInProgress(any(), any(), any());
224+
225+
Product p = new Product(42, "fake product", 12);
226+
DataRecord dr = new DataRecord(
227+
"42",
228+
DataRecord.Status.COMPLETED,
229+
Instant.now().plus(356, SECONDS).getEpochSecond(),
230+
p.getName(),
231+
null);
232+
doReturn(dr).when(store).getRecord(any(), any());
233+
234+
// WHEN
235+
IdempotencyStringFunction function = new IdempotencyStringFunction();
236+
String name = function.handleRequest(p, context);
237+
238+
// THEN
239+
assertThat(name).isEqualTo(p.getName() + RESPONSE_HOOK_SUFFIX);
240+
assertThat(function.handlerCalled()).isFalse();
241+
}
242+
167243
@Test
168244
public void secondCall_inProgress_shouldThrowIdempotencyAlreadyInProgressException()
169245
throws JsonProcessingException {
@@ -172,23 +248,23 @@ public void secondCall_inProgress_shouldThrowIdempotencyAlreadyInProgressExcepti
172248
.withPersistenceStore(store)
173249
.withConfig(IdempotencyConfig.builder()
174250
.withEventKeyJMESPath("id")
175-
.build()
176-
).configure();
251+
.build())
252+
.configure();
177253

178254
doThrow(IdempotencyItemAlreadyExistsException.class).when(store).saveInProgress(any(), any(), any());
179255

180256
Product p = new Product(42, "fake product", 12);
181257
Basket b = new Basket(p);
182-
OptionalLong timestampInFuture =
183-
OptionalLong.of(Instant.now().toEpochMilli() + 1000); // timeout not expired (in 1sec)
184-
DataRecord record = new DataRecord(
258+
OptionalLong timestampInFuture = OptionalLong.of(Instant.now().toEpochMilli() + 1000); // timeout not expired
259+
// (in 1sec)
260+
DataRecord dr = new DataRecord(
185261
"42",
186262
DataRecord.Status.INPROGRESS,
187263
Instant.now().plus(356, SECONDS).getEpochSecond(),
188264
JsonConfig.get().getObjectMapper().writer().writeValueAsString(b),
189265
null,
190266
timestampInFuture);
191-
doReturn(record).when(store).getRecord(any(), any());
267+
doReturn(dr).when(store).getRecord(any(), any());
192268

193269
// THEN
194270
IdempotencyEnabledFunction function = new IdempotencyEnabledFunction();
@@ -204,23 +280,23 @@ public void secondCall_inProgress_lambdaTimeout_timeoutExpired_shouldThrowIncons
204280
.withPersistenceStore(store)
205281
.withConfig(IdempotencyConfig.builder()
206282
.withEventKeyJMESPath("id")
207-
.build()
208-
).configure();
283+
.build())
284+
.configure();
209285

210286
doThrow(IdempotencyItemAlreadyExistsException.class).when(store).saveInProgress(any(), any(), any());
211287

212288
Product p = new Product(42, "fake product", 12);
213289
Basket b = new Basket(p);
214-
OptionalLong timestampInThePast =
215-
OptionalLong.of(Instant.now().toEpochMilli() - 100); // timeout expired 100ms ago
216-
DataRecord record = new DataRecord(
290+
OptionalLong timestampInThePast = OptionalLong.of(Instant.now().toEpochMilli() - 100); // timeout expired 100ms
291+
// ago
292+
DataRecord dr = new DataRecord(
217293
"42",
218294
DataRecord.Status.INPROGRESS,
219295
Instant.now().plus(356, SECONDS).getEpochSecond(),
220296
JsonConfig.get().getObjectMapper().writer().writeValueAsString(b),
221297
null,
222298
timestampInThePast);
223-
doReturn(record).when(store).getRecord(any(), any());
299+
doReturn(dr).when(store).getRecord(any(), any());
224300

225301
// THEN
226302
IdempotencyEnabledFunction function = new IdempotencyEnabledFunction();
@@ -235,8 +311,8 @@ public void functionThrowException_shouldDeleteRecord_andThrowFunctionException(
235311
.withPersistenceStore(store)
236312
.withConfig(IdempotencyConfig.builder()
237313
.withEventKeyJMESPath("id")
238-
.build()
239-
).configure();
314+
.build())
315+
.configure();
240316

241317
// WHEN / THEN
242318
IdempotencyWithErrorFunction function = new IdempotencyWithErrorFunction();
@@ -256,8 +332,8 @@ public void testIdempotencyDisabled_shouldJustRunTheFunction() {
256332
.withPersistenceStore(store)
257333
.withConfig(IdempotencyConfig.builder()
258334
.withEventKeyJMESPath("id")
259-
.build()
260-
).configure();
335+
.build())
336+
.configure();
261337

262338
// WHEN
263339
IdempotencyEnabledFunction function = new IdempotencyEnabledFunction();
@@ -340,13 +416,13 @@ public void idempotencyOnSubMethodAnnotated_secondCall_notExpired_shouldGetFromS
340416

341417
Product p = new Product(42, "fake product", 12);
342418
Basket b = new Basket(p);
343-
DataRecord record = new DataRecord(
419+
DataRecord dr = new DataRecord(
344420
"fake",
345421
DataRecord.Status.COMPLETED,
346422
Instant.now().plus(356, SECONDS).getEpochSecond(),
347423
JsonConfig.get().getObjectMapper().writer().writeValueAsString(b),
348424
null);
349-
doReturn(record).when(store).getRecord(any(), any());
425+
doReturn(dr).when(store).getRecord(any(), any());
350426

351427
// WHEN
352428
IdempotencyInternalFunction function = new IdempotencyInternalFunction(false);
@@ -405,8 +481,7 @@ public void idempotencyOnSubMethodAnnotated_keyJMESPathArray_shouldPutInStoreWit
405481
public void idempotencyOnSubMethodNotAnnotated_shouldThrowException() {
406482
Idempotency.config()
407483
.withPersistenceStore(store)
408-
.withConfig(IdempotencyConfig.builder().build()
409-
).configure();
484+
.withConfig(IdempotencyConfig.builder().build()).configure();
410485

411486
// WHEN
412487
IdempotencyInternalFunctionInvalid function = new IdempotencyInternalFunctionInvalid();
@@ -421,8 +496,7 @@ public void idempotencyOnSubMethodNotAnnotated_shouldThrowException() {
421496
public void idempotencyOnSubMethodVoid_shouldThrowException() {
422497
Idempotency.config()
423498
.withPersistenceStore(store)
424-
.withConfig(IdempotencyConfig.builder().build()
425-
).configure();
499+
.withConfig(IdempotencyConfig.builder().build()).configure();
426500

427501
// WHEN
428502
IdempotencyInternalFunctionVoid function = new IdempotencyInternalFunctionVoid();

0 commit comments

Comments
 (0)