FINERACT-2421: Allow reage preview API via Batch API
This commit is contained in:
+20
-1
@@ -52,6 +52,8 @@ import org.apache.fineract.batch.command.internal.GetLoanByExternalIdCommandStra
|
||||
import org.apache.fineract.batch.command.internal.GetLoanByIdCommandStrategy;
|
||||
import org.apache.fineract.batch.command.internal.GetLoanTransactionByExternalIdCommandStrategy;
|
||||
import org.apache.fineract.batch.command.internal.GetLoanTransactionByIdCommandStrategy;
|
||||
import org.apache.fineract.batch.command.internal.GetReagePreviewByLoanExternalIdCommandStrategy;
|
||||
import org.apache.fineract.batch.command.internal.GetReagePreviewByLoanIdCommandStrategy;
|
||||
import org.apache.fineract.batch.command.internal.LoanStateTransistionsByExternalIdCommandStrategy;
|
||||
import org.apache.fineract.batch.command.internal.ModifyLoanApplicationCommandStrategy;
|
||||
import org.apache.fineract.batch.command.internal.UnknownCommandStrategy;
|
||||
@@ -206,7 +208,24 @@ public class CommandStrategyProviderTest {
|
||||
Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1/interest-pauses", HttpMethod.POST,
|
||||
"createLoanInterestPauseByExternalIdCommandStrategy", mock(CommandStrategy.class)),
|
||||
Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1/interest-pauses/123", HttpMethod.PUT,
|
||||
"updateLoanInterestPauseByExternalIdCommandStrategy", mock(CommandStrategy.class)));
|
||||
"updateLoanInterestPauseByExternalIdCommandStrategy", mock(CommandStrategy.class)),
|
||||
Arguments.of("loans/123/transactions/reage-preview", HttpMethod.GET, "getReagePreviewByLoanIdCommandStrategy",
|
||||
mock(GetReagePreviewByLoanIdCommandStrategy.class)),
|
||||
Arguments.of("loans/123/transactions/reage-preview?frequencyNumber=1&frequencyType=MONTHS", HttpMethod.GET,
|
||||
"getReagePreviewByLoanIdCommandStrategy", mock(GetReagePreviewByLoanIdCommandStrategy.class)),
|
||||
Arguments.of(
|
||||
"loans/123/transactions/reage-preview?frequencyType=MONTHS&locale=en_US&frequencyNumber=1&dateFormat=MM%2Fdd%2Fyyyy&startDate=02%2F05%2F2026&numberOfInstallments=6",
|
||||
HttpMethod.GET, "getReagePreviewByLoanIdCommandStrategy", mock(GetReagePreviewByLoanIdCommandStrategy.class)),
|
||||
Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1/transactions/reage-preview", HttpMethod.GET,
|
||||
"getReagePreviewByLoanExternalIdCommandStrategy", mock(GetReagePreviewByLoanExternalIdCommandStrategy.class)),
|
||||
Arguments.of(
|
||||
"loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1/transactions/reage-preview?frequencyNumber=2&frequencyType=WEEKS",
|
||||
HttpMethod.GET, "getReagePreviewByLoanExternalIdCommandStrategy",
|
||||
mock(GetReagePreviewByLoanExternalIdCommandStrategy.class)),
|
||||
Arguments.of(
|
||||
"loans/external-id/0083477d-ea2a-45a4-a244-cb79a9ecf741/transactions/reage-preview?frequencyType=MONTHS&locale=en_US&frequencyNumber=1&dateFormat=MM%2Fdd%2Fyyyy&startDate=02%2F05%2F2026&numberOfInstallments=6",
|
||||
HttpMethod.GET, "getReagePreviewByLoanExternalIdCommandStrategy",
|
||||
mock(GetReagePreviewByLoanExternalIdCommandStrategy.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+305
@@ -19,9 +19,21 @@
|
||||
package org.apache.fineract.batch.command;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.fineract.batch.domain.BatchRequest;
|
||||
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;
|
||||
|
||||
class CommandStrategyUtilsTest {
|
||||
|
||||
@@ -90,4 +102,297 @@ class CommandStrategyUtilsTest {
|
||||
// then
|
||||
assertThat(result).isEqualTo("clients/123?command=action&something=else");
|
||||
}
|
||||
|
||||
// Tests for buildRequestFromQueryParameters method
|
||||
|
||||
private static Stream<Arguments> typeConversionTestCases() {
|
||||
return Stream.of(Arguments.of("stringField", "testValue", (Object) "testValue"), Arguments.of("integerField", "42", 42),
|
||||
Arguments.of("longField", "9876543210", 9876543210L), Arguments.of("bigDecimalField", "123.456", new BigDecimal("123.456")),
|
||||
Arguments.of("bigDecimalField", "0.1000000000000000055511151231257827021181583404541015625",
|
||||
new BigDecimal("0.1000000000000000055511151231257827021181583404541015625")),
|
||||
Arguments.of("doubleField", "3.14", 3.14), Arguments.of("booleanField", "true", true),
|
||||
Arguments.of("booleanField", "false", false), Arguments.of("booleanField", "TRUE", true));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("typeConversionTestCases")
|
||||
public void testBuildRequestFromQueryParametersTypeConversion(String fieldName, String value, Object expectedValue) {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put(fieldName, value);
|
||||
|
||||
// when
|
||||
TestRequest result = CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, TestRequest.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
Object actualValue = getFieldValue(result, fieldName);
|
||||
if (expectedValue instanceof BigDecimal) {
|
||||
assertThat((BigDecimal) actualValue).isEqualByComparingTo((BigDecimal) expectedValue);
|
||||
} else {
|
||||
assertThat(actualValue).isEqualTo(expectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> invalidTypeConversionTestCases() {
|
||||
return Stream.of(Arguments.of("integerField", "notANumber"), Arguments.of("longField", "notANumber"),
|
||||
Arguments.of("bigDecimalField", "notANumber"), Arguments.of("doubleField", "notANumber"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("invalidTypeConversionTestCases")
|
||||
public void testBuildRequestFromQueryParametersInvalidTypeConversion(String fieldName, String invalidValue) {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put(fieldName, invalidValue);
|
||||
|
||||
// when/then
|
||||
assertThatThrownBy(() -> CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, TestRequest.class))
|
||||
.isInstanceOf(RuntimeException.class).hasMessageContaining("Failed to build request object from query parameters")
|
||||
.hasCauseInstanceOf(NumberFormatException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRequestFromQueryParametersWithAllFieldTypes() {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("stringField", "testValue");
|
||||
queryParams.put("integerField", "42");
|
||||
queryParams.put("longField", "9876543210");
|
||||
queryParams.put("bigDecimalField", "3.14");
|
||||
queryParams.put("doubleField", "2.71");
|
||||
queryParams.put("booleanField", "true");
|
||||
|
||||
// when
|
||||
TestRequest result = CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, TestRequest.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStringField()).isEqualTo("testValue");
|
||||
assertThat(result.getIntegerField()).isEqualTo(42);
|
||||
assertThat(result.getLongField()).isEqualTo(9876543210L);
|
||||
assertThat(result.getBigDecimalField()).isEqualByComparingTo(new BigDecimal("3.14"));
|
||||
assertThat(result.getDoubleField()).isEqualTo(2.71);
|
||||
assertThat(result.getBooleanField()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRequestFromQueryParametersWithMissingFields() {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("stringField", "testValue");
|
||||
|
||||
// when
|
||||
TestRequest result = CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, TestRequest.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStringField()).isEqualTo("testValue");
|
||||
assertThat(result.getIntegerField()).isNull();
|
||||
assertThat(result.getLongField()).isNull();
|
||||
assertThat(result.getBigDecimalField()).isNull();
|
||||
assertThat(result.getDoubleField()).isNull();
|
||||
assertThat(result.getBooleanField()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRequestFromQueryParametersWithEmptyMap() {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
|
||||
// when
|
||||
TestRequest result = CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, TestRequest.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStringField()).isNull();
|
||||
assertThat(result.getIntegerField()).isNull();
|
||||
assertThat(result.getLongField()).isNull();
|
||||
assertThat(result.getBigDecimalField()).isNull();
|
||||
assertThat(result.getDoubleField()).isNull();
|
||||
assertThat(result.getBooleanField()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRequestFromQueryParametersSkipsSerialVersionUID() {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("serialVersionUID", "12345");
|
||||
queryParams.put("stringField", "testValue");
|
||||
|
||||
// when
|
||||
TestRequest result = CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, TestRequest.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStringField()).isEqualTo("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRequestFromQueryParametersWithClassWithoutBuilder() {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("field", "value");
|
||||
|
||||
// when/then
|
||||
assertThatThrownBy(() -> CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, ClassWithoutBuilder.class))
|
||||
.isInstanceOf(RuntimeException.class).hasMessageContaining("Failed to build request object from query parameters");
|
||||
}
|
||||
|
||||
private Object getFieldValue(TestRequest request, String fieldName) {
|
||||
switch (fieldName) {
|
||||
case "stringField":
|
||||
return request.getStringField();
|
||||
case "integerField":
|
||||
return request.getIntegerField();
|
||||
case "longField":
|
||||
return request.getLongField();
|
||||
case "bigDecimalField":
|
||||
return request.getBigDecimalField();
|
||||
case "doubleField":
|
||||
return request.getDoubleField();
|
||||
case "booleanField":
|
||||
return request.getBooleanField();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
static class TestRequest {
|
||||
|
||||
private String stringField;
|
||||
private Integer integerField;
|
||||
private Long longField;
|
||||
private BigDecimal bigDecimalField;
|
||||
private Double doubleField;
|
||||
private Boolean booleanField;
|
||||
}
|
||||
|
||||
static class ClassWithoutBuilder {
|
||||
|
||||
private String field;
|
||||
|
||||
ClassWithoutBuilder(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for getQueryParameters method with URL decoding
|
||||
|
||||
private static Stream<Arguments> urlDecodingTestCases() {
|
||||
return Stream.of(
|
||||
// URL-encoded forward slashes
|
||||
Arguments.of("loans/123?dateFormat=MM%2Fdd%2Fyyyy", "dateFormat", "MM/dd/yyyy"),
|
||||
Arguments.of("loans/123?startDate=02%2F05%2F2026", "startDate", "02/05/2026"),
|
||||
// URL-encoded spaces
|
||||
Arguments.of("loans/123?name=John%20Doe", "name", "John Doe"),
|
||||
// URL-encoded special characters
|
||||
Arguments.of("loans/123?email=user%40example.com", "email", "user@example.com"),
|
||||
Arguments.of("loans/123?query=hello%26world", "query", "hello&world"),
|
||||
// URL-encoded percentage sign
|
||||
Arguments.of("loans/123?discount=50%25", "discount", "50%"),
|
||||
// Non-encoded values (should remain unchanged)
|
||||
Arguments.of("loans/123?locale=en_US", "locale", "en_US"),
|
||||
Arguments.of("loans/123?frequencyType=MONTHS", "frequencyType", "MONTHS"),
|
||||
Arguments.of("loans/123?frequencyNumber=1", "frequencyNumber", "1"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("urlDecodingTestCases")
|
||||
public void testGetQueryParametersWithUrlDecoding(String relativeUrl, String expectedKey, String expectedValue) {
|
||||
// when
|
||||
Map<String, String> result = CommandStrategyUtils.getQueryParameters(relativeUrl);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).containsKey(expectedKey);
|
||||
assertThat(result.get(expectedKey)).isEqualTo(expectedValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQueryParametersWithMultipleUrlEncodedParams() {
|
||||
// given
|
||||
String relativeUrl = "loans/123?dateFormat=MM%2Fdd%2Fyyyy&startDate=02%2F05%2F2026&locale=en_US&name=John%20Doe";
|
||||
|
||||
// when
|
||||
Map<String, String> result = CommandStrategyUtils.getQueryParameters(relativeUrl);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).hasSize(4);
|
||||
assertThat(result.get("dateFormat")).isEqualTo("MM/dd/yyyy");
|
||||
assertThat(result.get("startDate")).isEqualTo("02/05/2026");
|
||||
assertThat(result.get("locale")).isEqualTo("en_US");
|
||||
assertThat(result.get("name")).isEqualTo("John Doe");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQueryParametersWithComplexUrlEncodedQuery() {
|
||||
// given - using the actual sample URL from the requirement
|
||||
String relativeUrl = "loans/external-id/0083477d-ea2a-45a4-a244-cb79a9ecf741/transactions/reage-preview"
|
||||
+ "?frequencyType=MONTHS&locale=en_US&frequencyNumber=1&dateFormat=MM%2Fdd%2Fyyyy"
|
||||
+ "&startDate=02%2F05%2F2026&numberOfInstallments=6";
|
||||
|
||||
// when
|
||||
Map<String, String> result = CommandStrategyUtils.getQueryParameters(relativeUrl);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).hasSize(6);
|
||||
assertThat(result.get("frequencyType")).isEqualTo("MONTHS");
|
||||
assertThat(result.get("locale")).isEqualTo("en_US");
|
||||
assertThat(result.get("frequencyNumber")).isEqualTo("1");
|
||||
assertThat(result.get("dateFormat")).isEqualTo("MM/dd/yyyy");
|
||||
assertThat(result.get("startDate")).isEqualTo("02/05/2026");
|
||||
assertThat(result.get("numberOfInstallments")).isEqualTo("6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQueryParametersWithEmptyValue() {
|
||||
// given
|
||||
String relativeUrl = "loans/123?param1=value1¶m2=¶m3=value3";
|
||||
|
||||
// when
|
||||
Map<String, String> result = CommandStrategyUtils.getQueryParameters(relativeUrl);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).hasSize(3);
|
||||
assertThat(result.get("param1")).isEqualTo("value1");
|
||||
assertThat(result.get("param2")).isEqualTo("");
|
||||
assertThat(result.get("param3")).isEqualTo("value3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQueryParametersWithNoQueryParams() {
|
||||
// given
|
||||
String relativeUrl = "loans/123";
|
||||
|
||||
// when
|
||||
Map<String, String> result = CommandStrategyUtils.getQueryParameters(relativeUrl);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRequestFromQueryParametersWithUrlEncodedValues() {
|
||||
// given
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("stringField", "Hello World"); // Already decoded by getQueryParameters
|
||||
queryParams.put("integerField", "42");
|
||||
|
||||
// when
|
||||
TestRequest result = CommandStrategyUtils.buildRequestFromQueryParameters(queryParams, TestRequest.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getStringField()).isEqualTo("Hello World");
|
||||
assertThat(result.getIntegerField()).isEqualTo(42);
|
||||
}
|
||||
}
|
||||
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* 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.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import jakarta.ws.rs.HttpMethod;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.fineract.batch.domain.BatchRequest;
|
||||
import org.apache.fineract.batch.domain.BatchResponse;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.request.ReAgePreviewRequest;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
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;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Test class for {@link GetReagePreviewByLoanExternalIdCommandStrategy}.
|
||||
*/
|
||||
public class GetReagePreviewByLoanExternalIdCommandStrategyTest {
|
||||
|
||||
/**
|
||||
* The query parameter provider.
|
||||
*
|
||||
* @return Arguments.
|
||||
*/
|
||||
private static Stream<Arguments> provideQueryParameters() {
|
||||
return Stream.of(Arguments.of(1, "MONTHS", "2024-01-15", 12, "dd MMMM yyyy", "en"),
|
||||
Arguments.of(2, "WEEKS", "2024-02-01", 24, "yyyy-MM-dd", "en_US"),
|
||||
Arguments.of(3, "DAYS", "2024-03-10", 6, "dd/MM/yyyy", "en_GB"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test {@link GetReagePreviewByLoanExternalIdCommandStrategy#execute} with wrong parameter names.
|
||||
*/
|
||||
@Test
|
||||
public void testExecuteWithWrongParameterNames() {
|
||||
// given
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
final String loanExternalId = UUID.randomUUID().toString();
|
||||
// Build request with WRONG parameter names
|
||||
final BatchRequest request = getBatchRequestWithWrongParameterNames(loanExternalId);
|
||||
|
||||
// Mock LoanScheduleData since it doesn't have a default constructor
|
||||
final LoanScheduleData loanScheduleData = mock(LoanScheduleData.class);
|
||||
final String responseBody = "{\"periods\":[]}";
|
||||
|
||||
given(testContext.loanTransactionsApiResource.previewReAgeSchedule(eq(loanExternalId), any(ReAgePreviewRequest.class)))
|
||||
.willReturn(loanScheduleData);
|
||||
given(testContext.toApiJsonSerializer.serialize(eq(loanScheduleData))).willReturn(responseBody);
|
||||
|
||||
// when
|
||||
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
|
||||
|
||||
// then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
|
||||
|
||||
// Verify the API resource was called
|
||||
verify(testContext.loanTransactionsApiResource).previewReAgeSchedule(eq(loanExternalId),
|
||||
testContext.reAgePreviewRequestCaptor.capture());
|
||||
|
||||
// Verify the serializer was invoked
|
||||
verify(testContext.toApiJsonSerializer).serialize(eq(loanScheduleData));
|
||||
|
||||
// Verify that wrong parameter names result in null values
|
||||
final ReAgePreviewRequest capturedRequest = testContext.reAgePreviewRequestCaptor.getValue();
|
||||
assertThat(capturedRequest.getFrequencyNumber()).isNull(); // frequencyNo was sent, not frequencyNumber
|
||||
assertThat(capturedRequest.getFrequencyType()).isNull(); // freqType was sent, not frequencyType
|
||||
assertThat(capturedRequest.getStartDate()).isNull(); // start was sent, not startDate
|
||||
assertThat(capturedRequest.getNumberOfInstallments()).isNull(); // installments was sent, not
|
||||
// numberOfInstallments
|
||||
assertThat(capturedRequest.getDateFormat()).isNull(); // format was sent, not dateFormat
|
||||
assertThat(capturedRequest.getLocale()).isNull(); // lang was sent, not locale
|
||||
}
|
||||
|
||||
/**
|
||||
* Test {@link GetReagePreviewByLoanExternalIdCommandStrategy#execute} happy path scenario.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideQueryParameters")
|
||||
public void testExecuteSuccessScenario(final Integer frequencyNumber, final String frequencyType, final String startDate,
|
||||
final Integer numberOfInstallments, final String dateFormat, final String locale) {
|
||||
// given
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
final String loanExternalId = UUID.randomUUID().toString();
|
||||
final BatchRequest request = getBatchRequest(loanExternalId, frequencyNumber, frequencyType, startDate, numberOfInstallments,
|
||||
dateFormat, locale);
|
||||
|
||||
// Mock LoanScheduleData since it doesn't have a default constructor
|
||||
final LoanScheduleData loanScheduleData = mock(LoanScheduleData.class);
|
||||
final String responseBody = "{\"periods\":[]}";
|
||||
|
||||
given(testContext.loanTransactionsApiResource.previewReAgeSchedule(eq(loanExternalId), any(ReAgePreviewRequest.class)))
|
||||
.willReturn(loanScheduleData);
|
||||
given(testContext.toApiJsonSerializer.serialize(eq(loanScheduleData))).willReturn(responseBody);
|
||||
|
||||
// when
|
||||
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
|
||||
|
||||
// then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
|
||||
assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
|
||||
assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
|
||||
assertThat(response.getBody()).isEqualTo(responseBody);
|
||||
|
||||
// Verify the API resource was called with correct parameters
|
||||
verify(testContext.loanTransactionsApiResource).previewReAgeSchedule(eq(loanExternalId),
|
||||
testContext.reAgePreviewRequestCaptor.capture());
|
||||
|
||||
// Verify the serializer was invoked with the returned LoanScheduleData
|
||||
verify(testContext.toApiJsonSerializer).serialize(eq(loanScheduleData));
|
||||
|
||||
// Verify the ReAgePreviewRequest was built correctly from query parameters
|
||||
final ReAgePreviewRequest capturedRequest = testContext.reAgePreviewRequestCaptor.getValue();
|
||||
assertThat(capturedRequest.getFrequencyNumber()).isEqualTo(frequencyNumber);
|
||||
assertThat(capturedRequest.getFrequencyType()).isEqualTo(frequencyType);
|
||||
assertThat(capturedRequest.getStartDate()).isEqualTo(startDate);
|
||||
assertThat(capturedRequest.getNumberOfInstallments()).isEqualTo(numberOfInstallments);
|
||||
assertThat(capturedRequest.getDateFormat()).isEqualTo(dateFormat);
|
||||
assertThat(capturedRequest.getLocale()).isEqualTo(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a batch request with the given parameters.
|
||||
*
|
||||
* @param loanExternalId
|
||||
* the loan external id
|
||||
* @param frequencyNumber
|
||||
* the frequency number
|
||||
* @param frequencyType
|
||||
* the frequency type
|
||||
* @param startDate
|
||||
* the start date
|
||||
* @param numberOfInstallments
|
||||
* the number of installments
|
||||
* @param dateFormat
|
||||
* the date format
|
||||
* @param locale
|
||||
* the locale
|
||||
* @return BatchRequest
|
||||
*/
|
||||
private BatchRequest getBatchRequest(final String loanExternalId, final Integer frequencyNumber, final String frequencyType,
|
||||
final String startDate, final Integer numberOfInstallments, final String dateFormat, final String locale) {
|
||||
|
||||
final BatchRequest br = new BatchRequest();
|
||||
String relativeUrl = "loans/external-id/" + loanExternalId + "/transactions/reage-preview";
|
||||
|
||||
Set<String> queryParams = new HashSet<>();
|
||||
queryParams.add("frequencyNumber=" + frequencyNumber);
|
||||
queryParams.add("frequencyType=" + frequencyType);
|
||||
queryParams.add("startDate=" + startDate);
|
||||
queryParams.add("numberOfInstallments=" + numberOfInstallments);
|
||||
queryParams.add("dateFormat=" + dateFormat);
|
||||
queryParams.add("locale=" + locale);
|
||||
|
||||
relativeUrl = relativeUrl + "?" + String.join("&", queryParams);
|
||||
|
||||
br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5)));
|
||||
br.setRelativeUrl(relativeUrl);
|
||||
br.setMethod(HttpMethod.GET);
|
||||
br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5)));
|
||||
br.setBody("{}");
|
||||
|
||||
return br;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a batch request with WRONG parameter names to test validation.
|
||||
*
|
||||
* @param loanExternalId
|
||||
* the loan external id
|
||||
* @return BatchRequest
|
||||
*/
|
||||
private BatchRequest getBatchRequestWithWrongParameterNames(final String loanExternalId) {
|
||||
|
||||
final BatchRequest br = new BatchRequest();
|
||||
String relativeUrl = "loans/external-id/" + loanExternalId + "/transactions/reage-preview";
|
||||
|
||||
Set<String> queryParams = new HashSet<>();
|
||||
// Using wrong parameter names
|
||||
queryParams.add("frequencyNo=1"); // should be frequencyNumber
|
||||
queryParams.add("freqType=MONTHS"); // should be frequencyType
|
||||
queryParams.add("start=2024-01-15"); // should be startDate
|
||||
queryParams.add("installments=12"); // should be numberOfInstallments
|
||||
queryParams.add("format=dd MMMM yyyy"); // should be dateFormat
|
||||
queryParams.add("lang=en"); // should be locale
|
||||
|
||||
relativeUrl = relativeUrl + "?" + String.join("&", queryParams);
|
||||
|
||||
br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5)));
|
||||
br.setRelativeUrl(relativeUrl);
|
||||
br.setMethod(HttpMethod.GET);
|
||||
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 {
|
||||
|
||||
/**
|
||||
* The Mock UriInfo
|
||||
*/
|
||||
@Mock
|
||||
private UriInfo uriInfo;
|
||||
|
||||
/**
|
||||
* The Mock {@link LoanTransactionsApiResource}
|
||||
*/
|
||||
@Mock
|
||||
private LoanTransactionsApiResource loanTransactionsApiResource;
|
||||
|
||||
/**
|
||||
* The Mock {@link DefaultToApiJsonSerializer}
|
||||
*/
|
||||
@Mock
|
||||
private DefaultToApiJsonSerializer<LoanScheduleData> toApiJsonSerializer;
|
||||
|
||||
/**
|
||||
* The Captor for ReAgePreviewRequest
|
||||
*/
|
||||
@Captor
|
||||
private ArgumentCaptor<ReAgePreviewRequest> reAgePreviewRequestCaptor;
|
||||
|
||||
/**
|
||||
* The class under test.
|
||||
*/
|
||||
private final GetReagePreviewByLoanExternalIdCommandStrategy subjectToTest;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
TestContext() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
subjectToTest = new GetReagePreviewByLoanExternalIdCommandStrategy(loanTransactionsApiResource, toApiJsonSerializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* 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.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import jakarta.ws.rs.HttpMethod;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.fineract.batch.domain.BatchRequest;
|
||||
import org.apache.fineract.batch.domain.BatchResponse;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.request.ReAgePreviewRequest;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
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;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Test class for {@link GetReagePreviewByLoanIdCommandStrategy}.
|
||||
*/
|
||||
public class GetReagePreviewByLoanIdCommandStrategyTest {
|
||||
|
||||
/**
|
||||
* The query parameter provider.
|
||||
*
|
||||
* @return Arguments.
|
||||
*/
|
||||
private static Stream<Arguments> provideQueryParameters() {
|
||||
return Stream.of(Arguments.of(1L, 1, "MONTHS", "2024-01-15", 12, "dd MMMM yyyy", "en"),
|
||||
Arguments.of(2L, 2, "WEEKS", "2024-02-01", 24, "yyyy-MM-dd", "en_US"),
|
||||
Arguments.of(3L, 3, "DAYS", "2024-03-10", 6, "dd/MM/yyyy", "en_GB"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test {@link GetReagePreviewByLoanIdCommandStrategy#execute} with wrong parameter names.
|
||||
*/
|
||||
@Test
|
||||
public void testExecuteWithWrongParameterNames() {
|
||||
// given
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
final Long loanId = 1L;
|
||||
// Build request with WRONG parameter names
|
||||
final BatchRequest request = getBatchRequestWithWrongParameterNames(loanId);
|
||||
|
||||
// Mock LoanScheduleData since it doesn't have a default constructor
|
||||
final LoanScheduleData loanScheduleData = mock(LoanScheduleData.class);
|
||||
final String responseBody = "{\"periods\":[]}";
|
||||
|
||||
given(testContext.loanTransactionsApiResource.previewReAgeSchedule(eq(loanId), any(ReAgePreviewRequest.class)))
|
||||
.willReturn(loanScheduleData);
|
||||
given(testContext.toApiJsonSerializer.serialize(eq(loanScheduleData))).willReturn(responseBody);
|
||||
|
||||
// when
|
||||
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
|
||||
|
||||
// then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
|
||||
|
||||
// Verify the API resource was called
|
||||
verify(testContext.loanTransactionsApiResource).previewReAgeSchedule(eq(loanId), testContext.reAgePreviewRequestCaptor.capture());
|
||||
|
||||
// Verify the serializer was invoked
|
||||
verify(testContext.toApiJsonSerializer).serialize(eq(loanScheduleData));
|
||||
|
||||
// Verify that wrong parameter names result in null values
|
||||
final ReAgePreviewRequest capturedRequest = testContext.reAgePreviewRequestCaptor.getValue();
|
||||
assertThat(capturedRequest.getFrequencyNumber()).isNull(); // frequencyNo was sent, not frequencyNumber
|
||||
assertThat(capturedRequest.getFrequencyType()).isNull(); // freqType was sent, not frequencyType
|
||||
assertThat(capturedRequest.getStartDate()).isNull(); // start was sent, not startDate
|
||||
assertThat(capturedRequest.getNumberOfInstallments()).isNull(); // installments was sent, not
|
||||
// numberOfInstallments
|
||||
assertThat(capturedRequest.getDateFormat()).isNull(); // format was sent, not dateFormat
|
||||
assertThat(capturedRequest.getLocale()).isNull(); // lang was sent, not locale
|
||||
}
|
||||
|
||||
/**
|
||||
* Test {@link GetReagePreviewByLoanIdCommandStrategy#execute} happy path scenario.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideQueryParameters")
|
||||
public void testExecuteSuccessScenario(final Long loanId, final Integer frequencyNumber, final String frequencyType,
|
||||
final String startDate, final Integer numberOfInstallments, final String dateFormat, final String locale) {
|
||||
// given
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
final BatchRequest request = getBatchRequest(loanId, frequencyNumber, frequencyType, startDate, numberOfInstallments, dateFormat,
|
||||
locale);
|
||||
|
||||
// Mock LoanScheduleData since it doesn't have a default constructor
|
||||
final LoanScheduleData loanScheduleData = mock(LoanScheduleData.class);
|
||||
final String responseBody = "{\"periods\":[]}";
|
||||
|
||||
given(testContext.loanTransactionsApiResource.previewReAgeSchedule(eq(loanId), any(ReAgePreviewRequest.class)))
|
||||
.willReturn(loanScheduleData);
|
||||
given(testContext.toApiJsonSerializer.serialize(eq(loanScheduleData))).willReturn(responseBody);
|
||||
|
||||
// when
|
||||
final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo);
|
||||
|
||||
// then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
|
||||
assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
|
||||
assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
|
||||
assertThat(response.getBody()).isEqualTo(responseBody);
|
||||
|
||||
// Verify the API resource was called with correct parameters
|
||||
verify(testContext.loanTransactionsApiResource).previewReAgeSchedule(eq(loanId), testContext.reAgePreviewRequestCaptor.capture());
|
||||
|
||||
// Verify the serializer was invoked with the returned LoanScheduleData
|
||||
verify(testContext.toApiJsonSerializer).serialize(eq(loanScheduleData));
|
||||
|
||||
// Verify the ReAgePreviewRequest was built correctly from query parameters
|
||||
final ReAgePreviewRequest capturedRequest = testContext.reAgePreviewRequestCaptor.getValue();
|
||||
assertThat(capturedRequest.getFrequencyNumber()).isEqualTo(frequencyNumber);
|
||||
assertThat(capturedRequest.getFrequencyType()).isEqualTo(frequencyType);
|
||||
assertThat(capturedRequest.getStartDate()).isEqualTo(startDate);
|
||||
assertThat(capturedRequest.getNumberOfInstallments()).isEqualTo(numberOfInstallments);
|
||||
assertThat(capturedRequest.getDateFormat()).isEqualTo(dateFormat);
|
||||
assertThat(capturedRequest.getLocale()).isEqualTo(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a batch request with the given parameters.
|
||||
*
|
||||
* @param loanId
|
||||
* the loan id
|
||||
* @param frequencyNumber
|
||||
* the frequency number
|
||||
* @param frequencyType
|
||||
* the frequency type
|
||||
* @param startDate
|
||||
* the start date
|
||||
* @param numberOfInstallments
|
||||
* the number of installments
|
||||
* @param dateFormat
|
||||
* the date format
|
||||
* @param locale
|
||||
* the locale
|
||||
* @return BatchRequest
|
||||
*/
|
||||
private BatchRequest getBatchRequest(final Long loanId, final Integer frequencyNumber, final String frequencyType,
|
||||
final String startDate, final Integer numberOfInstallments, final String dateFormat, final String locale) {
|
||||
|
||||
final BatchRequest br = new BatchRequest();
|
||||
String relativeUrl = "loans/" + loanId + "/transactions/reage-preview";
|
||||
|
||||
Set<String> queryParams = new HashSet<>();
|
||||
queryParams.add("frequencyNumber=" + frequencyNumber);
|
||||
queryParams.add("frequencyType=" + frequencyType);
|
||||
queryParams.add("startDate=" + startDate);
|
||||
queryParams.add("numberOfInstallments=" + numberOfInstallments);
|
||||
queryParams.add("dateFormat=" + dateFormat);
|
||||
queryParams.add("locale=" + locale);
|
||||
|
||||
relativeUrl = relativeUrl + "?" + String.join("&", queryParams);
|
||||
|
||||
br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5)));
|
||||
br.setRelativeUrl(relativeUrl);
|
||||
br.setMethod(HttpMethod.GET);
|
||||
br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5)));
|
||||
br.setBody("{}");
|
||||
|
||||
return br;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a batch request with WRONG parameter names to test validation.
|
||||
*
|
||||
* @param loanId
|
||||
* the loan id
|
||||
* @return BatchRequest
|
||||
*/
|
||||
private BatchRequest getBatchRequestWithWrongParameterNames(final Long loanId) {
|
||||
|
||||
final BatchRequest br = new BatchRequest();
|
||||
String relativeUrl = "loans/" + loanId + "/transactions/reage-preview";
|
||||
|
||||
Set<String> queryParams = new HashSet<>();
|
||||
// Using wrong parameter names
|
||||
queryParams.add("frequencyNo=1"); // should be frequencyNumber
|
||||
queryParams.add("freqType=MONTHS"); // should be frequencyType
|
||||
queryParams.add("start=2024-01-15"); // should be startDate
|
||||
queryParams.add("installments=12"); // should be numberOfInstallments
|
||||
queryParams.add("format=dd MMMM yyyy"); // should be dateFormat
|
||||
queryParams.add("lang=en"); // should be locale
|
||||
|
||||
relativeUrl = relativeUrl + "?" + String.join("&", queryParams);
|
||||
|
||||
br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5)));
|
||||
br.setRelativeUrl(relativeUrl);
|
||||
br.setMethod(HttpMethod.GET);
|
||||
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 {
|
||||
|
||||
/**
|
||||
* The Mock UriInfo
|
||||
*/
|
||||
@Mock
|
||||
private UriInfo uriInfo;
|
||||
|
||||
/**
|
||||
* The Mock {@link LoanTransactionsApiResource}
|
||||
*/
|
||||
@Mock
|
||||
private LoanTransactionsApiResource loanTransactionsApiResource;
|
||||
|
||||
/**
|
||||
* The Mock {@link DefaultToApiJsonSerializer}
|
||||
*/
|
||||
@Mock
|
||||
private DefaultToApiJsonSerializer<LoanScheduleData> toApiJsonSerializer;
|
||||
|
||||
/**
|
||||
* The Captor for ReAgePreviewRequest
|
||||
*/
|
||||
@Captor
|
||||
private ArgumentCaptor<ReAgePreviewRequest> reAgePreviewRequestCaptor;
|
||||
|
||||
/**
|
||||
* The class under test.
|
||||
*/
|
||||
private final GetReagePreviewByLoanIdCommandStrategy subjectToTest;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
TestContext() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
subjectToTest = new GetReagePreviewByLoanIdCommandStrategy(loanTransactionsApiResource, toApiJsonSerializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user