Skip to content

Commit

Permalink
FINERACT-2081: Validation error in mandatory loan account parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Jose Alberto Hernandez committed Sep 23, 2024
1 parent 2d0c8db commit 040b853
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1056,4 +1056,12 @@ public void throwValidationErrors() throws PlatformApiDataValidationException {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}

public static ApiParameterError buildValidationParameterApiError(final String resource, final String parameterName,
final String errorCode, final String errorMessage, final Object... defaultUserMessageArgs) {
final String validationErrorCode = "validation.msg." + resource + "." + parameterName + errorCode;
String defaultEnglishMessage = "The parameter `" + parameterName + "` " + errorMessage;
return ApiParameterError.parameterError(validationErrorCode, defaultEnglishMessage, parameterName, defaultUserMessageArgs);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ public void createFullyCustomizedLoanFixedLength(int fixedLength, DataTable tabl

@When("Admin creates a fully customized loan with Advanced payment allocation and with product no Advanced payment allocation set results an error:")
public void createFullyCustomizedLoanNoAdvancedPaymentError(DataTable table) throws IOException {
int errorCodeExpected = 400;
int errorCodeExpected = 403;
String errorMessageExpected = "Failed data validation due to: strategy.cannot.be.advanced.payment.allocation.if.not.configured.";

List<List<String>> data = table.asLists();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ private void validateForCreate(final JsonElement element) {
boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();

final Long productId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.productIdParameterName, element);
if (productId == null) {
throwMandatoryParameterError(LoanApiConstants.productIdParameterName);
}
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));

Expand All @@ -257,7 +260,6 @@ private void validateForCreate(final JsonElement element) {
final Group group = groupId != null ? this.groupRepository.findOneWithNotFoundDetection(groupId) : null;

validateClientOrGroup(client, group, productId);

validateOrThrow("loan", baseDataValidator -> {
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
Expand Down Expand Up @@ -511,7 +513,6 @@ private void validateForCreate(final JsonElement element) {

final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName,
element);

baseDataValidator.reset().parameter(LoanApiConstants.submittedOnDateParameterName).value(submittedOnDate).notNull();

if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnNoteParameterName, element)) {
Expand All @@ -523,8 +524,8 @@ private void validateForCreate(final JsonElement element) {

final String transactionProcessingStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);

validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct, baseDataValidator);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();

validateLinkedSavingsAccount(element, baseDataValidator);

Expand Down Expand Up @@ -744,12 +745,11 @@ private void validateForCreate(final JsonElement element) {
loanScheduleValidator.validateDownPaymentAttribute(loanProduct.getLoanProductRelatedDetail().isEnableDownPayment(), element);

checkForProductMixRestrictions(element);
validateSubmittedOnDate(element, null, null, loanProduct);
validateDisbursementDetails(loanProduct, element);
validateCollateral(element);
// validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(expectedDisbursementDate);
Long officeId = client != null ? client.getOffice().getId() : group.getOffice().getId();
Long officeId = resolveOfficeId(client, group);
validateDisbursementDateIsOnHoliday(expectedDisbursementDate, officeId);
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
Expand All @@ -759,6 +759,12 @@ private void validateForCreate(final JsonElement element) {
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
}
});

validateSubmittedOnDate(element, null, null, loanProduct);

final String transactionProcessingStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct);
}

private void validateBorrowerCycle(JsonElement element, LoanProduct loanProduct, Long clientId, Long groupId,
Expand Down Expand Up @@ -928,7 +934,7 @@ public void validateForModify(final JsonCommand command, final Loan loan) {
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
// Validating whether the processor is existing
validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct, baseDataValidator);
validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct);
}

if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
Expand Down Expand Up @@ -1410,7 +1416,7 @@ public void validateForModify(final JsonCommand command, final Loan loan) {

// validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(expectedDisbursementDate);
Long officeId = client != null ? client.getOffice().getId() : group.getOffice().getId();
final Long officeId = resolveOfficeId(client, group);
validateDisbursementDateIsOnHoliday(expectedDisbursementDate, officeId);

Integer recurringMoratoriumOnPrincipalPeriods = loan.getLoanProductRelatedDetail().getRecurringMoratoriumOnPrincipalPeriods();
Expand Down Expand Up @@ -1747,18 +1753,13 @@ private void officeSpecificLoanProductValidation(final Long productId, final Lon
}
}

private void validateTransactionProcessingStrategy(final String transactionProcessingStrategy, final LoanProduct loanProduct,
final DataValidatorBuilder baseDataValidator) {

baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
private void validateTransactionProcessingStrategy(final String transactionProcessingStrategy, final LoanProduct loanProduct) {

// TODO: Review exceptions
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(loanProduct.getTransactionProcessingStrategyCode())
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName).failWithCode(
"strategy.cannot.be.advanced.payment.allocation.if.not.configured",
throw new GeneralPlatformDomainRuleException("strategy.cannot.be.advanced.payment.allocation.if.not.configured",
"Loan transaction processing strategy cannot be Advanced Payment Allocation Strategy if it's not configured on loan product");
} else {
// PROGRESSIVE: Repayment strategy MUST be only "advanced payment allocation"
Expand Down Expand Up @@ -2127,4 +2128,22 @@ public static void validateOrThrow(String resource, Consumer<DataValidatorBuilde
dataValidationErrors);
}
}

public Long resolveOfficeId(Client client, Group group) {
if (client != null) {
return client.getOffice().getId();
}
if (group != null) {
return group.getOffice().getId();
}
return null;
}

private void throwMandatoryParameterError(final String parameterName) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
dataValidationErrors
.add(DataValidatorBuilder.buildValidationParameterApiError("loans", parameterName, ".cannot.be.blank", "is mandatory.", 0));
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3784,8 +3784,7 @@ public void uc130() {
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name())));

assertEquals(403, exception.getResponse().code());
assertTrue(exception.getMessage()
.contains("error.msg.loan.repayment.strategy.can.not.be.different.than.advanced.payment.allocation"));
assertTrue(exception.getMessage().contains("error.msg.fixed.length.only.supported.for.advanced.payment.allocation"));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public void loanApplicationShouldFailIfTransactionProcessingStrategyIsAdvancedPa
log.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}",
loanProductId);

loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode400);
loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403);
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
Expand All @@ -169,8 +169,7 @@ public void loanApplicationShouldFailIfTransactionProcessingStrategyIsAdvancedPa
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.build(clientId.toString(), loanProductId.toString(), null);
List<HashMap> error = (List<HashMap>) loanTransactionHelper.createLoanAccount(loanApplicationJSON, CommonConstants.RESPONSE_ERROR);
assertEquals(
"validation.msg.loan.transactionProcessingStrategyCode.strategy.cannot.be.advanced.payment.allocation.if.not.configured",
assertEquals("strategy.cannot.be.advanced.payment.allocation.if.not.configured",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.fineract.integrationtests;

import static org.junit.jupiter.api.Assertions.assertEquals;

import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
Expand Down Expand Up @@ -172,6 +174,94 @@ public void uc2() {
});
}

// uc3: Negative Test: Loan application without required parameters
// 1. Create a Loan product
// 2. Submit Loan application without required parameters
@Test
public void uc3() {
String operationDate = "15 August 2024";
runAt(operationDate, () -> {

LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
PostLoanProductsResponse loanProductResponse = loanProductHelper
.createLoanProduct(createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()));
// Product Id null
final PostLoansRequest applicationRequest01 = applyLoanRequest(client.getClientId(), null, operationDate, 100.0, 5)
.numberOfRepayments(6)//
.loanTermFrequency(6)//
.loanTermFrequencyType(2)//
.repaymentEvery(1)//
.repaymentFrequencyType(2)//
;//
CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.applyLoan(applicationRequest01));
assertEquals(400, callFailedRuntimeException.getResponse().code());
Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("The parameter `productId` is mandatory."));

// Transaction Processing Strategy Code null
final PostLoansRequest applicationRequest02 = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(),
operationDate, 100.0, 5).numberOfRepayments(6)//
.loanTermFrequency(6)//
.loanTermFrequencyType(2)//
.transactionProcessingStrategyCode(null)//
.repaymentEvery(1)//
.repaymentFrequencyType(2)//
;//

callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.applyLoan(applicationRequest02));
assertEquals(400, callFailedRuntimeException.getResponse().code());
Assertions.assertTrue(
callFailedRuntimeException.getMessage().contains("The parameter `transactionProcessingStrategyCode` is mandatory."));

final PostLoansRequest applicationRequest03 = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(),
operationDate, 100.0, 5).numberOfRepayments(6)//
.clientId(null) //
.loanTermFrequency(6)//
.loanTermFrequencyType(2)//
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
.repaymentEvery(1)//
.repaymentFrequencyType(2)//
;//
callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.applyLoan(applicationRequest03));
assertEquals(400, callFailedRuntimeException.getResponse().code());
Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("The parameter `clientId` is mandatory."));

// Submitted Date null
final PostLoansRequest applicationRequest04 = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(),
operationDate, 100.0, 5).numberOfRepayments(6)//
.submittedOnDate(null) //
.loanTermFrequency(6)//
.loanTermFrequencyType(2)//
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
.repaymentEvery(1)//
.repaymentFrequencyType(2)//
;//
callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.applyLoan(applicationRequest04));
assertEquals(400, callFailedRuntimeException.getResponse().code());
Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("The parameter `submittedOnDate` is mandatory."));

// Expected disbursement Date null
final PostLoansRequest applicationRequest05 = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(),
operationDate, 100.0, 5).numberOfRepayments(6)//
.expectedDisbursementDate(null) //
.loanTermFrequency(6)//
.loanTermFrequencyType(2)//
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
.repaymentEvery(1)//
.repaymentFrequencyType(2)//
;//
callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.applyLoan(applicationRequest05));
assertEquals(400, callFailedRuntimeException.getResponse().code());
Assertions
.assertTrue(callFailedRuntimeException.getMessage().contains("The parameter `expectedDisbursementDate` is mandatory."));
});
}

private static Integer createLoanProduct(final String principal, final String repaymentAfterEvery, final String numberOfRepayments,
boolean downPaymentEnabled, String downPaymentPercentage, boolean autoPayForDownPayment, LoanScheduleType loanScheduleType,
LoanScheduleProcessingType loanScheduleProcessingType, final Account... accounts) {
Expand Down

0 comments on commit 040b853

Please sign in to comment.