This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 9bf365b62d8dc39b6f898ccaccd10d1e3d7ea749 Author: Rustam Zeinalov <[email protected]> AuthorDate: Fri Mar 28 19:08:06 2025 +0100 FINERACT-2226: added e2e tests for validation of progressive loan charge-off reverse replay events fix --- .../fineract/test/messaging/EventAssertion.java | 24 ++++++- .../event/loan/transaction/BulkBusinessEvent.java | 45 +++++++++++++ .../fineract/test/stepdef/loan/LoanStepDef.java | 12 +++- .../test/resources/features/LoanChargeOff.feature | 74 ++++++++++++++++++++++ ...nWritePlatformServiceJpaRepositoryImplTest.java | 3 + 5 files changed, 156 insertions(+), 2 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java index 770037b3a1..dd06329624 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java @@ -25,6 +25,7 @@ import java.math.BigDecimal; import java.time.Duration; import java.time.LocalDate; import java.util.function.Function; +import java.util.function.Predicate; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -80,7 +81,10 @@ public class EventAssertion { T event = eventFactory.create(eventClazz); try { await().atMost(Duration.ofSeconds(eventProperties.getEventWaitTimeoutInSec())).until(() -> { - return eventStore.existsEventById(event, id); + if (id == null) { + return !eventStore.findByType(event).isEmpty(); + } + return eventStore.findByType(event).stream().anyMatch(em -> event.getIdExtractor().apply(em.getData()).equals(id)); }); String receivedEventsLogParam = eventStore.getReceivedEvents().stream().map(LoggedEvent::new).map(LoggedEvent::toString) @@ -105,6 +109,24 @@ public class EventAssertion { return new EventAssertionBuilder<>(eventMessage); } + public <R, T extends Event<R>> void assertEventNotRaised(Class<T> eventClazz, Predicate<? super EventMessage<R>> filter) { + if (eventProperties.isEventVerificationDisabled()) { + return; + } + T event = eventFactory.create(eventClazz); + try { + await().atMost(Duration.ofSeconds(eventProperties.getEventWaitTimeoutInSec())) + .until(() -> eventStore.findByType(event).stream().anyMatch(filter)); + + String receivedEventsLogParam = eventStore.getReceivedEvents().stream().map(LoggedEvent::new).map(LoggedEvent::toString) + .reduce("", (s, e) -> format("%s%s%n", s, e)); + Assertions.fail("%s has been received, but it was unexpected. Events received but not verified: %s", event.getEventName(), + receivedEventsLogParam); + } catch (ConditionTimeoutException e) { + // This is the expected outcome here! + } + } + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class EventAssertionBuilder<R> { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/BulkBusinessEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/BulkBusinessEvent.java new file mode 100644 index 0000000000..fa01be293a --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/BulkBusinessEvent.java @@ -0,0 +1,45 @@ +/** + * 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.test.messaging.event.loan.transaction; + +import java.util.function.Function; +import org.apache.fineract.avro.BulkMessagePayloadV1; +import org.apache.fineract.test.messaging.event.Event; + +public class BulkBusinessEvent implements Event<BulkMessagePayloadV1> { + + public static final String TYPE = "BulkBusinessEvent"; + + @Override + public String getEventName() { + return TYPE; + } + + @Override + public Class<BulkMessagePayloadV1> getDataClass() { + return BulkMessagePayloadV1.class; + } + + // not implemented + @Override + public Function<BulkMessagePayloadV1, Long> getIdExtractor() { + throw new UnsupportedOperationException("Not implemented"); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 282000e219..7c17076135 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -127,6 +127,7 @@ import org.apache.fineract.test.messaging.config.EventProperties; import org.apache.fineract.test.messaging.event.EventCheckHelper; import org.apache.fineract.test.messaging.event.loan.LoanRescheduledDueAdjustScheduleEvent; import org.apache.fineract.test.messaging.event.loan.LoanStatusChangedEvent; +import org.apache.fineract.test.messaging.event.loan.transaction.BulkBusinessEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanAdjustTransactionBusinessEvent; @@ -2337,7 +2338,8 @@ public class LoanStepDef extends AbstractStepDef { List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions(); GetLoansLoanIdTransactions accrualTransaction = transactions.stream() - .filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual".equals(t.getType().getValue())).findFirst() + .filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual".equals(t.getType().getValue())) + .reduce((first, second) -> second) .orElseThrow(() -> new IllegalStateException(String.format("No Accrual transaction found on %s", date))); Long accrualTransactionId = accrualTransaction.getId(); @@ -2378,6 +2380,11 @@ public class LoanStepDef extends AbstractStepDef { eventAssertion.assertEventRaised(LoanChargeAdjustmentPostBusinessEvent.class, loadTransaction.getId()); } + @Then("BulkBusinessEvent is not raised on {string}") + public void checkLoanBulkBusinessEventNotCreatedBusinessEvent(String date) { + eventAssertion.assertEventNotRaised(BulkBusinessEvent.class, em -> FORMATTER.format(em.getBusinessDate()).equals(date)); + } + @Then("LoanAccrualTransactionCreatedBusinessEvent is not raised on {string}") public void checkLoanAccrualTransactionNotCreatedBusinessEvent(String date) throws IOException { Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); @@ -2390,6 +2397,9 @@ public class LoanStepDef extends AbstractStepDef { assertThat(transactions).as("Unexpected Accrual activity transaction found on %s", date) .noneMatch(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue())); + + eventAssertion.assertEventNotRaised(LoanAccrualTransactionCreatedBusinessEvent.class, + em -> FORMATTER.format(em.getBusinessDate()).equals(date)); } @Then("{string} transaction on {string} got reverse-replayed on {string}") diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature index 55c726b1d8..488137934f 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature @@ -891,6 +891,80 @@ Feature: Charge-off | 01 June 2023 | Charge-off | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 | Then On Loan Transactions tab the "Repayment" Transaction with date "01 March 2023" is reverted + @TestRailId:C3568 + Scenario: Verify that charge-off is reversed/replayed if repayment is reversed after the charge-off with COB process + When Admin sets the business date to "21 February 2025" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_ACCRUAL_ACTIVITY_POSTING | 21 February 2024 | 500 | 9.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "21 January 2025" with "500" amount and expected disbursement date on "21 January 2025" + And Admin successfully disburse the loan on "21 January 2025" with "500" EUR transaction amount + When Admin sets the business date to "23 February 2025" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 21 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 21 February 2025 | | 334.71 | 165.29 | 4.16 | 0.0 | 0.0 | 169.45 | 0.0 | 0.0 | 0.0 | 169.45 | + | 2 | 28 | 21 March 2025 | | 168.14 | 166.57 | 2.88 | 0.0 | 0.0 | 169.45 | 0.0 | 0.0 | 0.0 | 169.45 | + | 3 | 31 | 21 April 2025 | | 0.0 | 168.14 | 1.4 | 0.0 | 0.0 | 169.54 | 0.0 | 0.0 | 0.0 | 169.54 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 8.44 | 0.0 | 0 | 508.44 | 0.0 | 0.0 | 0.0 | 508.44 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Replayed | Reverted | + | 21 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 21 February 2025 | Accrual Activity | 4.16 | 0.0 | 4.16 | 0.0 | 0.0 | 0.0 | false | false | + | 22 February 2025 | Accrual | 4.31 | 0.0 | 4.31 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "21 February 2025" + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "21 February 2025" due date and 20 EUR transaction amount + When Admin sets the business date to "22 February 2025" + When Customer makes "REPAYMENT" transaction with "SCHEDULED" payment type on "22 February 2025" with 169.45 EUR transaction amount and system-generated Idempotency key + When Admin runs inline COB job for Loan + When Admin sets the business date to "24 February 2025" + And Admin does charge-off the loan on "24 February 2025" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 21 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 21 February 2025 | | 334.71 | 165.29 | 4.16 | 20.0 | 0.0 | 189.45 | 169.45 | 0.0 | 169.45 | 20.0 | + | 2 | 28 | 21 March 2025 | | 168.11 | 166.6 | 2.85 | 0.0 | 0.0 | 169.45 | 0.0 | 0.0 | 0.0 | 169.45 | + | 3 | 31 | 21 April 2025 | | 0.0 | 168.11 | 1.4 | 0.0 | 0.0 | 169.51 | 0.0 | 0.0 | 0.0 | 169.51 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 8.41 | 20.0 | 0 | 528.41 | 169.45 | 0.0 | 169.45 | 358.96 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Replayed | Reverted | + | 21 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 21 February 2025 | Accrual Activity | 24.16 | 0.0 | 4.16 | 20.0 | 0.0 | 0.0 | true | false | + | 22 February 2025 | Accrual | 4.31 | 0.0 | 4.31 | 0.0 | 0.0 | 0.0 | false | false | + | 22 February 2025 | Repayment | 169.45 | 149.45 | 0.0 | 20.0 | 0.0 | 350.55 | false | false | + | 24 February 2025 | Accrual | 0.21 | 0.0 | 0.21 | 0.0 | 0.0 | 0.0 | false | false | + | 24 February 2025 | Charge-off | 358.96 | 350.55 | 8.41 | 0.0 | 0.0 | 0.0 | false | false | + When Customer undo "1"th "Repayment" transaction made on "22 February 2025" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 21 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 21 February 2025 | | 334.71 | 165.29 | 4.16 | 20.0 | 0.0 | 189.45 | 0.0 | 0.0 | 0.0 | 189.45 | + | 2 | 28 | 21 March 2025 | | 168.19 | 166.52 | 2.93 | 0.0 | 0.0 | 169.45 | 0.0 | 0.0 | 0.0 | 169.45 | + | 3 | 31 | 21 April 2025 | | 0.0 | 168.19 | 1.4 | 0.0 | 0.0 | 169.59 | 0.0 | 0.0 | 0.0 | 169.59 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 8.49 | 20.0 | 0 | 528.49 | 0.0 | 0.0 | 0.0 | 528.49 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Replayed | Reverted | + | 21 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 21 February 2025 | Accrual Activity | 24.16 | 0.0 | 4.16 | 20.0 | 0.0 | 0.0 | true | false | + | 22 February 2025 | Accrual | 4.31 | 0.0 | 4.31 | 0.0 | 0.0 | 0.0 | false | false | + | 22 February 2025 | Repayment | 169.45 | 149.45 | 0.0 | 20.0 | 0.0 | 350.55 | false | true | + | 24 February 2025 | Accrual | 0.21 | 0.0 | 0.21 | 0.0 | 0.0 | 0.0 | false | false | + | 24 February 2025 | Accrual | 0.09 | 0.0 | 0.09 | 0.0 | 0.0 | 0.0 | false | false | + | 24 February 2025 | Charge-off | 528.49 | 500.0 | 8.49 | 20.0 | 0.0 | 0.0 | true | false | + Then On Loan Transactions tab the "Repayment" Transaction with date "22 February 2025" is reverted + And LoanAccrualTransactionCreatedBusinessEvent is raised on "24 February 2025" + And "Charge-off" transaction on "24 February 2025" got reverse-replayed on "24 February 2025" + Then BulkBusinessEvent is not raised on "24 February 2025" + @TestRailId:C2762 Scenario: Verify that charge-off is reversed/replayed if Real time repayment which was placed on a date before the charge-off is reversed after the charge-off When Admin sets the business date to "01 January 2023" diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java index 3eb52ad7a4..13a5a87254 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java @@ -112,6 +112,9 @@ public class LoanWritePlatformServiceJpaRepositoryImplTest { @Mock private LoanJournalEntryPoster journalEntryPoster; + @Mock + private LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; + @InjectMocks private LoanWritePlatformServiceJpaRepositoryImpl loanWritePlatformService;
