Skip to content

Commit 5e96b4d

Browse files
ruzeynalovoleksii-novikov-onix
authored andcommitted
FINERACT-2455: Added e2e tests covering WC delinquency RESCHEDULE action to change minimum payment amount and frequency
1 parent d89b07d commit 5e96b4d

2 files changed

Lines changed: 457 additions & 48 deletions

File tree

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

Lines changed: 122 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@
3030
import java.time.format.DateTimeFormatter;
3131
import java.util.List;
3232
import java.util.Map;
33+
import java.util.Optional;
34+
import java.util.function.Consumer;
35+
import java.util.function.Predicate;
3336
import lombok.RequiredArgsConstructor;
3437
import lombok.extern.slf4j.Slf4j;
3538
import org.apache.fineract.client.feign.FineractFeignClient;
39+
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
3640
import org.apache.fineract.client.models.DelinquencyBucketRequest;
3741
import org.apache.fineract.client.models.MinimumPaymentPeriodAndRule;
3842
import org.apache.fineract.client.models.PostAllowAttributeOverrides;
@@ -94,17 +98,7 @@ public void createWcDelinquencyBucket(final int frequency, final String frequenc
9498

9599
@When("Admin creates WC delinquency reschedule action with minimumPayment {int} and frequency {int} {word}")
96100
public void createRescheduleAction(final int minimumPayment, final int frequency, final String frequencyType) {
97-
final Long loanId = getLoanId();
98-
final PostWorkingCapitalLoansDelinquencyActionRequest request = buildRescheduleRequest(new BigDecimal(minimumPayment), frequency,
99-
frequencyType);
100-
log.info("Creating RESCHEDULE action for WC loan {}: minimumPayment={}, frequency={} {}", loanId, minimumPayment, frequency,
101-
frequencyType);
102-
103-
final PostWorkingCapitalLoansDelinquencyActionResponse result = ok(
104-
() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().createDelinquencyAction(loanId, request));
105-
assertThat(result).isNotNull();
106-
assertThat(result.getResourceId()).isNotNull();
107-
log.info("RESCHEDULE action created with id={}", result.getResourceId());
101+
createRescheduleActionInternal(new BigDecimal(minimumPayment), frequency, frequencyType);
108102
}
109103

110104
@Then("Admin fails to create WC delinquency reschedule action with minimumPayment {int} and frequency {int} {word}")
@@ -118,6 +112,22 @@ public void failToCreateRescheduleAction(final int minimumPayment, final int fre
118112
fail(() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().createDelinquencyAction(loanId, request));
119113
}
120114

115+
@Then("Admin fails to create WC delinquency reschedule action with minimumPayment {int} and frequency {int} {word} with error containing {string}")
116+
public void failToCreateRescheduleActionWithMessage(final int minimumPayment, final int frequency, final String frequencyType,
117+
final String expectedMessage) {
118+
final Long loanId = getLoanId();
119+
final PostWorkingCapitalLoansDelinquencyActionRequest request = buildRescheduleRequest(new BigDecimal(minimumPayment), frequency,
120+
frequencyType);
121+
log.info(
122+
"Attempting to create RESCHEDULE action for WC loan {} (expecting HTTP 400 and message '{}'): minimumPayment={}, frequency={} {}",
123+
loanId, expectedMessage, minimumPayment, frequency, frequencyType);
124+
125+
final CallFailedRuntimeException exception = fail(
126+
() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().createDelinquencyAction(loanId, request));
127+
assertThat(exception.getStatus()).as("HTTP status code").isEqualTo(400);
128+
assertThat(exception.getDeveloperMessage()).as("Developer message").contains(expectedMessage);
129+
}
130+
121131
@Then("WC loan delinquency range schedule has the following periods:")
122132
public void verifyPeriods(final DataTable table) {
123133
final Long loanId = getLoanId();
@@ -128,39 +138,115 @@ public void verifyPeriods(final DataTable table) {
128138
assertThat(periods).as("Number of periods").hasSize(expectedRows.size());
129139

130140
for (int i = 0; i < expectedRows.size(); i++) {
131-
final Map<String, String> expected = expectedRows.get(i);
132141
final WorkingCapitalLoanDelinquencyRangeScheduleData actual = periods.get(i);
133-
final String periodLabel = "Period " + (i + 1);
134-
135-
assertThat(actual.getPeriodNumber()).as(periodLabel + " periodNumber")
136-
.isEqualTo(Integer.parseInt(expected.get("periodNumber")));
137-
assertThat(actual.getFromDate()).as(periodLabel + " fromDate")
138-
.isEqualTo(LocalDate.parse(expected.get("fromDate"), DATE_FORMAT));
139-
assertThat(actual.getToDate()).as(periodLabel + " toDate").isEqualTo(LocalDate.parse(expected.get("toDate"), DATE_FORMAT));
140-
assertThat(actual.getExpectedAmount()).as(periodLabel + " expectedAmount")
141-
.isEqualByComparingTo(new BigDecimal(expected.get("expectedAmount")));
142-
assertThat(actual.getPaidAmount()).as(periodLabel + " paidAmount")
143-
.isEqualByComparingTo(new BigDecimal(expected.get("paidAmount")));
144-
assertThat(actual.getOutstandingAmount()).as(periodLabel + " outstandingAmount")
145-
.isEqualByComparingTo(new BigDecimal(expected.get("outstandingAmount")));
146-
147-
final String criteriaMetStr = expected.get("minPaymentCriteriaMet");
148-
if (criteriaMetStr == null || criteriaMetStr.isBlank()) {
149-
assertThat(actual.getMinPaymentCriteriaMet()).as(periodLabel + " minPaymentCriteriaMet").isNull();
150-
} else {
151-
assertThat(actual.getMinPaymentCriteriaMet()).as(periodLabel + " minPaymentCriteriaMet")
152-
.isEqualTo(Boolean.parseBoolean(criteriaMetStr));
153-
}
142+
final int periodNumber = i + 1;
143+
expectedRows.get(i).forEach((field, value) -> verifyFullScheduleField(actual, field, value, periodNumber));
154144
}
155145
}
156146

157147
@Then("WC loan delinquency actions contain {int} action(s)")
158148
public void verifyActionCount(final int count) {
159149
final Long loanId = getLoanId();
160-
final List<WorkingCapitalLoanDelinquencyActionData> actions = ok(
161-
() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().retrieveDelinquencyActions(loanId));
150+
assertThat(retrieveDelinquencyActions(loanId)).hasSize(count);
151+
}
152+
153+
@Then("WC loan has both PAUSE and RESCHEDULE delinquency actions")
154+
public void verifyBothPauseAndRescheduleActions() {
155+
final Long loanId = getLoanId();
156+
final List<WorkingCapitalLoanDelinquencyActionData> actions = retrieveDelinquencyActions(loanId);
157+
assertThat(actions.stream().map(a -> a.getAction().name()).toList()).as("Should contain both PAUSE and RESCHEDULE")
158+
.contains("PAUSE", "RESCHEDULE");
159+
}
160+
161+
@Then("WC loan last delinquency action has the following data:")
162+
public void verifyLastActionContent(final DataTable table) {
163+
final Long loanId = getLoanId();
164+
final List<WorkingCapitalLoanDelinquencyActionData> actions = retrieveDelinquencyActions(loanId);
165+
assertThat(actions).as("Actions should not be empty").isNotEmpty();
166+
167+
final WorkingCapitalLoanDelinquencyActionData last = actions.get(actions.size() - 1);
168+
final List<Map<String, String>> rows = table.asMaps();
169+
assertThat(rows).as("Expected exactly 1 data row").hasSize(1);
170+
rows.get(0).forEach((field, value) -> verifyActionField(last, field, value));
171+
}
172+
173+
@Then("WC loan delinquency range schedule periods have specific data:")
174+
public void verifySpecificPeriods(final DataTable table) {
175+
final Long loanId = getLoanId();
176+
final List<WorkingCapitalLoanDelinquencyRangeScheduleData> periods = ok(
177+
() -> fineractFeignClient.workingCapitalLoanDelinquencyRangeSchedule().retrieveDelinquencyRangeSchedule(loanId));
178+
179+
for (final Map<String, String> expected : table.asMaps()) {
180+
final int periodNumber = Integer.parseInt(expected.get("periodNumber"));
181+
final WorkingCapitalLoanDelinquencyRangeScheduleData actual = periods.stream()
182+
.filter(p -> p.getPeriodNumber().equals(periodNumber)).findFirst().orElse(null);
183+
assertThat(actual).as("Period %d should exist", periodNumber).isNotNull();
184+
expected.forEach((field, value) -> verifyFullScheduleField(actual, field, value, periodNumber));
185+
}
186+
}
187+
188+
@When("Admin creates WC delinquency reschedule action with decimal minimumPayment {string} and frequency {int} {word}")
189+
public void createRescheduleActionWithDecimal(final String minimumPayment, final int frequency, final String frequencyType) {
190+
createRescheduleActionInternal(new BigDecimal(minimumPayment), frequency, frequencyType);
191+
}
192+
193+
private void createRescheduleActionInternal(final BigDecimal minimumPayment, final int frequency, final String frequencyType) {
194+
final Long loanId = getLoanId();
195+
final PostWorkingCapitalLoansDelinquencyActionRequest request = buildRescheduleRequest(minimumPayment, frequency, frequencyType);
196+
log.info("Creating RESCHEDULE action for WC loan {}: minimumPayment={}, frequency={} {}", loanId, minimumPayment, frequency,
197+
frequencyType);
198+
199+
final PostWorkingCapitalLoansDelinquencyActionResponse result = ok(
200+
() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().createDelinquencyAction(loanId, request));
201+
assertThat(result).isNotNull();
202+
assertThat(result.getResourceId()).isNotNull();
203+
log.info("RESCHEDULE action created with id={}", result.getResourceId());
204+
}
205+
206+
private List<WorkingCapitalLoanDelinquencyActionData> retrieveDelinquencyActions(final Long loanId) {
207+
return ok(() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().retrieveDelinquencyActions(loanId));
208+
}
209+
210+
private void verifyActionField(final WorkingCapitalLoanDelinquencyActionData actual, final String field, final String expected) {
211+
switch (field) {
212+
case "action" -> assertThat(actual.getAction().name()).as("action").isEqualTo(expected);
213+
case "startDate" -> assertThat(actual.getStartDate()).as("startDate").isEqualTo(LocalDate.parse(expected, DATE_FORMAT));
214+
case "endDate" ->
215+
verifyOptionalField(expected, v -> assertThat(actual.getEndDate()).as("endDate").isEqualTo(LocalDate.parse(v, DATE_FORMAT)),
216+
() -> assertThat(actual.getEndDate()).as("endDate").isNull());
217+
case "minimumPayment" ->
218+
assertThat(actual.getMinimumPayment()).as("minimumPayment").isEqualByComparingTo(new BigDecimal(expected));
219+
case "frequency" -> assertThat(actual.getFrequency()).as("frequency").isEqualTo(Integer.parseInt(expected));
220+
case "frequencyType" -> assertThat(actual.getFrequencyType().name()).as("frequencyType").isEqualTo(expected);
221+
default -> throw new IllegalArgumentException("Unknown action field: " + field);
222+
}
223+
}
224+
225+
private void verifyFullScheduleField(final WorkingCapitalLoanDelinquencyRangeScheduleData actual, final String field,
226+
final String expected, final int periodNumber) {
227+
final String label = "Period " + periodNumber + " " + field;
228+
switch (field) {
229+
case "periodNumber" -> assertThat(actual.getPeriodNumber()).as(label).isEqualTo(Integer.parseInt(expected));
230+
case "fromDate" -> assertThat(actual.getFromDate()).as(label).isEqualTo(LocalDate.parse(expected, DATE_FORMAT));
231+
case "toDate" -> assertThat(actual.getToDate()).as(label).isEqualTo(LocalDate.parse(expected, DATE_FORMAT));
232+
case "expectedAmount" -> assertThat(actual.getExpectedAmount()).as(label).isEqualByComparingTo(new BigDecimal(expected));
233+
case "paidAmount" -> assertThat(actual.getPaidAmount()).as(label).isEqualByComparingTo(new BigDecimal(expected));
234+
case "outstandingAmount" -> assertThat(actual.getOutstandingAmount()).as(label).isEqualByComparingTo(new BigDecimal(expected));
235+
case "minPaymentCriteriaMet" -> verifyOptionalField(expected,
236+
v -> assertThat(actual.getMinPaymentCriteriaMet()).as(label).isEqualTo(Boolean.parseBoolean(v)),
237+
() -> assertThat(actual.getMinPaymentCriteriaMet()).as(label).isNull());
238+
case "delinquentDays" ->
239+
verifyOptionalField(expected, v -> assertThat(actual.getDelinquentDays()).as(label).isEqualTo(Long.parseLong(v)),
240+
() -> assertThat(actual.getDelinquentDays()).as(label).isNull());
241+
case "delinquentAmount" -> verifyOptionalField(expected,
242+
v -> assertThat(actual.getDelinquentAmount()).as(label).isEqualByComparingTo(new BigDecimal(v)),
243+
() -> assertThat(actual.getDelinquentAmount()).as(label).isNull());
244+
default -> throw new IllegalArgumentException("Unknown schedule field: " + field);
245+
}
246+
}
162247

163-
assertThat(actions).hasSize(count);
248+
private void verifyOptionalField(final String expected, final Consumer<String> whenPresent, final Runnable whenAbsent) {
249+
Optional.ofNullable(expected).filter(Predicate.not(String::isBlank)).ifPresentOrElse(whenPresent, whenAbsent);
164250
}
165251

166252
private Long getLoanId() {

0 commit comments

Comments
 (0)