From d5686ed45ad202e29b687486c91b34772bd82dc4 Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Fri, 16 Jun 2023 18:57:59 +0200 Subject: [PATCH] FINERACT-1926: Fix request validation --- .../ExternalAssetOwnersWriteServiceImpl.java | 34 +++++ ...nitiateExternalAssetOwnerTransferTest.java | 126 +++++++++++++++--- 2 files changed, 143 insertions(+), 17 deletions(-) diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java index 1a026023b..d3db1547e 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java @@ -89,7 +89,9 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW @Transactional public CommandProcessingResult buybackLoanByLoanId(JsonCommand command) { final JsonElement json = fromApiJsonHelper.parse(command.json()); + validateBuybackRequestBody(command.json()); Long loanId = command.getLoanId(); + validateLoan(loanId); LocalDate settlementDate = getSettlementDateFromJson(json); ExternalId externalId = getTransferExternalIdFromJson(json); validateSettlementDate(settlementDate); @@ -99,6 +101,12 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW return buildResponseData(externalAssetOwnerTransfer); } + private void validateLoan(Long loanId) { + if (!loanRepository.existsById(loanId)) { + throw new LoanNotFoundException(loanId); + } + } + private void validateEffectiveTransferForSale(final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) { List effectiveTransfers = externalAssetOwnerTransferRepository .findEffectiveTransfers(externalAssetOwnerTransfer.getLoanId(), externalAssetOwnerTransfer.getSettlementDate()); @@ -244,6 +252,32 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW } } + private void validateBuybackRequestBody(String apiRequestBodyAsJson) { + final Set requestParameters = new HashSet<>( + Arrays.asList(ExternalTransferRequestParameters.SETTLEMENT_DATE, ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, + ExternalTransferRequestParameters.DATEFORMAT, ExternalTransferRequestParameters.LOCALE)); + final Type typeOfMap = new TypeToken>() { + + }.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson, requestParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loantransfer"); + final JsonElement json = fromApiJsonHelper.parse(apiRequestBodyAsJson); + + String transferExternalId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, json); + baseDataValidator.reset().parameter(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID).value(transferExternalId).ignoreIfNull() + .notExceedingLengthOf(100); + + LocalDate settlementDate = fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, json); + baseDataValidator.reset().parameter(ExternalTransferRequestParameters.SETTLEMENT_DATE).value(settlementDate).notNull(); + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors); + } + } + private LocalDate getSettlementDateFromJson(JsonElement json) { String dateFormat = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.DATEFORMAT, json); String locale = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.LOCALE, json); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java index 45d3fcb45..c662a73c0 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java @@ -146,7 +146,7 @@ public class InitiateExternalAssetOwnerTransferTest { Integer loanID = createLoanForClient(clientID); addPenaltyForLoan(loanID, "10"); - PostInitiateTransferResponse saleTransferResponse = createExternalAssetOwnerTransfer(loanID, "sale", "2020-03-02"); + PostInitiateTransferResponse saleTransferResponse = createSaleTransfer(loanID, "2020-03-02"); validateResponse(saleTransferResponse, loanID); getAndValidateExternalAssetOwnerTransferByLoan(loanID, ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-02", @@ -184,7 +184,7 @@ public class InitiateExternalAssetOwnerTransferTest { ExpectedJournalEntryData.expected((long) TRANSFER_ACCOUNT.getAccountID(), (long) JournalEntryType.CREDIT.getValue(), BigDecimal.valueOf(15767.420000), expectedDate, expectedDate)); - PostInitiateTransferResponse buybackTransferResponse = createExternalAssetOwnerTransfer(loanID, "buyback", "2020-03-03"); + PostInitiateTransferResponse buybackTransferResponse = createBuybackTransfer(loanID, "2020-03-03"); validateResponse(buybackTransferResponse, loanID); getAndValidateExternalAssetOwnerTransferByLoan(loanID, ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-02", @@ -278,10 +278,10 @@ public class InitiateExternalAssetOwnerTransferTest { Integer clientID = createClient(); Integer loanID = createLoanForClient(clientID); - createExternalAssetOwnerTransfer(loanID, "sale", "2020-03-02"); + createSaleTransfer(loanID, "2020-03-02"); CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, - () -> createExternalAssetOwnerTransfer(loanID, "sale", "2020-03-02")); + () -> createSaleTransfer(loanID, "2020-03-02")); assertTrue(exception.getMessage().contains("External asset owner transfer is already in PENDING state for this loan")); } finally { cleanUpAndRestoreBusinessDate(); @@ -302,7 +302,7 @@ public class InitiateExternalAssetOwnerTransferTest { LOAN_TRANSACTION_HELPER.makeRepayment("04 March 2020", 16000.0f, loanID); CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, - () -> createExternalAssetOwnerTransfer(loanID, "sale", "2020-03-02")); + () -> createSaleTransfer(loanID, "2020-03-02")); assertTrue(exception.getMessage().contains("Loan is not in active status")); } finally { cleanUpAndRestoreBusinessDate(); @@ -318,9 +318,9 @@ public class InitiateExternalAssetOwnerTransferTest { Integer clientID = createClient(); Integer loanID = createLoanForClient(clientID); - PostInitiateTransferResponse saleTransferResponse = createExternalAssetOwnerTransfer(loanID, "sale", "2020-03-02"); + PostInitiateTransferResponse saleTransferResponse = createSaleTransfer(loanID, "2020-03-02"); validateResponse(saleTransferResponse, loanID); - PostInitiateTransferResponse buybackTransferResponse = createExternalAssetOwnerTransfer(loanID, "buyback", "2020-03-02"); + PostInitiateTransferResponse buybackTransferResponse = createBuybackTransfer(loanID, "2020-03-02"); validateResponse(buybackTransferResponse, loanID); getAndValidateExternalAssetOwnerTransferByLoan(loanID, @@ -369,8 +369,8 @@ public class InitiateExternalAssetOwnerTransferTest { Integer clientID = createClient(); Integer loanID = createLoanForClient(clientID); - PostInitiateTransferResponse saleTransferResponse = createExternalAssetOwnerTransfer(loanID, "sale", "2020-03-04"); - PostInitiateTransferResponse buybackTransferResponse = createExternalAssetOwnerTransfer(loanID, "buyback", "2020-03-04"); + PostInitiateTransferResponse saleTransferResponse = createSaleTransfer(loanID, "2020-03-04"); + PostInitiateTransferResponse buybackTransferResponse = createBuybackTransfer(loanID, "2020-03-04"); getAndValidateExternalAssetOwnerTransferByLoan(loanID, ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), "2020-03-04", "2020-03-02", @@ -383,11 +383,11 @@ public class InitiateExternalAssetOwnerTransferTest { new BigDecimal("0.000000"))); CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, - () -> createExternalAssetOwnerTransfer(loanID, "sale", "2020-03-04")); + () -> createSaleTransfer(loanID, "2020-03-04")); assertTrue(exception.getMessage().contains("This loan cannot be sold, there is already an in progress transfer")); CallFailedRuntimeException exception2 = assertThrows(CallFailedRuntimeException.class, - () -> createExternalAssetOwnerTransfer(loanID, "buyback", "2020-03-04")); + () -> createBuybackTransfer(loanID, "2020-03-04")); assertTrue(exception2.getMessage() .contains("This loan cannot be bought back, external asset owner buyback transfer is already in progress")); } finally { @@ -395,19 +395,111 @@ public class InitiateExternalAssetOwnerTransferTest { } } + @Test + public void buybackExceptionHandling() { + try { + GlobalConfigurationHelper.manageConfigurations(REQUEST_SPEC, RESPONSE_SPEC, + GlobalConfigurationHelper.ENABLE_AUTOGENERATED_EXTERNAL_ID, true); + setInitialBusinessDate("2020-03-02"); + + CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> createBuybackTransfer(1, null)); + assertTrue(exception.getMessage().contains("The parameter `settlementDate` is mandatory.")); + + CallFailedRuntimeException exception2 = assertThrows(CallFailedRuntimeException.class, + () -> createBuybackTransfer(1, "1970-01-01")); + assertTrue(exception2.getMessage().contains("Settlement date cannot be in the past")); + + CallFailedRuntimeException exception3 = assertThrows(CallFailedRuntimeException.class, () -> { + Integer clientID = createClient(); + Integer loanID = createLoanForClient(clientID); + createSaleTransfer(loanID, "2020-03-03"); + createBuybackTransfer(loanID, "2020-03-02"); + }); + assertTrue(exception3.getMessage().contains( + "This loan cannot be bought back, settlement date is earlier than effective transfer settlement date: 2020-03-03")); + + CallFailedRuntimeException exception4 = assertThrows(CallFailedRuntimeException.class, () -> { + Integer clientID = createClient(); + Integer loanID = createLoanForClient(clientID); + createBuybackTransfer(loanID, "2020-03-03"); + }); + assertTrue(exception4.getMessage().contains("This loan cannot be bought back, it is not owned by an external asset owner")); + + CallFailedRuntimeException exception5 = assertThrows(CallFailedRuntimeException.class, + () -> createBuybackTransfer(-1, "2020-03-03")); + assertTrue(exception5.getMessage().contains("Loan with identifier -1 does not exist")); + } finally { + cleanUpAndRestoreBusinessDate(); + } + } + + @Test + public void saleExceptionHandling() { + try { + GlobalConfigurationHelper.manageConfigurations(REQUEST_SPEC, RESPONSE_SPEC, + GlobalConfigurationHelper.ENABLE_AUTOGENERATED_EXTERNAL_ID, true); + setInitialBusinessDate("2020-03-02"); + Integer clientID = createClient(); + Integer loanID = createLoanForClient(clientID); + + CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> createSaleTransfer(loanID, null)); + assertTrue(exception.getMessage().contains("The parameter `settlementDate` is mandatory.")); + + CallFailedRuntimeException exception2 = assertThrows(CallFailedRuntimeException.class, + () -> createSaleTransfer(loanID, "2020-03-02", UUID.randomUUID().toString(), null, "1.0")); + assertTrue(exception2.getMessage().contains("The parameter `ownerExternalId` is mandatory.")); + + CallFailedRuntimeException exception3 = assertThrows(CallFailedRuntimeException.class, + () -> createSaleTransfer(loanID, "2020-03-02", null, UUID.randomUUID().toString(), null)); + assertTrue(exception3.getMessage().contains("The parameter `purchasePriceRatio` is mandatory.")); + + CallFailedRuntimeException exception4 = assertThrows(CallFailedRuntimeException.class, + () -> createSaleTransfer(loanID, "1970-01-01")); + assertTrue(exception4.getMessage().contains("Settlement date cannot be in the past")); + + CallFailedRuntimeException exception5 = assertThrows(CallFailedRuntimeException.class, () -> { + createSaleTransfer(loanID, "2020-03-03"); + createBuybackTransfer(loanID, "2020-03-04"); + createSaleTransfer(loanID, "2020-03-05"); + }); + assertTrue(exception5.getMessage().contains("This loan cannot be sold, there is already an in progress transfer")); + CallFailedRuntimeException exception6 = assertThrows(CallFailedRuntimeException.class, () -> { + Integer loanID2 = createLoanForClient(clientID); + createSaleTransfer(loanID2, "2020-03-03"); + updateBusinessDateAndExecuteCOBJob("2020-03-04"); + createSaleTransfer(loanID2, "2020-03-05"); + }); + assertTrue(exception6.getMessage().contains("This loan cannot be sold, because it is owned by an external asset owner")); + } finally { + cleanUpAndRestoreBusinessDate(); + } + } + private void updateBusinessDateAndExecuteCOBJob(String date) { BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC, BUSINESS_DATE, LocalDate.parse(date)); SCHEDULER_JOB_HELPER.executeAndAwaitJob("Loan COB"); } - private PostInitiateTransferResponse createExternalAssetOwnerTransfer(Integer loanID, String command, String settlementDate) { + private PostInitiateTransferResponse createSaleTransfer(Integer loanID, String settlementDate) { String transferExternalId = UUID.randomUUID().toString(); - if (command.equals("sale")) { - ownerExternalId = UUID.randomUUID().toString(); - } - PostInitiateTransferResponse saleResponse = EXTERNAL_ASSET_OWNER_HELPER.initiateTransferByLoanId(loanID.longValue(), command, + ownerExternalId = UUID.randomUUID().toString(); + return createSaleTransfer(loanID, settlementDate, transferExternalId, ownerExternalId, "1.0"); + } + + private PostInitiateTransferResponse createSaleTransfer(Integer loanID, String settlementDate, String transferExternalId, + String ownerExternalId, String purchasePriceRatio) { + PostInitiateTransferResponse saleResponse = EXTERNAL_ASSET_OWNER_HELPER.initiateTransferByLoanId(loanID.longValue(), "sale", new PostInitiateTransferRequest().settlementDate(settlementDate).dateFormat("yyyy-MM-dd").locale("en") - .transferExternalId(transferExternalId).ownerExternalId(ownerExternalId).purchasePriceRatio("1.0")); + .transferExternalId(transferExternalId).ownerExternalId(ownerExternalId).purchasePriceRatio(purchasePriceRatio)); + assertEquals(transferExternalId, saleResponse.getResourceExternalId()); + return saleResponse; + } + + private PostInitiateTransferResponse createBuybackTransfer(Integer loanID, String settlementDate) { + String transferExternalId = UUID.randomUUID().toString(); + PostInitiateTransferResponse saleResponse = EXTERNAL_ASSET_OWNER_HELPER.initiateTransferByLoanId(loanID.longValue(), "buyback", + new PostInitiateTransferRequest().settlementDate(settlementDate).dateFormat("yyyy-MM-dd").locale("en") + .transferExternalId(transferExternalId)); assertEquals(transferExternalId, saleResponse.getResourceExternalId()); return saleResponse; }