Batch API extensions and a couple of OpenAPI fixes + minor datatable fixes

This commit is contained in:
Arnold Galovics
2022-07-13 21:50:42 +02:00
committed by Arnold Gálovics
parent 4f832f9481
commit 8c023e74a8
30 changed files with 1084 additions and 529 deletions
@@ -32,10 +32,11 @@ import org.apache.fineract.batch.command.internal.ApproveLoanRescheduleCommandSt
import org.apache.fineract.batch.command.internal.CollectChargesCommandStrategy;
import org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy;
import org.apache.fineract.batch.command.internal.CreateClientCommandStrategy;
import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy;
import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy;
import org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdCommandStrategy;
import org.apache.fineract.batch.command.internal.GetLoanByIdCommandStrategy;
import org.apache.fineract.batch.command.internal.GetTransactionByIdCommandStrategy;
import org.apache.fineract.batch.command.internal.RepayLoanCommandStrategy;
import org.apache.fineract.batch.command.internal.UnknownCommandStrategy;
import org.apache.fineract.batch.command.internal.UpdateClientCommandStrategy;
import org.junit.jupiter.params.ParameterizedTest;
@@ -66,8 +67,10 @@ public class CommandStrategyProviderTest {
Arguments.of("loans/123/charges", HttpMethod.POST, "createChargeCommandStrategy", mock(CreateChargeCommandStrategy.class)),
Arguments.of("loans/123/charges", HttpMethod.GET, "collectChargesCommandStrategy",
mock(CollectChargesCommandStrategy.class)),
Arguments.of("loans/123/transactions?command=repayment", HttpMethod.POST, "repayLoanCommandStrategy",
mock(RepayLoanCommandStrategy.class)),
Arguments.of("loans/123/transactions?command=repayment", HttpMethod.POST, "createTransactionLoanCommandStrategy",
mock(CreateTransactionLoanCommandStrategy.class)),
Arguments.of("loans/123/transactions?command=creditBalanceRefund", HttpMethod.POST, "createTransactionLoanCommandStrategy",
mock(CreateTransactionLoanCommandStrategy.class)),
Arguments.of("clients/456?command=activate", HttpMethod.POST, "activateClientCommandStrategy",
mock(ActivateClientCommandStrategy.class)),
Arguments.of("loans/123?command=approve", HttpMethod.POST, "approveLoanCommandStrategy",
@@ -77,7 +80,11 @@ public class CommandStrategyProviderTest {
Arguments.of("rescheduleloans/123?command=approve", HttpMethod.POST, "approveLoanRescheduleCommandStrategy",
mock(ApproveLoanRescheduleCommandStrategy.class)),
Arguments.of("loans/123/transactions/123", HttpMethod.GET, "getTransactionByIdCommandStrategy",
mock(GetTransactionByIdCommandStrategy.class)));
mock(GetTransactionByIdCommandStrategy.class)),
Arguments.of("datatables/test_dt_table/123", HttpMethod.GET, "getDatatableEntryByAppTableIdCommandStrategy",
mock(GetDatatableEntryByAppTableIdCommandStrategy.class)),
Arguments.of("datatables/test_dt_table/123?genericResultSet=true", HttpMethod.GET,
"getDatatableEntryByAppTableIdCommandStrategy", mock(GetDatatableEntryByAppTableIdCommandStrategy.class)));
}
/**
@@ -99,7 +106,6 @@ public class CommandStrategyProviderTest {
final ApplicationContext applicationContext = mock(ApplicationContext.class);
final CommandStrategyProvider commandStrategyProvider = new CommandStrategyProvider(applicationContext);
when(applicationContext.getBean(beanName)).thenReturn(commandStrategy);
final CommandStrategy result = commandStrategyProvider.getCommandStrategy(CommandContext.resource(url).method(httpMethod).build());
assertEquals(commandStrategy, result);
}
@@ -111,7 +117,9 @@ public class CommandStrategyProviderTest {
*/
private static Stream<Arguments> provideCommandStrategyResourceDetailsForErrors() {
return Stream.of(Arguments.of("loans/123?command=reject", HttpMethod.POST),
Arguments.of("loans/glimAccount/746?command=approve", HttpMethod.POST), Arguments.of("loans/123", HttpMethod.PUT));
Arguments.of("loans/glimAccount/746?command=approve", HttpMethod.POST), Arguments.of("loans/123", HttpMethod.PUT),
Arguments.of("datatables/test_dt_table", HttpMethod.GET), Arguments.of("datatables", HttpMethod.GET));
}
/**
@@ -0,0 +1,119 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.batch.command.internal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.fineract.batch.domain.BatchRequest;
import org.apache.fineract.batch.domain.BatchResponse;
import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Test class fpr {@link CreateTransactionLoanCommandStrategy}.
*/
public class CreateTransactionLoanCommandStrategyTest {
/**
* Test {@link CreateTransactionLoanCommandStrategy#execute} happy path scenario.
*/
@Test
public void testExecuteSuccessScenario() {
final TestContext testContext = new TestContext();
final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final String command = "myCommand";
final BatchRequest batchRequest = getBatchRequest(loanId, command);
final String responseBody = "myResponseBody";
when(testContext.loanTransactionsApiResource.executeLoanTransaction(loanId, command, batchRequest.getBody()))
.thenReturn(responseBody);
BatchResponse batchResponse = testContext.subjectToTest.execute(batchRequest, testContext.uriInfo);
assertEquals(HttpStatus.SC_OK, batchResponse.getStatusCode());
assertSame(responseBody, batchResponse.getBody());
assertEquals(batchRequest.getRequestId(), batchResponse.getRequestId());
assertEquals(batchRequest.getHeaders(), batchResponse.getHeaders());
verify(testContext.loanTransactionsApiResource).executeLoanTransaction(loanId, command, batchRequest.getBody());
}
/**
* Creates and returns a request with the given loan id and command value.
*
* @param loanId
* the loan id
* @param command
* the transaction id
* @return BatchRequest
*/
private BatchRequest getBatchRequest(final Long loanId, final String command) {
final BatchRequest br = new BatchRequest();
String relativeUrl = "loans/" + loanId + "/transactions?command=" + command;
br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5)));
br.setRelativeUrl(relativeUrl);
br.setMethod(HttpMethod.POST);
br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5)));
br.setBody("{}");
return br;
}
/**
* Private test context class used since testng runs in parallel to avoid state between tests
*/
private static class TestContext {
/**
* Mock URI info.
*/
@Mock
private UriInfo uriInfo;
/**
* Mock loan transactions API resource.
*/
@Mock
private LoanTransactionsApiResource loanTransactionsApiResource;
/**
* The {@link CreateTransactionLoanCommandStrategy} under test.
*/
private final CreateTransactionLoanCommandStrategy subjectToTest;
/**
* Constructor.
*/
TestContext() {
MockitoAnnotations.openMocks(this);
subjectToTest = new CreateTransactionLoanCommandStrategy(loanTransactionsApiResource);
}
}
}
@@ -0,0 +1,160 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.batch.command.internal;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import java.util.List;
import java.util.stream.Stream;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang3.RandomUtils;
import org.apache.fineract.batch.domain.BatchRequest;
import org.apache.fineract.batch.domain.BatchResponse;
import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
import org.apache.fineract.infrastructure.dataqueries.api.DatatablesApiResource;
import org.apache.http.HttpStatus;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests {GetDatatableEntryByAppTableIdCommandStrategy}.
*/
public class GetDatatableEntryByAppTableIdCommandStrategyTest {
/**
* Query parameter provider.
*
* @return the test data stream
*/
private static Stream<Arguments> provideQueryParameters() {
return Stream.of(Arguments.of(null, 0), Arguments.of("genericResultSet=true", 1));
}
/**
* Test {@link GetDatatableEntryByAppTableIdCommandStrategy#execute} happy path scenario.
*
* @param queryParameter
* the query parameter
* @param numberOfQueryParams
* number of query params are provided
*/
@ParameterizedTest
@MethodSource("provideQueryParameters")
public void testExecuteSuccessScenario(final String queryParameter, final int numberOfQueryParams) {
final TestContext testContext = new TestContext();
final Long loanId = RandomUtils.nextLong();
final String datatableName = "dt_loan_xyz";
final BatchRequest request = getBatchRequest(loanId, queryParameter, datatableName);
final String responseBody = "{\\\"columnHeaders\\\":[{}],\\\"data\\\":\\\"{}\\\"}";
given(testContext.dataTableApiResource.getDatatable(eq(datatableName), eq(loanId), eq(null), any(UriInfo.class)))
.willReturn(responseBody);
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
assertEquals(HttpStatus.SC_OK, response.getStatusCode());
assertEquals(request.getRequestId(), response.getRequestId());
assertEquals(request.getHeaders(), response.getHeaders());
assertEquals(responseBody, response.getBody());
verify(testContext.dataTableApiResource).getDatatable(eq(datatableName), eq(loanId), eq(null), testContext.uriInfoCaptor.capture());
MutableUriInfo mutableUriInfo = testContext.uriInfoCaptor.getValue();
assertThat(mutableUriInfo.getAdditionalQueryParameters()).hasSize(numberOfQueryParams);
if (numberOfQueryParams > 0) {
List<String> param = mutableUriInfo.getAdditionalQueryParameters().get("genericResultSet");
assertEquals(param.get(0), "true");
}
}
/**
* Creates and returns a request with the given loan id.
*
* @param loanId
* the loan id
* @param queryParameter
* the query parameter
* @param datatableName
* the datatable name
* @return the {@link BatchRequest}
*/
private BatchRequest getBatchRequest(final Long loanId, final String queryParameter, final String datatableName) {
final BatchRequest br = new BatchRequest();
String relativeUrl = String.format("datatables/%s/%s", datatableName, loanId);
if (queryParameter != null) {
relativeUrl = relativeUrl + "?" + queryParameter;
}
br.setRequestId(RandomUtils.nextLong());
br.setRelativeUrl(relativeUrl);
br.setMethod(HttpMethod.GET);
br.setReference(RandomUtils.nextLong());
br.setBody("{}");
return br;
}
/**
* Private test context class used since testng runs in parallel to avoid state between tests
*/
private static final class TestContext {
/**
* The subject under test.
*/
private final GetDatatableEntryByAppTableIdCommandStrategy subjectToTest;
/**
* Mock of {@link UriInfo}
*/
@Mock
private UriInfo uriInfo;
/**
* Captor of {@link MutableUriInfo}.
*/
@Captor
private ArgumentCaptor<MutableUriInfo> uriInfoCaptor;
/**
* {@link DatatablesApiResource} mock.
*/
@Mock
private DatatablesApiResource dataTableApiResource;
/**
* Constructor.
*/
private TestContext() {
MockitoAnnotations.openMocks(this);
subjectToTest = new GetDatatableEntryByAppTableIdCommandStrategy(dataTableApiResource);
}
}
}
@@ -34,9 +34,7 @@ import org.apache.fineract.batch.domain.BatchRequest;
import org.apache.fineract.batch.domain.BatchResponse;
import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
import org.apache.fineract.portfolio.loanaccount.api.LoansApiResource;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -87,64 +85,6 @@ public class GetLoanByIdCommandStrategyTest {
assertThat(mutableUriInfo.getAdditionalQueryParameters()).hasSize(noOfQueryParams);
}
/**
* Test {@link GetLoanByIdCommandStrategy#execute} for internal server error.
*/
@Test
public void testExecuteForInternalServerError() {
// given
final TestContext testContext = new TestContext();
final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final BatchRequest request = getBatchRequest(loanId, null, null, null);
given(testContext.loansApiResource.retrieveLoan(eq(loanId), eq(false), eq(null), eq(null), eq(null), any(UriInfo.class)))
.willThrow(new RuntimeException("Some error"));
// when
final BatchResponse response = testContext.underTest.execute(request, testContext.uriInfo);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_INTERNAL_SERVER_ERROR);
assertThat(response.getBody()).isEqualTo("{\"Exception\": java.lang.RuntimeException: Some error}");
assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
}
/**
* Test {@link GetLoanByIdCommandStrategy#execute} for loan not found exception.
*/
@Test
public void testExecuteForLoanNotFoundException() {
// given
final TestContext testContext = new TestContext();
final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final BatchRequest request = getBatchRequest(loanId, null, null, null);
given(testContext.loansApiResource.retrieveLoan(eq(loanId), eq(false), eq(null), eq(null), eq(null), any(UriInfo.class)))
.willThrow(new LoanNotFoundException(loanId));
// when
final BatchResponse response = testContext.underTest.execute(request, testContext.uriInfo);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
assertThat(response.getBody()).isEqualTo(buildLoanNotFoundError(loanId));
assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
}
private String buildLoanNotFoundError(final Long loanId) {
return String.format(
"{\n \"developerMessage\": \"The requested resource is not available.\",\n"
+ " \"httpStatusCode\": \"404\",\n \"defaultUserMessage\": \"The requested resource is not available.\",\n"
+ " \"userMessageGlobalisationCode\": \"error.msg.resource.not.found\",\n \"errors\": [\n {\n"
+ " \"developerMessage\": \"Loan with identifier %s does not exist\",\n"
+ " \"defaultUserMessage\": \"Loan with identifier %s does not exist\",\n"
+ " \"userMessageGlobalisationCode\": \"error.msg.loan.id.invalid\",\n \"parameterName\": \"id\",\n"
+ " \"args\": [\n {\n \"value\": %s\n }\n ]\n }\n ]\n" + "}",
loanId, loanId, loanId);
}
/**
* Creates and returns a request with the given loan id.
*
@@ -19,11 +19,9 @@
package org.apache.fineract.batch.command.internal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.when;
import java.util.stream.Stream;
import javax.ws.rs.HttpMethod;
@@ -32,10 +30,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.fineract.batch.domain.BatchRequest;
import org.apache.fineract.batch.domain.BatchResponse;
import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -84,75 +79,6 @@ public class GetTransactionByIdCommandStrategyTest {
assertThat(response.getBody()).isEqualTo(responseBody);
}
/**
* Test {@link GetTransactionByIdCommandStrategy#execute} for internal server error.
*/
@Test
public void testExecuteForInternalServerError() {
// given
final TestContext testContext = new TestContext();
final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final Long transactionId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final BatchRequest request = getBatchRequest(loanId, transactionId, null);
given(testContext.loanTransactionsApiResource.retrieveTransaction(loanId, transactionId, null, testContext.uriInfo))
.willThrow(new RuntimeException("Some error"));
// when
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_INTERNAL_SERVER_ERROR);
assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
assertThat(response.getBody()).isEqualTo("{\"Exception\": java.lang.RuntimeException: Some error}");
}
/**
* Test {@link GetTransactionByIdCommandStrategy#execute} for loan not found exception.
*/
@Test
public void testExecuteForLoanNotFoundException() {
// given
final TestContext testContext = new TestContext();
final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final Long transactionId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final BatchRequest request = getBatchRequest(loanId, transactionId, null);
given(testContext.loanTransactionsApiResource.retrieveTransaction(loanId, transactionId, null, testContext.uriInfo))
.willThrow(new LoanNotFoundException(loanId));
// when
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
assertThat(response.getBody()).isEqualTo(build404NotFoundError("Loan", loanId));
}
/**
* Test {@link GetTransactionByIdCommandStrategy#execute} for transaction not found exception.
*/
@Test
public void testExecuteForTransactionNotFoundException() {
final TestContext testContext = new TestContext();
final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final Long transactionId = Long.valueOf(RandomStringUtils.randomNumeric(4));
final BatchRequest request = getBatchRequest(loanId, transactionId, null);
when(testContext.loanTransactionsApiResource.retrieveTransaction(loanId, transactionId, null, testContext.uriInfo))
.thenThrow(new LoanTransactionNotFoundException(transactionId));
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode());
assertEquals(build404NotFoundError("Transaction", transactionId), response.getBody());
assertEquals(request.getRequestId(), response.getRequestId());
assertEquals(request.getHeaders(), response.getHeaders());
}
/**
* Creates and returns a request with the given loan id and transaction id.
*
@@ -179,25 +105,6 @@ public class GetTransactionByIdCommandStrategyTest {
return br;
}
/**
* Builds the 404 not found error.
*
* @param field
* the field name
* @param id
* the id
*/
private String build404NotFoundError(final String field, final Long id) {
return String.format("{\n" + " \"developerMessage\": \"The requested resource is not available.\",\n"
+ " \"httpStatusCode\": \"404\",\n" + " \"defaultUserMessage\": \"The requested resource is not available.\",\n"
+ " \"userMessageGlobalisationCode\": \"error.msg.resource.not.found\",\n" + " \"errors\": [\n" + " {\n"
+ " \"developerMessage\": \"%s with identifier %s does not exist\",\n"
+ " \"defaultUserMessage\": \"%s with identifier %s does not exist\",\n"
+ " \"userMessageGlobalisationCode\": \"error.msg.loan.id.invalid\",\n" + " \"parameterName\": \"id\",\n"
+ " \"args\": [\n" + " {\n" + " \"value\": %s\n" + " }\n" + " ]\n" + " }\n" + " ]\n"
+ "}", field, id, field, id, id);
}
/**
* Private test context class used since testng runs in parallel to avoid state between tests
*/