FINERACT-2304: Create Configuration assembler and move resilience4j props into fineract namespace implement proper retry for SynchronousCommandProcessingService and BatchApiServiceImpl
This commit is contained in:
+108
@@ -21,16 +21,28 @@ package org.apache.fineract.batch.service;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.github.resilience4j.retry.Retry;
|
||||
import io.github.resilience4j.retry.RetryConfig;
|
||||
import io.github.resilience4j.retry.RetryRegistry;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.batch.command.CommandStrategy;
|
||||
import org.apache.fineract.batch.command.CommandStrategyProvider;
|
||||
import org.apache.fineract.batch.domain.BatchRequest;
|
||||
import org.apache.fineract.batch.domain.BatchResponse;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
import org.apache.fineract.commands.configuration.RetryConfigurationAssembler;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
|
||||
import org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
@@ -57,8 +69,21 @@ class BatchApiServiceImplTest {
|
||||
private CommandStrategy commandStrategy;
|
||||
@Mock
|
||||
private UriInfo uriInfo;
|
||||
@Mock
|
||||
private ErrorHandler errorHandler;
|
||||
|
||||
@Mock
|
||||
private RetryRegistry registry;
|
||||
|
||||
@Mock
|
||||
private FineractProperties fineractProperties;
|
||||
|
||||
@InjectMocks
|
||||
private RetryConfigurationAssembler retryConfigurationAssembler;
|
||||
|
||||
private final ResolutionHelper resolutionHelper = Mockito.spy(new ResolutionHelper(new FromJsonHelper()));
|
||||
private final List<BatchRequestPreprocessor> batchPreprocessors = Mockito.spy(List.of());
|
||||
|
||||
@InjectMocks
|
||||
private BatchApiServiceImpl batchApiService;
|
||||
private BatchRequest request;
|
||||
@@ -66,6 +91,8 @@ class BatchApiServiceImplTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
batchApiService = new BatchApiServiceImpl(strategyProvider, resolutionHelper, transactionManager, errorHandler, List.of(),
|
||||
batchPreprocessors, retryConfigurationAssembler);
|
||||
batchApiService.setEntityManager(entityManager);
|
||||
request = new BatchRequest();
|
||||
request.setRequestId(1L);
|
||||
@@ -75,6 +102,16 @@ class BatchApiServiceImplTest {
|
||||
response.setRequestId(1L);
|
||||
response.setStatusCode(200);
|
||||
response.setBody("Success");
|
||||
FineractProperties.RetryProperties settings = new FineractProperties.RetryProperties();
|
||||
settings.setInstances(new FineractProperties.RetryProperties.InstancesProperties());
|
||||
settings.getInstances().setExecuteCommand(new FineractProperties.RetryProperties.InstancesProperties.ExecuteCommandProperties());
|
||||
settings.getInstances().getExecuteCommand().setMaxAttempts(3);
|
||||
settings.getInstances().getExecuteCommand().setWaitDuration(Duration.ofMillis(2));
|
||||
settings.getInstances().getExecuteCommand().setEnableExponentialBackoff(false);
|
||||
settings.getInstances().getExecuteCommand().setRetryExceptions(new Class[] { RetryException.class });
|
||||
when(fineractProperties.getRetry()).thenReturn(settings);
|
||||
when(registry.retry(anyString(), any(RetryConfig.class)))
|
||||
.thenAnswer(i -> Retry.of((String) i.getArgument(0), (RetryConfig) i.getArgument(1)));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -87,6 +124,74 @@ class BatchApiServiceImplTest {
|
||||
Mockito.reset(transactionManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleBatchRequestsWithEnclosingTransactionResult200WithRetryOnTransactionFailure() {
|
||||
|
||||
List<BatchRequest> requestList = List.of(request);
|
||||
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
|
||||
when(commandStrategy.execute(any(), any())).thenReturn(response).thenReturn(response);
|
||||
// throw exception at transaction commit to verify If OptimisticLockException or similar exceptions are on the
|
||||
// retry list, they can perform a retry.
|
||||
// do nothing on 2nd hit to simulate success commit
|
||||
doThrow(new RetryException()).doNothing().when(transactionManager).commit(any());
|
||||
|
||||
// Regular transaction
|
||||
when(transactionManager.getTransaction(any()))
|
||||
.thenReturn(new DefaultTransactionStatus("txn_name", null, true, true, false, false, false, null));
|
||||
|
||||
List<BatchResponse> result = batchApiService.handleBatchRequestsWithEnclosingTransaction(requestList, uriInfo);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(200, result.getFirst().getStatusCode());
|
||||
assertTrue(result.getFirst().getBody().contains("Success"));
|
||||
|
||||
verify(transactionManager, times(2)).commit(any());
|
||||
verify(entityManager, times(2)).flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleBatchRequestsWithEnclosingTransactionResult200WithRetry() {
|
||||
|
||||
ErrorInfo errorInfo = mock(ErrorInfo.class);
|
||||
when(errorInfo.getMessage()).thenReturn("Failed");
|
||||
when(errorInfo.getStatusCode()).thenReturn(500);
|
||||
when(errorHandler.handle(any())).thenReturn(errorInfo);
|
||||
|
||||
List<BatchRequest> requestList = List.of(request);
|
||||
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
|
||||
when(commandStrategy.execute(any(), any())).thenThrow(new RetryException()).thenReturn(response);
|
||||
// Regular transaction
|
||||
when(transactionManager.getTransaction(any()))
|
||||
.thenReturn(new DefaultTransactionStatus("txn_name", null, true, true, false, false, false, null));
|
||||
List<BatchResponse> result = batchApiService.handleBatchRequestsWithEnclosingTransaction(requestList, uriInfo);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(200, result.getFirst().getStatusCode());
|
||||
assertTrue(result.getFirst().getBody().contains("Success"));
|
||||
Mockito.verify(entityManager, times(2)).flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleBatchRequestsWithEnclosingTransactionFailsWithRetry() {
|
||||
|
||||
List<BatchRequest> requestList = List.of(request);
|
||||
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
|
||||
when(commandStrategy.execute(any(), any())).thenThrow(new RetryException()).thenThrow(new RetryException())
|
||||
.thenThrow(new RetryException());
|
||||
|
||||
ErrorInfo errorInfo = mock(ErrorInfo.class);
|
||||
when(errorInfo.getMessage()).thenReturn("Failed");
|
||||
when(errorInfo.getStatusCode()).thenReturn(500);
|
||||
when(errorHandler.handle(any())).thenReturn(errorInfo);
|
||||
|
||||
// Regular transaction
|
||||
when(transactionManager.getTransaction(any()))
|
||||
.thenReturn(new DefaultTransactionStatus("txn_name", null, true, true, false, false, false, null));
|
||||
List<BatchResponse> result = batchApiService.handleBatchRequestsWithEnclosingTransaction(requestList, uriInfo);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(500, result.getFirst().getStatusCode());
|
||||
assertTrue(result.getFirst().getBody().contains("Failed"));
|
||||
Mockito.verify(entityManager, times(3)).flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleBatchRequestsWithEnclosingTransaction() {
|
||||
List<BatchRequest> requestList = List.of(request);
|
||||
@@ -116,4 +221,7 @@ class BatchApiServiceImplTest {
|
||||
assertTrue(result.get(0).getBody().contains("Success"));
|
||||
Mockito.verifyNoInteractions(entityManager);
|
||||
}
|
||||
|
||||
private static final class RetryException extends RuntimeException {}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user