Skip to content

Commit

Permalink
FINERACT-2204: [TEST] - Fix accrual activity reversal logic: prevent…
Browse files Browse the repository at this point in the history
… duplicate reverse-replay, copy external ID correctly

 Based on the work of @tamasszabo-dpc and @peter-kovacs-dpc
  • Loading branch information
adamsaghy committed Mar 7, 2025
1 parent 1d6e20b commit 3ef6de4
Show file tree
Hide file tree
Showing 4 changed files with 408 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -892,4 +892,8 @@ public static String wrongNumberOfLinesInChargeOffReasonOptions(final int actual
"Number of lines in loan charge-off reason options is not correct. Actual value is: %d - Expected value is: %d", actual,
expected);
}

public static String wrongExternalID(String actual, String expected) {
return String.format("Wrong transaction External ID - %nActual value is: %s %nExpected value is: %s", actual, expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.format.DateTimeFormatter;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -199,8 +198,7 @@ private void loanAccountDataV1Check(Class<? extends AbstractLoanEvent> eventClaz
Long clientIdExpected = body.getClientId();
BigDecimal principalDisbursedActual = loanAccountDataV1.getSummary().getPrincipalDisbursed();
Double principalDisbursedExpectedDouble = body.getSummary().getPrincipalDisbursed();
BigDecimal principalDisbursedExpected = new BigDecimal(principalDisbursedExpectedDouble, MathContext.DECIMAL64)
.setScale(8, RoundingMode.HALF_DOWN);
BigDecimal principalDisbursedExpected = BigDecimal.valueOf(principalDisbursedExpectedDouble);
String actualDisbursementDateActual = loanAccountDataV1.getTimeline().getActualDisbursementDate();
String actualDisbursementDateExpected = FORMATTER_EVENTS.format(body.getTimeline().getActualDisbursementDate());
String currencyCodeActual = loanAccountDataV1.getSummary().getCurrency().getCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2387,8 +2387,9 @@ public void checkLoanAccrualTransactionNotCreatedBusinessEvent(String date) thro
.noneMatch(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue()));
}

@Then("LoanAdjustTransactionBusinessEvent is raised for the origin of Accrual Activity on {string} but not raised for the replayed one")
public void checkLoanAdjustTransactionBusinessEvent(String date) throws IOException {
@Then("{string} transaction on {string} got reverse-replayed on {string}")
public void checkLoanAdjustTransactionBusinessEvent(String transactionType, String transactionDate, String submittedOnDate)
throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();

Expand All @@ -2398,20 +2399,26 @@ public void checkLoanAdjustTransactionBusinessEvent(String date) throws IOExcept
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();

GetLoansLoanIdTransactions loadTransaction = transactions.stream()
.filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("No Accrual Activity transaction found on %s", date)));
Long replayedTransactionId = loadTransaction.getId();
.filter(t -> transactionDate.equals(FORMATTER.format(t.getDate())) && transactionType.equals(t.getType().getValue()))
.findFirst().orElseThrow(
() -> new IllegalStateException(String.format("No %s transaction found on %s", transactionType, transactionDate)));

Set<GetLoansLoanIdLoanTransactionRelation> transactionRelations = loadTransaction.getTransactionRelations();
Long originalTransactionId = transactionRelations.stream().map(GetLoansLoanIdLoanTransactionRelation::getToLoanTransaction)
.filter(Objects::nonNull).findFirst().get();
.filter(Objects::nonNull).findFirst()
.orElseThrow(() -> new IllegalStateException("Transaction was reversed, but not replayed!"));

eventAssertion.assertEventRaised(LoanAdjustTransactionBusinessEvent.class, originalTransactionId);
eventAssertion.assertEventNotRaised(LoanAdjustTransactionBusinessEvent.class, replayedTransactionId);
// Check whether reverse-replay event got occurred
eventAssertion.assertEvent(LoanAdjustTransactionBusinessEvent.class, originalTransactionId).extractingData(
e -> e.getNewTransactionDetail() != null && e.getNewTransactionDetail().getId().equals(loadTransaction.getId()));
// Check whether there was just ONE event related to this transaction
eventAssertion.assertEventNotRaised(LoanAdjustTransactionBusinessEvent.class, originalTransactionId);
assertThat(FORMATTER.format(loadTransaction.getSubmittedOnDate()))
.as("Loan got replayed on %s", loadTransaction.getSubmittedOnDate()).isEqualTo(submittedOnDate);
}

@Then("LoanAdjustTransactionBusinessEvent is not raised on {string}")
public void checkLoanAdjustTransactionBusinessEventNotCreated(String date) throws IOException {
@When("Save external ID of {string} transaction made on {string} as {string}")
public void saveExternalIdForTransaction(String transactionName, String transactionDate, String externalIdKey) throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();

Expand All @@ -2420,12 +2427,19 @@ public void checkLoanAdjustTransactionBusinessEventNotCreated(String date) throw

List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();

assertThat(transactions).as("Unexpected Accrual Adjustment transaction found on %s", date)
.noneMatch(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Adjustment".equals(t.getType().getValue()));
GetLoansLoanIdTransactions loadTransaction = transactions.stream()
.filter(t -> transactionDate.equals(FORMATTER.format(t.getDate())) && transactionName.equals(t.getType().getValue()))
.findFirst().orElseThrow(
() -> new IllegalStateException(String.format("No %s transaction found on %s", transactionName, transactionDate)));

String externalId = loadTransaction.getExternalId();
testContext().set(externalIdKey, externalId);
log.debug("Transaction external ID: {} saved to testContext", externalId);
}

@Then("External ID for the replayed Accrual Activity on {string} is present but is null for the original transaction")
public void checkExternalIdForReplayedAccrualActivity(String date) throws IOException {
@Then("External ID of replayed {string} on {string} is matching with {string}")
public void checkExternalIdForReplayedAccrualActivity(String transactionType, String transactionDate, String savedExternalIdKey)
throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();

Expand All @@ -2434,19 +2448,20 @@ public void checkExternalIdForReplayedAccrualActivity(String date) throws IOExce

List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();

GetLoansLoanIdTransactions loadTransaction = transactions.stream()
.filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("No Accrual Activity transaction found on %s", date)));
Long replayedTransactionId = loadTransaction.getId();
GetLoansLoanIdTransactions transactionDetails = transactions.stream()
.filter(t -> transactionDate.equals(FORMATTER.format(t.getDate())) && transactionType.equals(t.getType().getValue()))
.findFirst().orElseThrow(
() -> new IllegalStateException(String.format("No %s transaction found on %s", transactionType, transactionDate)));

Set<GetLoansLoanIdLoanTransactionRelation> transactionRelations = loadTransaction.getTransactionRelations();
Set<GetLoansLoanIdLoanTransactionRelation> transactionRelations = transactionDetails.getTransactionRelations();
Long originalTransactionId = transactionRelations.stream().map(GetLoansLoanIdLoanTransactionRelation::getToLoanTransaction)
.filter(Objects::nonNull).findFirst().get();
.filter(Objects::nonNull).findFirst()
.orElseThrow(() -> new IllegalStateException("Transaction was reversed, but not replayed!"));

Response<GetLoansLoanIdTransactionsTransactionIdResponse> replayedTransaction = loanTransactionsApi
.retrieveTransaction(loanId, replayedTransactionId, "").execute();
assertNotNull(String.format("Replayed transaction external id is null %n%s", replayedTransaction.body()),
replayedTransaction.body().getExternalId());
String externalIdExpected = testContext().get(savedExternalIdKey).toString();
String externalIdActual = transactionDetails.getExternalId();
assertThat(externalIdActual).as(ErrorMessageHelper.wrongExternalID(externalIdActual, externalIdExpected))
.isEqualTo(externalIdExpected);

Response<GetLoansLoanIdTransactionsTransactionIdResponse> originalTransaction = loanTransactionsApi
.retrieveTransaction(loanId, originalTransactionId, "").execute();
Expand Down
Loading

0 comments on commit 3ef6de4

Please sign in to comment.