Skip to content

Commit

Permalink
FINERACT-1694: Improve Testing of External Business Events
Browse files Browse the repository at this point in the history
  • Loading branch information
somasorosdpc authored and adamsaghy committed Aug 7, 2024
1 parent 23b5099 commit 81bf50d
Showing 1 changed file with 241 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 81bf50d

Please sign in to comment.