Skip to content

Commit 1ae3edc

Browse files
ruzeynalovadamsaghy
authored andcommitted
FINERACT-2421: added e2e test for verifying that the loan originator information is present in LoanAdjustTransactionBusinessEvent and LoanAccrualTransactionCreatedBusinessEvent
1 parent 619c751 commit 1ae3edc

2 files changed

Lines changed: 206 additions & 0 deletions

File tree

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOriginationStepDef.java

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
import io.cucumber.java.en.Then;
2828
import io.cucumber.java.en.When;
2929
import java.math.BigDecimal;
30+
import java.time.format.DateTimeFormatter;
3031
import java.util.List;
3132
import java.util.Map;
3233
import java.util.UUID;
3334
import java.util.concurrent.TimeUnit;
3435
import lombok.extern.slf4j.Slf4j;
36+
import org.apache.fineract.avro.loan.v1.LoanTransactionAdjustmentDataV1;
37+
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
3538
import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
3639
import org.apache.fineract.client.feign.FineractFeignClient;
3740
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
@@ -41,11 +44,14 @@
4144
import org.apache.fineract.client.models.GetLoanOriginatorsResponse;
4245
import org.apache.fineract.client.models.GetLoansLoanIdOriginatorData;
4346
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
47+
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
4448
import org.apache.fineract.client.models.PostClientsResponse;
4549
import org.apache.fineract.client.models.PostLoanOriginatorsRequest;
4650
import org.apache.fineract.client.models.PostLoanOriginatorsResponse;
4751
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
4852
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
53+
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
54+
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
4955
import org.apache.fineract.client.models.PostLoansOriginatorData;
5056
import org.apache.fineract.client.models.PostLoansRequest;
5157
import org.apache.fineract.client.models.PostLoansResponse;
@@ -56,6 +62,8 @@
5662
import org.apache.fineract.test.helper.ErrorMessageHelper;
5763
import org.apache.fineract.test.messaging.EventAssertion;
5864
import org.apache.fineract.test.messaging.event.loan.LoanApprovedEvent;
65+
import org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
66+
import org.apache.fineract.test.messaging.event.loan.transaction.LoanAdjustTransactionBusinessEvent;
5967
import org.apache.fineract.test.messaging.store.EventStore;
6068
import org.apache.fineract.test.stepdef.AbstractStepDef;
6169
import org.apache.fineract.test.support.TestContextKey;
@@ -65,6 +73,10 @@
6573
public class LoanOriginationStepDef extends AbstractStepDef {
6674

6775
private static final long NON_EXISTENT_ID = Long.MAX_VALUE;
76+
private static final String DATE_FORMAT = "dd MMMM yyyy";
77+
private static final String DEFAULT_LOCALE = "en";
78+
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
79+
private static final String ADJUSTED_TRANSACTION_ID = "adjustedTransactionId";
6880

6981
@Autowired
7082
private FineractFeignClient fineractClient;
@@ -603,6 +615,135 @@ public void deleteOriginatorShouldFailWithStatus(int expectedStatus) {
603615
log.info("Deleting originator {} failed with expected status {}", originatorId, expectedStatus);
604616
}
605617

618+
@When("Customer makes a repayment undo on {string} without event check")
619+
public void makeLoanRepaymentUndoWithoutEventCheck(String transactionDate) {
620+
eventStore.reset();
621+
PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
622+
long loanId = loanResponse.getLoanId();
623+
PostLoansLoanIdTransactionsResponse repaymentResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
624+
Long originalTransactionId = repaymentResponse.getResourceId();
625+
626+
PostLoansLoanIdTransactionsTransactionIdRequest repaymentUndoRequest = LoanRequestFactory.defaultRepaymentUndoRequest()
627+
.transactionDate(transactionDate).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
628+
629+
ok(() -> fineractClient.loanTransactions().adjustLoanTransaction(loanId, originalTransactionId, repaymentUndoRequest,
630+
Map.<String, Object>of()));
631+
testContext().set(ADJUSTED_TRANSACTION_ID, originalTransactionId);
632+
log.info("Repayment {} undo on loan {} (event check skipped for separate originator verification)", originalTransactionId, loanId);
633+
}
634+
635+
@When("Customer adjusts the repayment on {string} to {double} EUR without event check")
636+
public void adjustLoanRepaymentWithoutEventCheck(String transactionDate, double transactionAmount) {
637+
eventStore.reset();
638+
PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
639+
long loanId = loanResponse.getLoanId();
640+
PostLoansLoanIdTransactionsResponse repaymentResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
641+
Long originalTransactionId = repaymentResponse.getResourceId();
642+
643+
PostLoansLoanIdTransactionsTransactionIdRequest repaymentAdjustRequest = LoanRequestFactory
644+
.defaultRepaymentAdjustRequest(transactionAmount).transactionDate(transactionDate).dateFormat(DATE_FORMAT)
645+
.locale(DEFAULT_LOCALE);
646+
647+
PostLoansLoanIdTransactionsResponse repaymentAdjustmentResponse = ok(() -> fineractClient.loanTransactions()
648+
.adjustLoanTransaction(loanId, originalTransactionId, repaymentAdjustRequest, Map.<String, Object>of()));
649+
testContext().set(TestContextKey.LOAN_REPAYMENT_UNDO_RESPONSE, repaymentAdjustmentResponse);
650+
testContext().set(ADJUSTED_TRANSACTION_ID, originalTransactionId);
651+
log.info("Repayment {} adjusted to {} on loan {} (event check skipped for separate originator verification)", originalTransactionId,
652+
transactionAmount, loanId);
653+
}
654+
655+
@When("Customer reverses the waiver transaction on {string}")
656+
public void reverseWaiverTransaction(String transactionDate) {
657+
eventStore.reset();
658+
PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
659+
long loanId = loanResponse.getLoanId();
660+
661+
GetLoansLoanIdResponse loanDetails = ok(
662+
() -> fineractClient.loans().retrieveLoan(loanId, Map.<String, Object>of("associations", "transactions")));
663+
Long waiveTransactionId = loanDetails.getTransactions().stream()
664+
.filter(t -> "loanTransactionType.waiveCharges".equals(t.getType().getCode())).map(GetLoansLoanIdTransactions::getId)
665+
.findFirst().orElseThrow(() -> new IllegalStateException("Waiver transaction not found on loan " + loanId));
666+
667+
PostLoansLoanIdTransactionsTransactionIdRequest undoRequest = LoanRequestFactory.defaultRepaymentUndoRequest()
668+
.transactionDate(transactionDate).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
669+
670+
ok(() -> fineractClient.loanTransactions().adjustLoanTransaction(loanId, waiveTransactionId, undoRequest,
671+
Map.<String, Object>of()));
672+
testContext().set(ADJUSTED_TRANSACTION_ID, waiveTransactionId);
673+
log.info("Waiver transaction {} reversed on loan {} (for originator event verification)", waiveTransactionId, loanId);
674+
}
675+
676+
// --- Originator event verification steps ---
677+
678+
@Then("LoanAdjustTransactionBusinessEvent is created with originator details in {string}")
679+
public void verifyOriginatorInAdjustEvent(String nestedField) {
680+
long loanId = getLoanId();
681+
Long adjustedTransactionId = testContext().get(ADJUSTED_TRANSACTION_ID);
682+
String expectedExternalId = testContext().get(TestContextKey.ORIGINATOR_EXTERNAL_ID);
683+
684+
eventAssertion.assertEvent(LoanAdjustTransactionBusinessEvent.class, adjustedTransactionId).extractingData(adjustmentData -> {
685+
LoanTransactionDataV1 nested = resolveAdjustmentField(adjustmentData, nestedField);
686+
assertThat(nested).as("Field '%s' in LoanAdjustTransactionBusinessEvent", nestedField).isNotNull();
687+
688+
List<OriginatorDetailsV1> originators = nested.getOriginators();
689+
assertThat(originators).as("Originators in %s should not be null or empty", nestedField).isNotNull().isNotEmpty();
690+
assertThat(originators.get(0).getExternalId()).as("Originator externalId in %s", nestedField).isEqualTo(expectedExternalId);
691+
assertThat(originators.get(0).getStatus()).as("Originator status in %s", nestedField).isEqualTo("ACTIVE");
692+
return adjustmentData.getTransactionToAdjust().getId();
693+
}).isEqualTo(adjustedTransactionId);
694+
log.info("Verified originator {} in LoanAdjustTransactionBusinessEvent.{} for loan {}", expectedExternalId, nestedField, loanId);
695+
}
696+
697+
@Then("LoanAdjustTransactionBusinessEvent is created without originator details in {string}")
698+
public void verifyNoOriginatorInAdjustEvent(String nestedField) {
699+
Long adjustedTransactionId = testContext().get(ADJUSTED_TRANSACTION_ID);
700+
701+
eventAssertion.assertEvent(LoanAdjustTransactionBusinessEvent.class, adjustedTransactionId).extractingData(adjustmentData -> {
702+
LoanTransactionDataV1 nested = resolveAdjustmentField(adjustmentData, nestedField);
703+
assertThat(nested).as("Field '%s' in LoanAdjustTransactionBusinessEvent", nestedField).isNotNull();
704+
705+
List<OriginatorDetailsV1> originators = nested.getOriginators();
706+
assertThat(originators).as("Originators in %s should be null or empty", nestedField).isNullOrEmpty();
707+
return adjustmentData.getTransactionToAdjust().getId();
708+
}).isEqualTo(adjustedTransactionId);
709+
log.info("Verified no originators in LoanAdjustTransactionBusinessEvent.{}", nestedField);
710+
}
711+
712+
@Then("LoanAccrualTransactionCreatedBusinessEvent is created with originator details on {string}")
713+
public void verifyOriginatorInAccrualEvent(String date) {
714+
long loanId = getLoanId();
715+
String expectedExternalId = testContext().get(TestContextKey.ORIGINATOR_EXTERNAL_ID);
716+
717+
GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId,
718+
Map.of("staffInSelectedOfficeOnly", "false", "associations", "transactions")));
719+
GetLoansLoanIdTransactions accrualTransaction = loanDetails.getTransactions().stream()
720+
.filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual".equals(t.getType().getValue()))
721+
.reduce((first, second) -> second)
722+
.orElseThrow(() -> new IllegalStateException(String.format("No Accrual transaction found on %s", date)));
723+
724+
eventAssertion.assertEvent(LoanAccrualTransactionCreatedBusinessEvent.class, accrualTransaction.getId())
725+
.extractingData(loanTransactionDataV1 -> {
726+
List<OriginatorDetailsV1> originators = loanTransactionDataV1.getOriginators();
727+
assertThat(originators).as("Originators in LoanAccrualTransactionCreatedBusinessEvent should not be null or empty")
728+
.isNotNull().isNotEmpty();
729+
assertThat(originators.get(0).getExternalId()).as("Originator externalId in LoanAccrualTransactionCreatedBusinessEvent")
730+
.isEqualTo(expectedExternalId);
731+
assertThat(originators.get(0).getStatus()).as("Originator status in LoanAccrualTransactionCreatedBusinessEvent")
732+
.isEqualTo("ACTIVE");
733+
return loanTransactionDataV1.getId();
734+
}).isEqualTo(accrualTransaction.getId());
735+
log.info("Verified originator {} in LoanAccrualTransactionCreatedBusinessEvent on {} for loan {}", expectedExternalId, date,
736+
loanId);
737+
}
738+
739+
private LoanTransactionDataV1 resolveAdjustmentField(LoanTransactionAdjustmentDataV1 data, String field) {
740+
return switch (field) {
741+
case "transactionToAdjust" -> data.getTransactionToAdjust();
742+
case "newTransactionDetail" -> data.getNewTransactionDetail();
743+
default -> throw new IllegalArgumentException("Unknown adjustment field: " + field);
744+
};
745+
}
746+
606747
// --- Helper methods ---
607748

608749
private long getLoanId() {

fineract-e2e-tests-runner/src/test/resources/features/LoanOrigination.feature

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,68 @@ Feature: Loan Origination
269269
When Admin creates new user with "ORIGINATOR_NO_DELETE" username, "ORIGINATOR_NO_DELETE_ROLE" role name and given permissions:
270270
| READ_LOAN_ORIGINATOR |
271271
Then Created user without DELETE_LOAN_ORIGINATOR permission fails to delete the originator
272+
273+
@TestRailId:C74521
274+
Scenario: Verify that originator details are present in LoanAdjustTransactionBusinessEvent after repayment reversal
275+
When Admin sets the business date to "1 January 2025"
276+
When Admin creates a client with random data
277+
When Admin creates a new loan originator with external ID and name "Adjust Event Originator"
278+
When Admin creates a new default Loan with date: "1 January 2025"
279+
When Admin attaches the originator to the loan
280+
And Admin successfully approves the loan on "1 January 2025" with "1000" amount and expected disbursement date on "1 January 2025"
281+
When Admin successfully disburse the loan on "1 January 2025" with "1000" EUR transaction amount
282+
And Customer makes "AUTOPAY" repayment on "1 January 2025" with 500 EUR transaction amount
283+
When Customer makes a repayment undo on "1 January 2025" without event check
284+
Then LoanAdjustTransactionBusinessEvent is created with originator details in "transactionToAdjust"
285+
286+
@TestRailId:C74522
287+
Scenario: Verify that originator details are present in LoanAccrualTransactionCreatedBusinessEvent after COB runs
288+
When Admin sets the business date to "1 January 2025"
289+
When Admin creates a client with random data
290+
When Admin creates a new loan originator with external ID and name "Accrual Event Originator"
291+
When Admin creates a new default Loan with date: "1 January 2025"
292+
When Admin attaches the originator to the loan
293+
And Admin successfully approves the loan on "1 January 2025" with "1000" amount and expected disbursement date on "1 January 2025"
294+
When Admin successfully disburse the loan on "1 January 2025" with "1000" EUR transaction amount
295+
When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 January 2025" due date and 10 EUR transaction amount
296+
When Admin sets the business date to "2 January 2025"
297+
When Admin runs inline COB job for Loan
298+
Then LoanAccrualTransactionCreatedBusinessEvent is created with originator details on "01 January 2025"
299+
300+
@TestRailId:C74523
301+
Scenario: Verify that originator details are present in LoanAdjustTransactionBusinessEvent after charge waiver reversal
302+
When Admin sets the business date to "1 January 2025"
303+
When Admin creates a client with random data
304+
When Admin creates a new loan originator with external ID and name "Waiver Reversal Originator"
305+
When Admin creates a new default Loan with date: "1 January 2025"
306+
When Admin attaches the originator to the loan
307+
And Admin successfully approves the loan on "1 January 2025" with "1000" amount and expected disbursement date on "1 January 2025"
308+
When Admin successfully disburse the loan on "1 January 2025" with "1000" EUR transaction amount
309+
When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 January 2025" due date and 10 EUR transaction amount
310+
And Admin waives due date charge
311+
When Customer reverses the waiver transaction on "1 January 2025"
312+
Then LoanAdjustTransactionBusinessEvent is created with originator details in "transactionToAdjust"
313+
314+
@TestRailId:C74524
315+
Scenario: Verify that no originator details are present in LoanAdjustTransactionBusinessEvent when loan has no originator attached
316+
When Admin sets the business date to "1 January 2025"
317+
When Admin creates a client with random data
318+
When Admin creates a new default Loan with date: "1 January 2025"
319+
And Admin successfully approves the loan on "1 January 2025" with "1000" amount and expected disbursement date on "1 January 2025"
320+
When Admin successfully disburse the loan on "1 January 2025" with "1000" EUR transaction amount
321+
And Customer makes "AUTOPAY" repayment on "1 January 2025" with 500 EUR transaction amount
322+
When Customer makes a repayment undo on "1 January 2025" without event check
323+
Then LoanAdjustTransactionBusinessEvent is created without originator details in "transactionToAdjust"
324+
325+
@TestRailId:C74538
326+
Scenario: Verify that originator details are present in LoanAdjustTransactionBusinessEvent new transaction detail after repayment adjustment
327+
When Admin sets the business date to "1 January 2025"
328+
When Admin creates a client with random data
329+
When Admin creates a new loan originator with external ID and name "Adjustment Replacement Originator"
330+
When Admin creates a new default Loan with date: "1 January 2025"
331+
When Admin attaches the originator to the loan
332+
And Admin successfully approves the loan on "1 January 2025" with "1000" amount and expected disbursement date on "1 January 2025"
333+
When Admin successfully disburse the loan on "1 January 2025" with "1000" EUR transaction amount
334+
And Customer makes "AUTOPAY" repayment on "1 January 2025" with 500 EUR transaction amount
335+
When Customer adjusts the repayment on "1 January 2025" to 300 EUR without event check
336+
Then LoanAdjustTransactionBusinessEvent is created with originator details in "newTransactionDetail"

0 commit comments

Comments
 (0)