FINERACT-1694: Improve Testing of External Business Events
This commit is contained in:
+241
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* 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.integrationtests;
|
||||
|
||||
import static org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder.DEFAULT_STRATEGY;
|
||||
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.builder.ResponseSpecBuilder;
|
||||
import io.restassured.http.ContentType;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import io.restassured.specification.ResponseSpecification;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.PostClientsResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
|
||||
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.ExternalEventConfigurationHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper;
|
||||
import org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@Slf4j
|
||||
@ExtendWith({ LoanTestLifecycleExtension.class, ExternalEventsExtension.class })
|
||||
public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
||||
|
||||
private static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
||||
private static PostClientsResponse client;
|
||||
private static LoanTransactionHelper loanTransactionHelper;
|
||||
private static Long loanProductId;
|
||||
private static ResponseSpecification responseSpec;
|
||||
private static RequestSpecification requestSpec;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() {
|
||||
Utils.initializeRESTAssured();
|
||||
requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
requestSpec.header("Fineract-Platform-TenantId", "default");
|
||||
responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec);
|
||||
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||
BusinessStepHelper businessStepHelper = new BusinessStepHelper();
|
||||
client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||
loanProductId = createLoanProductPeriodicWithInterest();
|
||||
// setup COB Business Steps to prevent test failing due other integration test configurations
|
||||
businessStepHelper.updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
|
||||
"CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
|
||||
"EXTERNAL_ASSET_OWNER_TRANSFER");
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testExternalBusinessEventLoanBalanceChangedBusinessEventOnMultiDisbursedInterestBearingLoanForRepaymentAndOverpaymentAndReverseRepaymentAndFullRepayment() {
|
||||
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
runAt("1 March 2023", () -> {
|
||||
enableLoanBalanceChangedBusinessEvent();
|
||||
Long loanId = applyForLoanApplicationWithInterest(client.getClientId(), loanProductId, BigDecimal.valueOf(1000), "1 March 2023",
|
||||
"1 March 2023");
|
||||
loanIdRef.set(loanId);
|
||||
loanTransactionHelper.approveLoan("1 March 2023", loanId.intValue());
|
||||
|
||||
deleteAllExternalEvents();
|
||||
|
||||
loanTransactionHelper.disburseLoan("1 March 2023", loanId.intValue(), "400", null);
|
||||
|
||||
});
|
||||
runAt("15 March 2023", () -> {
|
||||
deleteAllExternalEvents();
|
||||
|
||||
loanTransactionHelper.makeLoanRepayment("15 March 2023", 125.0F, loanIdRef.get().intValue());
|
||||
|
||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 March 2023", 300, 400.0, 291.04));
|
||||
});
|
||||
runAt("1 April 2023", () -> {
|
||||
|
||||
loanTransactionHelper.disburseLoan("1 April 2023", loanIdRef.get().intValue(), "600", null);
|
||||
|
||||
});
|
||||
runAt("15 April 2023", () -> {
|
||||
deleteAllExternalEvents();
|
||||
|
||||
loanTransactionHelper.makeLoanRepayment("15 April 2023", 125.0F, loanIdRef.get().intValue());
|
||||
|
||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 300, 1000.0, 770.06));
|
||||
|
||||
deleteAllExternalEvents();
|
||||
|
||||
Long transactionId = loanTransactionHelper.makeLoanRepayment("15 April 2023", 1000F, loanIdRef.get().intValue())
|
||||
.getResourceId();
|
||||
Assertions.assertNotNull(transactionId);
|
||||
|
||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 700, 1000.0, 0.0));
|
||||
|
||||
deleteAllExternalEvents();
|
||||
|
||||
loanTransactionHelper.reverseRepayment(loanIdRef.get().intValue(), transactionId.intValue(), "15 April 2023");
|
||||
|
||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 300, 1000.0, 770.06));
|
||||
|
||||
deleteAllExternalEvents();
|
||||
|
||||
loanTransactionHelper.makeLoanRepayment("15 April 2023", 830.22F, loanIdRef.get().intValue());
|
||||
|
||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 600, 1000.0, 0.0));
|
||||
|
||||
disableLoanBalanceChangedBusinessEvent();
|
||||
});
|
||||
}
|
||||
|
||||
private static void enableLoanBalanceChangedBusinessEvent() {
|
||||
final Map<String, Boolean> updatedConfigurations = ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
|
||||
responseSpec, "{\"externalEventConfigurations\":{\"LoanBalanceChangedBusinessEvent\":true}}\n");
|
||||
Assertions.assertEquals(updatedConfigurations.size(), 1);
|
||||
Assertions.assertTrue(updatedConfigurations.containsKey("LoanBalanceChangedBusinessEvent"));
|
||||
Assertions.assertTrue(updatedConfigurations.get("LoanBalanceChangedBusinessEvent"));
|
||||
}
|
||||
|
||||
private static void disableLoanBalanceChangedBusinessEvent() {
|
||||
final Map<String, Boolean> updatedConfigurations = ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
|
||||
responseSpec, "{\"externalEventConfigurations\":{\"LoanBalanceChangedBusinessEvent\":false}}\n");
|
||||
Assertions.assertEquals(updatedConfigurations.size(), 1);
|
||||
Assertions.assertTrue(updatedConfigurations.containsKey("LoanBalanceChangedBusinessEvent"));
|
||||
Assertions.assertFalse(updatedConfigurations.get("LoanBalanceChangedBusinessEvent"));
|
||||
}
|
||||
|
||||
private void deleteAllExternalEvents() {
|
||||
ExternalEventHelper.deleteAllExternalEvents(requestSpec, createResponseSpecification(Matchers.is(204)));
|
||||
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||
Assertions.assertEquals(0, allExternalEvents.size());
|
||||
}
|
||||
|
||||
private static Long createLoanProductPeriodicWithInterest() {
|
||||
String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
|
||||
String shortName = Utils.uniqueRandomStringGenerator("", 4);
|
||||
Long resourceId = loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName)
|
||||
.multiDisburseLoan(true).maxTrancheCount(2).interestType(0).interestCalculationPeriodType(0)
|
||||
.disallowExpectedDisbursements(true).description("Test loan description").currencyCode("USD").digitsAfterDecimal(2)
|
||||
.daysInYearType(1).daysInMonthType(1).interestRecalculationCompoundingMethod(0).recalculationRestFrequencyType(1)
|
||||
.rescheduleStrategyMethod(1).recalculationRestFrequencyInterval(0).isInterestRecalculationEnabled(false)
|
||||
.interestRateFrequencyType(2).locale("en_GB").numberOfRepayments(4).repaymentFrequencyType(2L).interestRatePerPeriod(2.0)
|
||||
.repaymentEvery(1).minPrincipal(100.0).principal(1000.0).maxPrincipal(10000000.0).amortizationType(1)
|
||||
.dateFormat(DATETIME_PATTERN).transactionProcessingStrategyCode(DEFAULT_STRATEGY).accountingRule(1)).getResourceId();
|
||||
log.info("Test MultiDisburse Loan Product With Interest. loanProductId: {}", resourceId);
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
private static Long applyForLoanApplicationWithInterest(final Long clientId, final Long loanProductId, BigDecimal principal,
|
||||
String submittedOnDate, String expectedDisburmentDate) {
|
||||
final PostLoansRequest loanRequest = new PostLoansRequest() //
|
||||
.loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2)
|
||||
.interestRatePerPeriod(BigDecimal.valueOf(2)).repaymentEvery(1).principal(principal).amortizationType(1).interestType(1)
|
||||
.interestCalculationPeriodType(0).dateFormat("dd MMMM yyyy").transactionProcessingStrategyCode(DEFAULT_STRATEGY)
|
||||
.loanType("individual").submittedOnDate(submittedOnDate).expectedDisbursementDate(expectedDisburmentDate).clientId(clientId)
|
||||
.productId(loanProductId);
|
||||
Long loanId = loanTransactionHelper.applyLoan(loanRequest).getLoanId();
|
||||
log.info("Test MultiDisbursed Loan with Interest. clientId: {} loanId: {}", client.getClientId(), loanId);
|
||||
return loanId;
|
||||
}
|
||||
|
||||
private void logBusinessEvents(List<ExternalEventDTO> allExternalEvents) {
|
||||
allExternalEvents.forEach(externalEventDTO -> {
|
||||
log.info("Event Received { type:'{}' businessDate:'{}' }", externalEventDTO.getType(), externalEventDTO.getBusinessDate());
|
||||
log.debug(externalEventDTO.toString());
|
||||
});
|
||||
}
|
||||
|
||||
public void verifyBusinessEvents(BusinessEvent... businessEvents) {
|
||||
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||
logBusinessEvents(allExternalEvents);
|
||||
Assertions.assertNotNull(businessEvents);
|
||||
Assertions.assertNotNull(allExternalEvents);
|
||||
Assertions.assertTrue(businessEvents.length <= allExternalEvents.size(),
|
||||
"Expected business event count is less than actual. Expected: " + businessEvents.length + " Actual: "
|
||||
+ allExternalEvents.size());
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN, Locale.ENGLISH);
|
||||
for (BusinessEvent businessEvent : businessEvents) {
|
||||
long count = allExternalEvents.stream().filter(externalEvent -> {
|
||||
Object summaryRes = externalEvent.getPayLoad().get("summary");
|
||||
Object statusRes = externalEvent.getPayLoad().get("status");
|
||||
Map<String, Object> summary = summaryRes instanceof Map ? (Map<String, Object>) summaryRes : Map.of();
|
||||
Map<String, Object> status = statusRes instanceof Map ? (Map<String, Object>) statusRes : Map.of();
|
||||
var principalDisbursed = summary.get("principalDisbursed");
|
||||
|
||||
var principalOutstanding = summary.get("principalOutstanding");
|
||||
var businessDate = LocalDate.parse(businessEvent.getBusinessDate(), formatter);
|
||||
Double statusId = (Double) status.get("id");
|
||||
return Objects.equals(externalEvent.getType(), businessEvent.getType())
|
||||
&& Objects.equals(externalEvent.getBusinessDate(), businessDate)
|
||||
&& Objects.equals(statusId, businessEvent.getStatusId().doubleValue())
|
||||
&& Objects.equals(principalDisbursed, businessEvent.getPrincipalDisbursed())
|
||||
&& Objects.equals(principalOutstanding, businessEvent.getPrincipalOutstanding());
|
||||
}).count();
|
||||
Assertions.assertEquals(1, count, "Expected business event not found " + businessEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class BusinessEvent {
|
||||
|
||||
private String type;
|
||||
private String businessDate;
|
||||
private Integer statusId;
|
||||
private Double principalDisbursed;
|
||||
private Double principalOutstanding;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user