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;
 

Reply via email to