FINERACT-2070: Further test cases

This commit is contained in:
Arnold Galovics
2024-03-26 21:24:05 +01:00
committed by Arnold Gálovics
parent 7c618a69d5
commit ed5098b5a5
39 changed files with 31201 additions and 0 deletions
@@ -0,0 +1,75 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.service;
import static org.awaitility.Awaitility.await;
import java.io.IOException;
import java.time.Duration;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.fineract.client.models.ExecuteJobRequest;
import org.apache.fineract.client.models.GetJobsResponse;
import org.apache.fineract.client.services.SchedulerJobApi;
import org.apache.fineract.test.data.job.Job;
import org.apache.fineract.test.data.job.JobResolver;
import org.apache.fineract.test.helper.ErrorHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import retrofit2.Response;
@Component
@RequiredArgsConstructor
@Slf4j
public class JobService {
@Autowired
private SchedulerJobApi schedulerJobApi;
private final JobResolver jobResolver;
public void execute(Job job) {
try {
Long jobId = jobResolver.resolve(job);
Response<Void> response = schedulerJobApi.executeJob(jobId, "executeJob", new ExecuteJobRequest()).execute();
ErrorHelper.checkSuccessfulApiCall(response);
} catch (IOException e) {
throw new RuntimeException("Exception while executing job %s".formatted(job.getName()), e);
}
}
public void executeAndWait(Job job) {
execute(job);
waitUntilJobIsFinished(job);
}
private void waitUntilJobIsFinished(Job job) {
String jobName = job.getName();
await().atMost(Duration.ofMinutes(2)).alias("%s didn't finish on time".formatted(jobName)).pollInterval(Duration.ofSeconds(10))
.until(() -> {
log.info("Waiting for job {} to finish", jobName);
Long jobId = jobResolver.resolve(job);
Response<GetJobsResponse> getJobsResponse = schedulerJobApi.retrieveOne5(jobId).execute();
ErrorHelper.checkSuccessfulApiCall(getJobsResponse);
Boolean currentlyRunning = getJobsResponse.body().getCurrentlyRunning();
return BooleanUtils.isFalse(currentlyRunning);
});
}
}
@@ -0,0 +1,771 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.assetexternalization;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.gson.Gson;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.math.RoundingMode;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.ExternalOwnerJournalEntryData;
import org.apache.fineract.client.models.ExternalOwnerTransferJournalEntryData;
import org.apache.fineract.client.models.ExternalTransferData;
import org.apache.fineract.client.models.JournalEntryData;
import org.apache.fineract.client.models.PageExternalTransferData;
import org.apache.fineract.client.models.PostInitiateTransferRequest;
import org.apache.fineract.client.models.PostInitiateTransferResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.ExternalAssetOwnersApi;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.test.data.AssetExternalizationErrorMessage;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.ErrorResponse;
import org.apache.fineract.test.helper.Utils;
import org.apache.fineract.test.messaging.event.EventCheckHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@Slf4j
public class AssetExternalizationStepDef extends AbstractStepDef {
public static final String OWNER_EXTERNAL_ID_PREFIX = "TestOwner-";
public static final String DATE_FORMAT_ASSET_EXT = "yyyy-MM-dd";
public static final String DEFAULT_LOCALE = "en";
public static final String TRANSACTION_TYPE_SALE = "sale";
public static final String TRANSACTION_TYPE_BUYBACK = "buyback";
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT_ASSET_EXT);
private static final Gson GSON = new JSON().getGson();
@Autowired
private ExternalAssetOwnersApi externalAssetOwnersApi;
@Autowired
private EventCheckHelper eventCheckHelper;
@When("Admin makes asset externalization request by Loan ID with unique ownerExternalId, user-generated transferExternalId and the following data:")
public void createAssetExternalizationRequestByLoanIdUserGeneratedExtId(DataTable table) throws IOException {
// if user created transferExternalId previously, it will use that, otherwise create a new one
String transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED);
if (transferExternalId == null) {
transferExternalId = Utils.randomNameGenerator("TestTransferExtId_", 3);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED, transferExternalId);
}
createAssetExternalizationRequestByLoanId(table, transferExternalId);
}
@When("Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data:")
public void createAssetExternalizationRequestByLoanIdSystemGeneratedExtId(DataTable table) throws IOException {
createAssetExternalizationRequestByLoanId(table, null);
}
private void createAssetExternalizationRequestByLoanId(DataTable table, String transferExternalId) throws IOException {
createAssetExternalizationRequestByLoanId(table, transferExternalId, true);
}
private void createAssetExternalizationRequestByLoanId(DataTable table, String transferExternalId, boolean regenerateOwner)
throws IOException {
List<List<String>> data = table.asLists();
List<String> transferData = data.get(1);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostInitiateTransferRequest request = new PostInitiateTransferRequest();
if (transferData.get(0).equals(TRANSACTION_TYPE_BUYBACK)) {
request.settlementDate(transferData.get(1))//
.transferExternalId(transferExternalId)//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanId(loanId, request, transferData.get(0)).execute();
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE, response);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE,
response.body().getResourceExternalId());
ErrorHelper.checkSuccessfulApiCall(response);
} else if (transferData.get(0).equals(TRANSACTION_TYPE_SALE)) {
String ownerExternalId;
if (regenerateOwner) {
ownerExternalId = Utils.randomNameGenerator(OWNER_EXTERNAL_ID_PREFIX, 3);
} else {
ownerExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
}
request.settlementDate(transferData.get(1))//
.ownerExternalId(ownerExternalId)//
.transferExternalId(transferExternalId)//
.purchasePriceRatio(transferData.get(2))//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanId(loanId, request, transferData.get(0)).execute();
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE, response);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE,
response.body().getResourceExternalId());
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID, ownerExternalId);
ErrorHelper.checkSuccessfulApiCall(response);
} else {
throw new IllegalStateException(String.format("%s is not supported Asset externalization transaction", transferData.get(0)));
}
}
@When("Admin makes asset externalization BUYBACK request with ownerExternalId = null and settlement date {string} by Loan ID with system-generated transferExternalId")
public void createAssetExternalizationBuybackRequestOwnerNullByLoanIdSystemGeneratedExtId(String settlementDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostInitiateTransferRequest request = new PostInitiateTransferRequest()//
.settlementDate(settlementDate)//
.ownerExternalId(null)//
.transferExternalId(testContext().get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_FROM_RESPONSE))//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanId(loanId, request, TRANSACTION_TYPE_BUYBACK).execute();
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE, response);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE,
response.body().getResourceExternalId());
ErrorHelper.checkSuccessfulApiCall(response);
}
@When("Admin makes asset externalization request by Loan external ID with unique ownerExternalId, user-generated transferExternalId and the following data:")
public void createAssetExternalizationRequestByLoanExternalIdUserGeneratedExtId(DataTable table) throws IOException {
// if user created transferExternalId previously, it will use that, otherwise create a new one
String transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED);
if (transferExternalId == null) {
transferExternalId = Utils.randomNameGenerator("TestTransferExtId_", 3);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED, transferExternalId);
}
createAssetExternalizationRequestByLoanExternalId(table, transferExternalId);
}
@When("Admin makes asset externalization request by Loan external ID with unique ownerExternalId, system-generated transferExternalId and the following data:")
public void createAssetExternalizationRequestByLoanExternalIdSystemGeneratedExtId(DataTable table) throws IOException {
createAssetExternalizationRequestByLoanExternalId(table, null);
}
private void createAssetExternalizationRequestByLoanExternalId(DataTable table, String transferExternalId) throws IOException {
List<List<String>> data = table.asLists();
List<String> transferData = data.get(1);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
String loanExternalId = loanResponse.body().getResourceExternalId();
PostInitiateTransferRequest request = new PostInitiateTransferRequest();
if (transferData.get(0).equals(TRANSACTION_TYPE_BUYBACK)) {
request.settlementDate(transferData.get(1))//
.transferExternalId(transferExternalId)//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanExternalId(loanExternalId, request, transferData.get(0)).execute();
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE, response);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE,
response.body().getResourceExternalId());
ErrorHelper.checkSuccessfulApiCall(response);
} else if (transferData.get(0).equals(TRANSACTION_TYPE_SALE)) {
String ownerExternalId = Utils.randomNameGenerator(OWNER_EXTERNAL_ID_PREFIX, 3);
request.settlementDate(transferData.get(1))//
.ownerExternalId(ownerExternalId)//
.transferExternalId(transferExternalId)//
.purchasePriceRatio(transferData.get(2))//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanExternalId(loanExternalId, request, transferData.get(0)).execute();
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE, response);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE,
response.body().getResourceExternalId());
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID, ownerExternalId);
ErrorHelper.checkSuccessfulApiCall(response);
} else {
throw new IllegalStateException(String.format("%s is not supported Asset externalization transaction", transferData.get(0)));
}
}
@Then("Asset externalization response has the correct Loan ID, transferExternalId")
public void checkAssetExternalizationResponse() {
String ownerExternalIdStored = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
String transferExternalIdExpected = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostInitiateTransferResponse> response = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE);
PostInitiateTransferResponse body = response.body();
Long loanIdActual = body.getSubResourceId();
String transferExternalIdActual = body.getResourceExternalId();
log.info("loanId: {}", loanId);
log.info("ownerExternalIdStored: {}", ownerExternalIdStored);
log.info("transferExternalId generated by user: {}", transferExternalIdExpected);
log.info("transferExternalIdActual: {}", transferExternalIdActual);
assertThat(loanIdActual).as(ErrorMessageHelper.wrongDataInAssetExternalizationResponse(loanIdActual, loanId)).isEqualTo(loanId);
assertThat(body.getResourceId()).isNotNull();
if (transferExternalIdExpected != null) {
assertThat(transferExternalIdActual)
.as(ErrorMessageHelper.wrongDataInAssetExternalizationResponse(transferExternalIdActual, transferExternalIdExpected))
.isEqualTo(transferExternalIdExpected);
} else {
assertThat(transferExternalIdActual).isNotEmpty();
}
}
@Then("Fetching Asset externalization details by loan id gives numberOfElements: {int} with correct ownerExternalId and the following data:")
public void checkAssetExternalizationDetailsByLoanId(int numberOfElements, DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PageExternalTransferData> response = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null).execute();
ErrorHelper.checkSuccessfulApiCall(response);
checkExternalAssetDetails(loanId, null, response, numberOfElements, table);
}
@Then("Fetching Asset externalization details by loan external id gives numberOfElements: {int} with correct ownerExternalId and the following data:")
public void checkAssetExternalizationDetailsByLoanExternalId(int numberOfElements, DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
String loanExternalId = loanResponse.body().getResourceExternalId();
Response<PageExternalTransferData> response = externalAssetOwnersApi.getTransfers(null, null, loanExternalId, null, null).execute();
ErrorHelper.checkSuccessfulApiCall(response);
checkExternalAssetDetails(null, loanExternalId, response, numberOfElements, table);
}
@Then("Fetching Asset externalization details by transfer external id gives numberOfElements: {int} with correct ownerExternalId and the following data:")
public void checkAssetExternalizationDetailsByTransferExternalId(int numberOfElements, DataTable table) throws IOException {
String transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
Response<PageExternalTransferData> response = externalAssetOwnersApi.getTransfers(transferExternalId, null, null, null, null)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
checkExternalAssetDetails(null, null, response, numberOfElements, table);
}
@Then("Asset externalization details has the generated transferExternalId")
public void checkGeneratedTransferExternalId() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostInitiateTransferResponse> assetExtResponse = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE);
String transferExternalIdExpected = assetExtResponse.body().getResourceExternalId();
Response<PageExternalTransferData> response = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null).execute();
ErrorHelper.checkSuccessfulApiCall(response);
List<ExternalTransferData> content = response.body().getContent();
content.forEach(e -> {
assertThat(e.getTransferExternalId()).as(ErrorMessageHelper
.wrongDataInAssetExternalizationTransferExternalId(e.getTransferExternalId(), transferExternalIdExpected))
.isEqualTo(transferExternalIdExpected);
});
}
private void checkExternalAssetDetails(Long loanId, String loanExternalId, Response<PageExternalTransferData> response,
int numberOfElements, DataTable table) {
PageExternalTransferData body = response.body();
Integer numberOfElementsActual = body.getNumberOfElements();
List<ExternalTransferData> content = body.getContent();
String ownerExternalIdStored = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
String transferExternalId;
List<List<String>> data = table.asLists();
for (int i = 1; i < data.size(); i++) {
List<String> expectedValues = data.get(i);
String transactionType = expectedValues.get(5);
if (transactionType.equals(ExternalTransferData.StatusEnum.BUYBACK.getValue())) {
transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
} else {
transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
}
expectedValues.add(ownerExternalIdStored);
expectedValues.add(loanId == null ? null : String.valueOf(loanId));
expectedValues.add(loanExternalId);
expectedValues.add(transferExternalId);
List<List<String>> actualValuesList = content.stream().map(t -> {
List<String> actualValues = new ArrayList<>();
actualValues.add(t.getSettlementDate() == null ? null : FORMATTER.format(t.getSettlementDate()));
actualValues.add(t.getPurchasePriceRatio() == null ? null : t.getPurchasePriceRatio());
actualValues.add(t.getStatus() == null ? null : String.valueOf(t.getStatus()));
actualValues.add(t.getEffectiveFrom() == null ? null : FORMATTER.format(t.getEffectiveFrom()));
actualValues.add(t.getEffectiveTo() == null ? null : FORMATTER.format(t.getEffectiveTo()));
actualValues.add(transactionType);
actualValues.add(t.getOwner().getExternalId() == null ? null : t.getOwner().getExternalId());
actualValues.add(loanId == null ? null : String.valueOf(t.getLoan().getLoanId()));
actualValues.add(loanExternalId == null ? null : t.getLoan().getExternalId());
actualValues.add(t.getTransferExternalId());
return actualValues;
}).collect(Collectors.toList());
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
assertThat(numberOfElementsActual)
.as(ErrorMessageHelper.wrongTotalFilteredRecordsInAssetExternalizationDetails(numberOfElementsActual, numberOfElements))
.isEqualTo(numberOfElements);
assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInExternalAssetDetails(i, actualValuesList, expectedValues))
.isTrue();
}
}
@Then("BUYBACK transaction results a {int} error and proper error message when its settlementDate is earlier than the original settlementDate")
public void buybackDateError(int errorCodeExpected, DataTable table) throws IOException {
List<List<String>> data = table.asLists();
List<String> transferData = data.get(1);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
String transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
PostInitiateTransferRequest request = new PostInitiateTransferRequest()//
.settlementDate(transferData.get(1))//
.transferExternalId(transferExternalId)//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanId(loanId, request, transferData.get(0)).execute();
PageExternalTransferData transfers = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null).execute().body();
String settlementDateOriginal = FORMATTER.format(transfers.getContent().get(0).getSettlementDate());
String errorMessageExpected = String.format(
"This loan cannot be bought back, settlement date is earlier than effective transfer settlement date: %s",
settlementDateOriginal);
String errorToString = response.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(errorToString, ErrorResponse.class);
String errorMessageActual = errorResponse.getDeveloperMessage();
int errorCodeActual = response.code();
assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected);
assertThat(errorMessageActual).as(ErrorMessageHelper.wrongErrorMessage(errorMessageActual, errorMessageExpected))
.isEqualTo(errorMessageExpected);
log.info("ERROR CODE: {}", errorCodeActual);
log.info("ERROR MESSAGE: {}", errorMessageActual);
}
@Then("Asset externalization transaction with the following data results a {int} error and {string} error message")
public void transactionError(int errorCodeExpected, String errorMessageType, DataTable table) throws IOException {
List<List<String>> data = table.asLists();
List<String> transferData = data.get(1);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostInitiateTransferRequest request = new PostInitiateTransferRequest();
if (transferData.get(0).equals(TRANSACTION_TYPE_BUYBACK)) {
request.settlementDate(transferData.get(1))//
.transferExternalId(null)//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
} else if (transferData.get(0).equals(TRANSACTION_TYPE_SALE)) {
String ownerExternalId = Utils.randomNameGenerator(OWNER_EXTERNAL_ID_PREFIX, 3);
request.settlementDate(transferData.get(1))//
.ownerExternalId(ownerExternalId)//
.transferExternalId(null)//
.purchasePriceRatio(transferData.get(2))//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID, ownerExternalId);
} else {
throw new IllegalStateException(String.format("%s is not supported Asset externalization transaction", transferData.get(0)));
}
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanId(loanId, request, transferData.get(0)).execute();
AssetExternalizationErrorMessage errorMsgType = AssetExternalizationErrorMessage.valueOf(errorMessageType);
String errorMessageExpected = errorMsgType.getValue();
String errorToString = response.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(errorToString, ErrorResponse.class);
String errorMessageActual = errorResponse.getDeveloperMessage();
int errorCodeActual = response.code();
assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected);
assertThat(errorMessageActual).as(ErrorMessageHelper.wrongErrorMessage(errorMessageActual, errorMessageExpected))
.isEqualTo(errorMessageExpected);
log.info("ERROR CODE: {}", errorCodeActual);
log.info("ERROR MESSAGE: {}", errorMessageActual);
}
@Then("Asset externalization SALES transaction with ownerExternalId = null and the following data results a {int} error and {string} error message")
public void transactionErrorSalesOwnerNull(int errorCodeExpected, String errorMessageType, DataTable table) throws IOException {
List<List<String>> data = table.asLists();
List<String> transferData = data.get(1);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostInitiateTransferRequest request = new PostInitiateTransferRequest()//
.settlementDate(transferData.get(0))//
.ownerExternalId(null)//
.transferExternalId(null)//
.purchasePriceRatio(transferData.get(1))//
.dateFormat(DATE_FORMAT_ASSET_EXT)//
.locale(DEFAULT_LOCALE);//
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi
.transferRequestWithLoanId(loanId, request, TRANSACTION_TYPE_SALE).execute();
AssetExternalizationErrorMessage errorMsgType = AssetExternalizationErrorMessage.valueOf(errorMessageType);
String errorMessageExpected = errorMsgType.getValue();
String errorToString = response.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(errorToString, ErrorResponse.class);
String errorMessageActual = errorResponse.getDeveloperMessage();
int errorCodeActual = response.code();
assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected);
assertThat(errorMessageActual).as(ErrorMessageHelper.wrongErrorMessage(errorMessageActual, errorMessageExpected))
.isEqualTo(errorMessageExpected);
log.info("ERROR CODE: {}", errorCodeActual);
log.info("ERROR MESSAGE: {}", errorMessageActual);
}
@Then("The latest asset externalization transaction with {string} status has the following TRANSFER Journal entries:")
public void checkJournalEntriesTransaction(String status, DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Long lastTransferIdByStatus = getLastTransferIdByStatus(loanId, status);
Response<ExternalOwnerTransferJournalEntryData> journalEntriesOfTransfer = externalAssetOwnersApi
.getJournalEntriesOfTransfer(lastTransferIdByStatus, null, null).execute();
List<JournalEntryData> content = journalEntriesOfTransfer.body().getJournalEntryData().getContent();
List<List<String>> data = table.asLists();
int linesExpected = data.size() - 1;
for (int i = 1; i < data.size(); i++) {
List<String> expectedValues = data.get(i);
List<List<String>> actualValuesList = content.stream().map(t -> {
List<String> actualValues = new ArrayList<>();
actualValues.add(t.getGlAccountType().getValue());
actualValues.add(t.getGlAccountCode());
actualValues.add(t.getGlAccountName());
actualValues.add(t.getEntryType().getValue());
actualValues.add(t.getAmount().setScale(2, RoundingMode.HALF_DOWN).toString());
return actualValues;
}).collect(Collectors.toList());
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
assertThat(containsExpectedValues)
.as(ErrorMessageHelper.wrongValueInLineInAssetExternalizationJournalEntry(i, actualValuesList, expectedValues))
.isTrue();
int linesActual = journalEntriesOfTransfer.body().getJournalEntryData().getNumberOfElements();
assertThat(linesActual).as(ErrorMessageHelper.wrongNumberOfLinesInAssetExternalizationJournalEntry(linesActual, linesExpected))
.isEqualTo(linesExpected);
}
log.info("loanId: {}", journalEntriesOfTransfer.body().getTransferData().getLoan().getLoanId());
log.info("ownerExternalId: {}", journalEntriesOfTransfer.body().getTransferData().getOwner().getExternalId());
log.info("transferId: {}", lastTransferIdByStatus);
log.info("transferExternalId: {}", journalEntriesOfTransfer.body().getTransferData().getTransferExternalId());
}
private Long getLastTransferIdByStatus(Long loanId, String status) throws IOException {
Response<PageExternalTransferData> transfersResponse = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null)
.execute();
List<ExternalTransferData> content = transfersResponse.body().getContent();
ExternalTransferData result = content.stream().filter(t -> status.equals(t.getStatus().getValue()))
.reduce((first, second) -> second)
.orElseThrow(() -> new IllegalStateException(String.format("No Journal entry found with status: %s", status)));
return result.getTransferId();
}
private Long getLastTransferId(Long loanId) throws IOException {
Response<PageExternalTransferData> transfersResponse = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null)
.execute();
List<ExternalTransferData> content = transfersResponse.body().getContent();
ExternalTransferData result = content.stream().reduce((first, second) -> second)
.orElseThrow(() -> new IllegalStateException("transfersResponse.body().getContent() is empty"));
return result.getTransferId();
}
@Then("The asset external owner has the following OWNER Journal entries:")
public void checkJournalEntriesOwner(DataTable table) throws IOException {
String ownerExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
Response<ExternalOwnerJournalEntryData> journalEntriesOfOwner = externalAssetOwnersApi
.getJournalEntriesOfOwner(ownerExternalId, null, null).execute();
List<JournalEntryData> content = journalEntriesOfOwner.body().getJournalEntryData().getContent();
List<List<String>> data = table.asLists();
int linesExpected = data.size() - 1;
for (int i = 1; i < data.size(); i++) {
List<String> expectedValues = data.get(i);
List<List<String>> actualValuesList = content.stream().map(t -> {
List<String> actualValues = new ArrayList<>();
actualValues.add(t.getGlAccountType().getValue());
actualValues.add(t.getGlAccountCode());
actualValues.add(t.getGlAccountName());
actualValues.add(t.getEntryType().getValue());
actualValues.add(t.getAmount().setScale(2, RoundingMode.HALF_DOWN).toString());
return actualValues;
}).collect(Collectors.toList());
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
assertThat(containsExpectedValues)
.as(ErrorMessageHelper.wrongValueInLineInAssetExternalizationJournalEntry(i, actualValuesList, expectedValues))
.isTrue();
int linesActual = journalEntriesOfOwner.body().getJournalEntryData().getNumberOfElements();
assertThat(linesActual).as(ErrorMessageHelper.wrongNumberOfLinesInAssetExternalizationJournalEntry(linesActual, linesExpected))
.isEqualTo(linesExpected);
}
log.info("ownerExternalId: {}", journalEntriesOfOwner.body().getOwnerData().getExternalId());
}
@Then("LoanOwnershipTransferBusinessEvent is created")
public void loanOwnershipTransferBusinessEventCheck() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Long transferId = getLastTransferId(loanId);
eventCheckHelper.loanOwnershipTransferBusinessEventCheck(loanId, transferId);
}
@Then("LoanOwnershipTransferBusinessEvent with transfer status: {string} and transfer status reason {string} is created")
public void loanOwnershipTransferBusinessEventCheck(String transferStatus, String transferStatusReason) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Long transferId = getLastTransferId(loanId);
eventCheckHelper.loanOwnershipTransferBusinessEventWithStatusCheck(loanId, transferId, transferStatus, transferStatusReason);
}
@Then("LoanAccountSnapshotBusinessEvent is created")
public void loanAccountSnapshotBusinessEventCheck() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Long transferId = getLastTransferId(loanId);
eventCheckHelper.loanAccountSnapshotBusinessEventCheck(loanId, transferId);
}
@Then("Asset externalization response {string} has the correct Loan ID, transferExternalId")
public void checkAssetExternalizationResponse(String type) {
String ownerExternalIdStored = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
String transferExternalIdExpected = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_PREFIX + "_" + type);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostInitiateTransferResponse> response = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_RESPONSE);
PostInitiateTransferResponse body = response.body();
Long loanIdActual = body.getSubResourceId();
String transferExternalIdActual = body.getResourceExternalId();
log.info("loanId: {}", loanId);
log.info("ownerExternalIdStored: {}", ownerExternalIdStored);
log.info("transferExternalId generated by user: {}", transferExternalIdExpected);
log.info("transferExternalIdActual: {}", transferExternalIdActual);
assertThat(loanIdActual).as(ErrorMessageHelper.wrongDataInAssetExternalizationResponse(loanIdActual, loanId)).isEqualTo(loanId);
assertThat(body.getResourceId()).isNotNull();
if (transferExternalIdExpected != null) {
assertThat(transferExternalIdActual)
.as(ErrorMessageHelper.wrongDataInAssetExternalizationResponse(transferExternalIdActual, transferExternalIdExpected))
.isEqualTo(transferExternalIdExpected);
} else {
assertThat(transferExternalIdActual).isNotEmpty();
}
}
@When("Admin makes asset externalization request for type {string} by Loan ID with unique ownerExternalId, user-generated transferExternalId and the following data:")
public void createAssetExternalizationRequestByLoanIdUserGeneratedExtId(String type, DataTable table) throws IOException {
// if user created transferExternalId previously, it will use that, otherwise create a new one
String transferExternalId = testContext()
.get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED + "_" + type);
if (transferExternalId == null) {
transferExternalId = Utils.randomNameGenerator("TestTransferExtId_", 3);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED + "_" + type, transferExternalId);
}
createAssetExternalizationRequestByLoanId(table, transferExternalId);
}
@When("Admin send {string} command to the transaction type {string}")
public void adminTransactionCommandTheWithType(String command, String type) throws IOException {
String transferExternalId = testContext()
.get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED + "_" + type);
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi.transferRequestWithId1(transferExternalId, command)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
@When("Admin send {string} command to the transaction type {string} will throw error")
public void adminTransactionCommandTheWithTypeThrowError(String command, String type) throws IOException {
String transferExternalId = testContext()
.get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED + "_" + type);
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi.transferRequestWithId1(transferExternalId, command)
.execute();
ErrorHelper.checkFailedApiCall(response, 403);
}
@Then("Fetching Asset externalization details by loan id gives numberOfElements: {int} with correct ownerExternalId, ignore transactionExternalId and contain the following data:")
public void checkAssetExternalizationDetailsByLoanIdIgnoreTransactionExternalId(int numberOfElements, DataTable table)
throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PageExternalTransferData> response = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null).execute();
ErrorHelper.checkSuccessfulApiCall(response);
checkExternalAssetDetailsIgnoreTransferExternalId(loanId, null, response, numberOfElements, table);
}
private void checkExternalAssetDetailsIgnoreTransferExternalId(Long loanId, String loanExternalId,
Response<PageExternalTransferData> response, int numberOfElements, DataTable table) {
PageExternalTransferData body = response.body();
Integer numberOfElementsActual = body.getNumberOfElements();
List<ExternalTransferData> content = body.getContent();
String ownerExternalIdStored = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
List<List<String>> data = table.asLists();
for (int i = 1; i < data.size(); i++) {
List<String> expectedValues = data.get(i);
expectedValues.add(ownerExternalIdStored);
expectedValues.add(loanId == null ? null : String.valueOf(loanId));
expectedValues.add(loanExternalId);
List<List<String>> actualValuesList = content.stream().map(t -> {
List<String> actualValues = new ArrayList<>();
actualValues.add(t.getSettlementDate() == null ? null : FORMATTER.format(t.getSettlementDate()));
actualValues.add(t.getPurchasePriceRatio() == null ? null : t.getPurchasePriceRatio());
actualValues.add(t.getStatus() == null ? null : String.valueOf(t.getStatus()));
actualValues.add(t.getEffectiveFrom() == null ? null : FORMATTER.format(t.getEffectiveFrom()));
actualValues.add(t.getEffectiveTo() == null ? null : FORMATTER.format(t.getEffectiveTo()));
actualValues.add(t.getOwner().getExternalId() == null ? null : t.getOwner().getExternalId());
actualValues.add(loanId == null ? null : String.valueOf(t.getLoan().getLoanId()));
actualValues.add(loanExternalId == null ? null : t.getLoan().getExternalId());
return actualValues;
}).collect(Collectors.toList());
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
assertThat(numberOfElementsActual)
.as(ErrorMessageHelper.wrongTotalFilteredRecordsInAssetExternalizationDetails(numberOfElementsActual, numberOfElements))
.isEqualTo(numberOfElements);
assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInExternalAssetDetails(i, actualValuesList, expectedValues))
.isTrue();
}
}
@When("Admin send {string} command on {string} transaction it will throw an error")
public void adminSendCommandAndItWillThrowError(String command, String transactionType) throws IOException {
String transferExternalId;
if (transactionType.equals(ExternalTransferData.StatusEnum.BUYBACK.getValue())) {
transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
} else {
transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
}
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi.transferRequestWithId1(transferExternalId, command)
.execute();
ErrorHelper.checkFailedApiCall(response, 403);
}
@When("Admin send {string} command on {string} transaction")
public void adminSendCommand(String command, String transactionType) throws IOException {
String transferExternalId;
if (transactionType.equals(ExternalTransferData.StatusEnum.BUYBACK.getValue())) {
transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
} else {
transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
}
Response<PostInitiateTransferResponse> response = externalAssetOwnersApi.transferRequestWithId1(transferExternalId, command)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
@When("Admin makes asset externalization request for type {string} by Loan ID with unique ownerExternalId, force generated transferExternalId and without change test owner with following data:")
public void createAssetExternalizationRequestByLoanIdUserGeneratedExtIdForceTransferIdNoTestOwner(String type, DataTable table)
throws IOException {
// if user created transferExternalId previously, it will use that, otherwise create a new one
String transferExternalId = Utils.randomNameGenerator("TestTransferExtId_", 3);
testContext().set(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED + "_" + type, transferExternalId);
createAssetExternalizationRequestByLoanId(table, transferExternalId, false);
}
}
@@ -0,0 +1,64 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.common;
import static org.assertj.core.api.Assertions.assertThat;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.apache.fineract.client.models.BusinessDateResponse;
import org.apache.fineract.client.services.BusinessDateManagementApi;
import org.apache.fineract.test.helper.BusinessDateHelper;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class BusinessDateStepDef extends AbstractStepDef {
@Autowired
private BusinessDateHelper businessDateHelper;
@Autowired
private BusinessDateManagementApi businessDateManagementApi;
@When("Admin sets the business date to {string}")
public void setBusinessDate(String businessDate) throws IOException {
businessDateHelper.setBusinessDate(businessDate);
}
@When("Admin sets the business date to the actual date")
public void setBusinessDateToday() throws IOException {
businessDateHelper.setBusinessDateToday();
}
@Then("Admin checks that the business date is correctly set to {string}")
public void checkBusinessDate(String businessDate) throws IOException {
Response<BusinessDateResponse> businessDateResponse = businessDateManagementApi.getBusinessDate(BusinessDateHelper.BUSINESS_DATE)
.execute();
ErrorHelper.checkSuccessfulApiCall(businessDateResponse);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy");
LocalDate localDate = LocalDate.parse(businessDate, formatter);
assertThat(businessDateResponse.body().getDate()).isEqualTo(localDate);
}
}
@@ -0,0 +1,152 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.common;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.client.models.BusinessStep;
import org.apache.fineract.client.models.UpdateBusinessStepConfigRequest;
import org.apache.fineract.client.services.BusinessStepConfigurationApi;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class BusinessStepStepDef extends AbstractStepDef {
private static final String WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS = "LOAN_CLOSE_OF_BUSINESS";
private static final String BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS = "APPLY_CHARGE_TO_OVERDUE_LOANS";
private static final String BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION = "LOAN_DELINQUENCY_CLASSIFICATION";
private static final String BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE = "CHECK_LOAN_REPAYMENT_DUE";
private static final String BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE = "CHECK_LOAN_REPAYMENT_OVERDUE";
private static final String BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING = "UPDATE_LOAN_ARREARS_AGING";
private static final String BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES = "ADD_PERIODIC_ACCRUAL_ENTRIES";
private static final String BUSINESS_STEP_NAME_EXTERNAL_ASSET_OWNER_TRANSFER = "EXTERNAL_ASSET_OWNER_TRANSFER";
private static final String BUSINESS_STEP_NAME_CHECK_DUE_INSTALLMENTS = "CHECK_DUE_INSTALLMENTS";
@Autowired
private BusinessStepConfigurationApi businessStepConfigurationApi;
@Given("Admin puts EXTERNAL_ASSET_OWNER_TRANSFER job into LOAN_CLOSE_OF_BUSINESS workflow")
public void putExternalAssetOwnerTransferJobInCOB() throws IOException {
BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L);
BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION)
.order(2L);
BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L);
BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L);
BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L);
BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L);
BusinessStep externalAssetOwnerTransfer = new BusinessStep().stepName(BUSINESS_STEP_NAME_EXTERNAL_ASSET_OWNER_TRANSFER).order(7L);
List<BusinessStep> businessSteps = new ArrayList<>();
businessSteps.add(applyChargeToOverdueLoans);
businessSteps.add(loanDelinquencyClassification);
businessSteps.add(checkLoanRepaymentDue);
businessSteps.add(checkLoanRepaymentOverdue);
businessSteps.add(updateLoanArrearsAging);
businessSteps.add(addPeriodicAccrualEntries);
businessSteps.add(externalAssetOwnerTransfer);
UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps);
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
@Then("Admin removes EXTERNAL_ASSET_OWNER_TRANSFER job from LOAN_CLOSE_OF_BUSINESS workflow")
public void removeExternalAssetOwnerTransferJobInCOB() throws IOException {
BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L);
BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION)
.order(2L);
BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L);
BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L);
BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L);
BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L);
List<BusinessStep> businessSteps = new ArrayList<>();
businessSteps.add(applyChargeToOverdueLoans);
businessSteps.add(loanDelinquencyClassification);
businessSteps.add(checkLoanRepaymentDue);
businessSteps.add(checkLoanRepaymentOverdue);
businessSteps.add(updateLoanArrearsAging);
businessSteps.add(addPeriodicAccrualEntries);
UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps);
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
@Given("Admin puts CHECK_DUE_INSTALLMENTS job into LOAN_CLOSE_OF_BUSINESS workflow")
public void putCheckDueInstallmentsJobInCOB() throws IOException {
BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L);
BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION)
.order(2L);
BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L);
BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L);
BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L);
BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L);
BusinessStep checkDueInstallments = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_DUE_INSTALLMENTS).order(7L);
List<BusinessStep> businessSteps = new ArrayList<>();
businessSteps.add(applyChargeToOverdueLoans);
businessSteps.add(loanDelinquencyClassification);
businessSteps.add(checkLoanRepaymentDue);
businessSteps.add(checkLoanRepaymentOverdue);
businessSteps.add(updateLoanArrearsAging);
businessSteps.add(addPeriodicAccrualEntries);
businessSteps.add(checkDueInstallments);
UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps);
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
@Then("Admin removes CHECK_DUE_INSTALLMENTS job from LOAN_CLOSE_OF_BUSINESS workflow")
public void removeCheckDueInstallmentsJobInCOB() throws IOException {
BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L);
BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION)
.order(2L);
BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L);
BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L);
BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L);
BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L);
List<BusinessStep> businessSteps = new ArrayList<>();
businessSteps.add(applyChargeToOverdueLoans);
businessSteps.add(loanDelinquencyClassification);
businessSteps.add(checkLoanRepaymentDue);
businessSteps.add(checkLoanRepaymentOverdue);
businessSteps.add(updateLoanArrearsAging);
businessSteps.add(addPeriodicAccrualEntries);
UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps);
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
}
@@ -33,6 +33,7 @@ import org.apache.fineract.test.helper.CodeHelper;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.Utils;
import org.apache.fineract.test.messaging.event.EventCheckHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
@@ -49,6 +50,9 @@ public class ClientStepDef extends AbstractStepDef {
@Autowired
private ClientRequestFactory clientRequestFactory;
@Autowired
private EventCheckHelper eventCheckHelper;
@When("Admin creates a client with random data")
public void createClientRandomFirstNameLastName() throws IOException {
PostClientsRequest clientsRequest = clientRequestFactory.defaultClientCreationRequest();
@@ -56,6 +60,8 @@ public class ClientStepDef extends AbstractStepDef {
Response<PostClientsResponse> response = clientApi.create6(clientsRequest).execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.CLIENT_CREATE_RESPONSE, response);
eventCheckHelper.clientEventCheck(response);
}
@When("Admin creates a second client with random data")
@@ -65,6 +71,8 @@ public class ClientStepDef extends AbstractStepDef {
Response<PostClientsResponse> response = clientApi.create6(clientsRequest).execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.CLIENT_CREATE_SECOND_CLIENT_RESPONSE, response);
eventCheckHelper.clientEventCheck(response);
}
@When("Admin creates a client with Firstname {string} and Lastname {string}")
@@ -114,5 +122,7 @@ public class ClientStepDef extends AbstractStepDef {
Response<PostClientsResponse> response = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
assertThat(response.isSuccessful()).as(ErrorMessageHelper.requestFailed(response)).isTrue();
eventCheckHelper.clientEventCheck(response);
}
}
@@ -0,0 +1,73 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.common;
import io.cucumber.java.en.Then;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.Event;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class EventStepDef extends AbstractStepDef {
@Autowired
private EventAssertion eventAssertion;
@Then("{string} event has been raised for the loan")
public void assertEventRaisedForLoan(String eventType) {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Class<? extends Event> eventTypeClazz = resolveEventType(eventType);
eventAssertion.assertEventRaised(eventTypeClazz, loanId);
}
@Then("No new event with type {string} has been raised for the loan")
public void assertEventNotRaisedForLoan(String eventType) {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Class<? extends Event> eventTypeClazz = resolveEventType(eventType);
eventAssertion.assertEventNotRaised(eventTypeClazz, loanId);
}
private Class<? extends Event> resolveEventType(String eventType) {
String fullyQualifiedEventClassName = null;
try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("org.apache.fineract.test").scan()) {
ClassInfoList matchingClasses = scanResult.getAllClasses().filter(i -> i.getSimpleName().equalsIgnoreCase(eventType));
if (matchingClasses.isEmpty()) {
throw new IllegalArgumentException("Cannot find event with type name: " + eventType);
}
fullyQualifiedEventClassName = matchingClasses.get(0).getName();
}
Class<? extends Event> eventTypeClazz = null;
try {
eventTypeClazz = (Class<? extends Event>) Class.forName(fullyQualifiedEventClassName);
} catch (Exception e) {
throw new RuntimeException("Cannot find event with type name: " + eventType, e);
}
return eventTypeClazz;
}
}
@@ -0,0 +1,225 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.common;
import static org.assertj.core.api.Assertions.assertThat;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.JournalEntryTransactionItem;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.JournalEntriesApi;
import org.apache.fineract.client.services.LoansApi;
import org.apache.fineract.test.data.TransactionType;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@Slf4j
public class JournalEntriesStepDef extends AbstractStepDef {
public static final String DATE_FORMAT = "dd MMMM yyyy";
@Autowired
private LoansApi loansApi;
@Autowired
private JournalEntriesApi journalEntriesApi;
@Then("Loan Transactions tab has a {string} transaction with date {string} which has the following Journal entries:")
public void journalEntryDataCheck(String transactionType, String transactionDate, DataTable table) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
TransactionType transactionType1 = TransactionType.valueOf(transactionType);
String transactionTypeExpected = transactionType1.getValue();
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
List<GetLoansLoanIdTransactions> transactionsMatch = transactions.stream()
.filter(t -> transactionDate.equals(formatter.format(t.getDate()))
&& transactionTypeExpected.equals(t.getType().getCode().substring(20)))
.collect(Collectors.toList());
List<List<JournalEntryTransactionItem>> journalLinesActualList = transactionsMatch.stream().map(t -> {
String transactionId = "L" + t.getId();
Response<GetJournalEntriesTransactionIdResponse> journalEntryDataResponse = null;
try {
journalEntryDataResponse = journalEntriesApi.retrieveAll1(//
null, //
null, //
null, //
null, //
null, //
null, //
null, //
transactionId, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
true//
).execute();
ErrorHelper.checkSuccessfulApiCall(journalEntryDataResponse);
} catch (IOException e) {
log.error("Exception", e);
}
return journalEntryDataResponse.body().getPageItems();
}).collect(Collectors.toList());
List<List<String>> data = table.asLists();
for (int i = 1; i < data.size(); i++) {
List<List<List<String>>> possibleActualValuesList = new ArrayList<>();
List<String> expectedValues = data.get(i);
boolean containsAnyExpected = false;
for (int j = 0; j < journalLinesActualList.size(); j++) {
List<JournalEntryTransactionItem> journalLinesActual = journalLinesActualList.get(j);
List<List<String>> actualValuesList = journalLinesActual.stream().map(t -> {
List<String> actualValues = new ArrayList<>();
actualValues.add(t.getGlAccountType().getValue() == null ? null : t.getGlAccountType().getValue());
actualValues.add(t.getGlAccountCode() == null ? null : t.getGlAccountCode());
actualValues.add(t.getGlAccountName() == null ? null : t.getGlAccountName());
actualValues.add("DEBIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
actualValues.add("CREDIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
return actualValues;
}).collect(Collectors.toList());
possibleActualValuesList.add(actualValuesList);
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
if (containsExpectedValues) {
containsAnyExpected = true;
}
}
assertThat(containsAnyExpected)
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(i, possibleActualValuesList, expectedValues)).isTrue();
}
}
@Then("In Loan transactions the replayed {string} transaction with date {string} has a reverted transaction pair with the following Journal entries:")
public void revertedJournalEntryDataCheck(String transactionType, String transactionDate, DataTable table) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
TransactionType transactionType1 = TransactionType.valueOf(transactionType);
String transactionTypeExpected = transactionType1.getValue();
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
List<GetLoansLoanIdTransactions> transactionsMatch = transactions.stream()
.filter(t -> transactionDate.equals(formatter.format(t.getDate()))
&& transactionTypeExpected.equals(t.getType().getCode().substring(20)))
.collect(Collectors.toList());
List<String> transactionIdList = transactionsMatch.stream().flatMap(t -> t.getTransactionRelations().stream()
.filter(e -> "REPLAYED".equals(e.getRelationType())).map(c -> "L" + c.getToLoanTransaction().toString()))
.collect(Collectors.toList());
List<List<JournalEntryTransactionItem>> journalLinesActualList = transactionIdList.stream().map(t -> {
Response<GetJournalEntriesTransactionIdResponse> journalEntryDataResponse = null;
try {
journalEntryDataResponse = journalEntriesApi.retrieveAll1(//
null, //
null, //
null, //
null, //
null, //
null, //
null, //
t, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
true//
).execute();
ErrorHelper.checkSuccessfulApiCall(journalEntryDataResponse);
} catch (IOException e) {
log.error("Exception", e);
}
return journalEntryDataResponse.body().getPageItems();
}).collect(Collectors.toList());
List<List<String>> data = table.asLists();
for (int i = 1; i < data.size(); i++) {
List<List<List<String>>> possibleActualValuesList = new ArrayList<>();
List<String> expectedValues = data.get(i);
boolean containsAnyExpected = false;
for (int j = 0; j < journalLinesActualList.size(); j++) {
List<JournalEntryTransactionItem> journalLinesActual = journalLinesActualList.get(j);
List<List<String>> actualValuesList = journalLinesActual.stream().map(t -> {
List<String> actualValues = new ArrayList<>();
actualValues.add(t.getGlAccountType().getValue() == null ? null : t.getGlAccountType().getValue());
actualValues.add(t.getGlAccountCode() == null ? null : t.getGlAccountCode());
actualValues.add(t.getGlAccountName() == null ? null : t.getGlAccountName());
actualValues.add("DEBIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
actualValues.add("CREDIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
return actualValues;
}).collect(Collectors.toList());
possibleActualValuesList.add(actualValuesList);
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
if (containsExpectedValues) {
containsAnyExpected = true;
}
}
assertThat(containsAnyExpected)
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(i, possibleActualValuesList, expectedValues)).isTrue();
}
}
}
@@ -0,0 +1,59 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.common;
import io.cucumber.java.en.And;
import io.cucumber.java.en.When;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.test.data.job.DefaultJob;
import org.apache.fineract.test.service.JobService;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.springframework.beans.factory.annotation.Autowired;
@Slf4j
public class SchedulerStepDef extends AbstractStepDef {
@Autowired
private JobService jobService;
@And("Admin runs the Add Accrual Transactions job")
public void runAccrualTransaction() {
jobService.executeAndWait(DefaultJob.ADD_ACCRUAL_TRANSACTIONS);
}
@And("Admin runs the Add Periodic Accrual Transactions job")
public void runPeriodicAccrualTransaction() {
jobService.executeAndWait(DefaultJob.ADD_PERIODIC_ACCRUAL_TRANSACTIONS);
}
@And("Admin runs the Increase Business Date by 1 day job")
public void runIncreaseBusinessDate() {
jobService.executeAndWait(DefaultJob.INCREASE_BUSINESS_DAY);
}
@And("Admin runs the Loan Delinquency Classification job")
public void runLoanDelinquencyClassification() {
jobService.executeAndWait(DefaultJob.LOAN_DELINQUENCY_CLASSIFICATION);
}
@When("Admin runs COB job")
public void runCOB() {
jobService.executeAndWait(DefaultJob.LOAN_COB);
}
}
@@ -0,0 +1,87 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.common;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.client.models.GetRolesResponse;
import org.apache.fineract.client.models.PostRolesRequest;
import org.apache.fineract.client.models.PostRolesResponse;
import org.apache.fineract.client.models.PostUsersRequest;
import org.apache.fineract.client.models.PostUsersResponse;
import org.apache.fineract.client.models.PutRolesRoleIdPermissionsRequest;
import org.apache.fineract.client.models.PutRolesRoleIdPermissionsResponse;
import org.apache.fineract.client.services.RolesApi;
import org.apache.fineract.client.services.UsersApi;
import org.apache.fineract.test.api.ApiProperties;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.Utils;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class UserStepDef extends AbstractStepDef {
private static final String EMAIL = "test@test.com";
@Autowired
private RolesApi rolesApi;
@Autowired
private UsersApi usersApi;
@Autowired
private ApiProperties apiProperties;
@When("Admin creates new user with {string} username, {string} role name and given permissions:")
public void createUserWithUsernameAndRoles(String username, String roleName, List<String> permissions) throws IOException {
Response<List<GetRolesResponse>> retrieveAllRolesResponse = rolesApi.retrieveAllRoles().execute();
ErrorHelper.checkSuccessfulApiCall(retrieveAllRolesResponse);
PostRolesRequest newRoleRequest = new PostRolesRequest().name(Utils.randomNameGenerator(roleName, 8)).description(roleName);
Response<PostRolesResponse> createNewRole = rolesApi.createRole(newRoleRequest).execute();
ErrorHelper.checkSuccessfulApiCall(createNewRole);
Long roleId = createNewRole.body().getResourceId();
Map<String, Boolean> permissionMap = new HashMap<>();
permissions.forEach(role -> permissionMap.put(role, true));
PutRolesRoleIdPermissionsRequest putRolesRoleIdPermissionsRequest = new PutRolesRoleIdPermissionsRequest()
.permissions(permissionMap);
Response<PutRolesRoleIdPermissionsResponse> updateRolePermissionResponse = rolesApi
.updateRolePermissions(roleId, putRolesRoleIdPermissionsRequest).execute();
ErrorHelper.checkSuccessfulApiCall(updateRolePermissionResponse);
PostUsersRequest postUsersRequest = new PostUsersRequest() //
.username(Utils.randomNameGenerator(username, 8)) //
.email(EMAIL) //
.firstname(username) //
.lastname(username) //
.sendPasswordToEmail(Boolean.FALSE) //
.officeId(1L) //
.password(apiProperties.getPassword()) //
.repeatPassword(apiProperties.getPassword()) //
.roles(List.of(roleId));
Response<PostUsersResponse> createUserResponse = usersApi.create15(postUsersRequest).execute();
ErrorHelper.checkSuccessfulApiCall(createUserResponse);
testContext().set(TestContextKey.CREATED_SIMPLE_USER_RESPONSE, createUserResponse);
}
}
@@ -0,0 +1,89 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.apache.fineract.client.models.InlineJobRequest;
import org.apache.fineract.client.models.InlineJobResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.ExternalEventConfigurationApi;
import org.apache.fineract.client.services.InlineJobApi;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.assetexternalization.LoanAccountCustomSnapshotEvent;
import org.apache.fineract.test.messaging.event.loan.repayment.LoanRepaymentDueEvent;
import org.apache.fineract.test.messaging.event.loan.repayment.LoanRepaymentOverdueEvent;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class InlineCOBStepDef extends AbstractStepDef {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd MMMM yyyy");
@Autowired
private InlineJobApi inlineJobApi;
@Autowired
private EventAssertion eventAssertion;
@Autowired
ExternalEventConfigurationApi eventConfigurationApi;
@When("Admin runs inline COB job for Loan")
public void runInlineCOB() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
InlineJobRequest inlineJobRequest = new InlineJobRequest().addLoanIdsItem(loanId);
Response<InlineJobResponse> inlineJobResponse = inlineJobApi.executeInlineJob("LOAN_COB", inlineJobRequest).execute();
ErrorHelper.checkSuccessfulApiCall(inlineJobResponse);
}
@Then("Loan Repayment Due Business Event is created")
public void checkLoanRepaymentDueBusinessEventCreated() {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
eventAssertion.assertEventRaised(LoanRepaymentDueEvent.class, loanId);
}
@Then("Loan Repayment Overdue Business Event is created")
public void checkLoanRepaymentOverdueBusinessEventCreated() {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
eventAssertion.assertEventRaised(LoanRepaymentOverdueEvent.class, loanId);
}
@Then("LoanAccountCustomSnapshotBusinessEvent is created with business date {string}")
public void checkLoanRepaymentDueBusinessEventCreatedWithBusinessDate(String expectedBusinessDate) {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
LocalDate expectedBusinessDateParsed = LocalDate.parse(expectedBusinessDate, FORMATTER);
eventAssertion.assertEvent(LoanAccountCustomSnapshotEvent.class, loanId).isRaisedOnBusinessDate(expectedBusinessDateParsed);
}
}
@@ -0,0 +1,113 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import static org.assertj.core.api.Assertions.assertThat;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetLoanAccountLockResponse;
import org.apache.fineract.client.models.GetOldestCOBProcessedLoanResponse;
import org.apache.fineract.client.models.LoanAccountLock;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.DefaultApi;
import org.apache.fineract.client.services.LoanAccountLockApi;
import org.apache.fineract.client.services.LoanCobCatchUpApi;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@Slf4j
public class LoanCOBStepDef extends AbstractStepDef {
@Autowired
private LoanCobCatchUpApi loanCobCatchUpApi;
@Autowired
private LoanAccountLockApi loanAccountLockApi;
@Autowired
private DefaultApi defaultApi;
@Then("The cobProcessedDate of the oldest loan processed by COB is more than 1 day earlier than cobBusinessDate")
public void checkOldestCOBProcessed() throws IOException {
Response<GetOldestCOBProcessedLoanResponse> response = loanCobCatchUpApi.getOldestCOBProcessedLoan().execute();
ErrorHelper.checkSuccessfulApiCall(response);
LocalDate cobDate = response.body().getCobBusinessDate();
LocalDate cobDateMinusOne = cobDate.minusDays(1);
LocalDate cobProcessedDate = response.body().getCobProcessedDate();
log.info("cobDateMinusOne: {}", cobDateMinusOne);
log.info("cobProcessedDate: {}", cobProcessedDate);
boolean result = cobDateMinusOne.isAfter(cobProcessedDate);
assertThat(result).as(ErrorMessageHelper.wrongLastCOBProcessedLoanDate(cobProcessedDate, cobDateMinusOne)).isTrue();
}
@Then("There are no locked loan accounts")
public void listOfLockedLoansEmpty() throws IOException {
Response<GetLoanAccountLockResponse> response = loanAccountLockApi.retrieveLockedAccounts(0, 1000).execute();
ErrorHelper.checkSuccessfulApiCall(response);
int size = response.body().getContent().size();
assertThat(size).as(ErrorMessageHelper.listOfLockedLoansNotEmpty(response)).isEqualTo(0);
log.info("Size of List of the locked loans: {}", size);
}
@Then("The loan account is not locked")
public void loanIsNotInListOfLockedLoans() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
Long targetLoanId = loanResponse.body().getLoanId();
Response<GetLoanAccountLockResponse> response = loanAccountLockApi.retrieveLockedAccounts(0, 1000).execute();
ErrorHelper.checkSuccessfulApiCall(response);
List<LoanAccountLock> content = response.body().getContent();
boolean contains = content.stream()//
.map(LoanAccountLock::getLoanId)//
.anyMatch(targetLoanId::equals);//
assertThat(contains).as(ErrorMessageHelper.listOfLockedLoansContainsLoan(targetLoanId, response)).isFalse();
}
@When("Admin places a lock on loan account with an error message")
public void placeLockOnLoanAccountWithErrorMessage() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<Void> response = defaultApi.placeLockOnLoanAccount(loanId, "LOAN_COB_CHUNK_PROCESSING", "TestError").execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
@When("Admin places a lock on loan account WITHOUT an error message")
public void placeLockOnLoanAccountNoErrorMessage() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<Void> response = defaultApi.placeLockOnLoanAccount(loanId, "LOAN_COB_CHUNK_PROCESSING").execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
}
@@ -0,0 +1,188 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.gson.Gson;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.client.models.BusinessDateResponse;
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.BusinessDateManagementApi;
import org.apache.fineract.client.services.LoanChargesApi;
import org.apache.fineract.client.services.LoanTransactionsApi;
import org.apache.fineract.client.services.LoansApi;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.test.data.ChargeProductType;
import org.apache.fineract.test.factory.LoanRequestFactory;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.ErrorResponse;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class LoanChargeAdjustmentStepDef extends AbstractStepDef {
public static final String DATE_FORMAT = "dd MMMM yyyy";
private static final Gson GSON = new JSON().getGson();
@Autowired
private LoanChargesApi loanChargesApi;
@Autowired
private LoansApi loansApi;
@Autowired
private LoanTransactionsApi loanTransactionsApi;
@Autowired
private BusinessDateManagementApi businessDateManagementApi;
@When("Admin makes a charge adjustment for the last {string} type charge which is due on {string} with {double} EUR transaction amount and externalId {string}")
public void makeLoanChargeAdjustment(String chargeTypeEnum, String date, Double transactionAmount, String externalId)
throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "charges", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
Long transactionId = getTransactionIdForLastChargeMetConditions(chargeTypeEnum, date, loanDetailsResponse);
makeChargeAdjustmentCall(loanId, transactionId, externalId, transactionAmount);
}
@When("Admin makes a charge adjustment for the last {string} type charge which is due on {string} with transaction amount higher than the available charge amount")
public void loanChargeAdjustmentFailedOnWrongAmount(String chargeTypeEnum, String date) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "charges", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
Long transactionId = getTransactionIdForLastChargeMetConditions(chargeTypeEnum, date, loanDetailsResponse);
PostLoansLoanIdChargesChargeIdRequest chargeAdjustmentRequest = LoanRequestFactory.defaultChargeAdjustmentRequest().amount(8.0)
.externalId("");
Response<PostLoansLoanIdChargesChargeIdResponse> chargeAdjustmentResponseFail = loanChargesApi
.executeLoanCharge2(loanId, transactionId, chargeAdjustmentRequest, "adjustment").execute();
String string = chargeAdjustmentResponseFail.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(string, ErrorResponse.class);
Integer httpStatusCodeActual = errorResponse.getHttpStatusCode();
String developerMessageActual = errorResponse.getErrors().get(0).getDeveloperMessage();
Integer httpStatusCodeExpected = 403;
String developerMessageExpected = "Transaction amount cannot be higher than the available charge amount for adjustment: 7.000000";
assertThat(httpStatusCodeActual)
.as(ErrorMessageHelper.wrongErrorCodeInFailedChargeAdjustment(httpStatusCodeActual, httpStatusCodeExpected))
.isEqualTo(httpStatusCodeExpected);
assertThat(developerMessageActual)
.as(ErrorMessageHelper.wrongErrorMessageInFailedChargeAdjustment(developerMessageActual, developerMessageExpected))
.isEqualTo(developerMessageExpected);
}
@When("Admin reverts the charge adjustment which was raised on {string} with {double} EUR transaction amount")
public void loanChargeAdjustmentUndo(String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
Long transactionId = getTransactionIdForTransactionMetConditions(transactionDate, transactionAmount, loanDetailsResponse);
Response<List<BusinessDateResponse>> businessDateResponse = businessDateManagementApi.getBusinessDates().execute();
LocalDate businessDate = businessDateResponse.body().get(0).getDate();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
String businessDateActual = formatter.format(businessDate);
PostLoansLoanIdTransactionsTransactionIdRequest chargeAdjustmentUndoRequest = LoanRequestFactory
.defaultChargeAdjustmentTransactionUndoRequest().transactionDate(businessDateActual);
Response<PostLoansLoanIdTransactionsResponse> chargeAdjustmentUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, transactionId, chargeAdjustmentUndoRequest, "").execute();
ErrorHelper.checkSuccessfulApiCall(chargeAdjustmentUndoResponse);
}
private Long getTransactionIdForTransactionMetConditions(String transactionDate, double transactionAmount,
Response<GetLoansLoanIdResponse> loanDetailsResponse) {
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
GetLoansLoanIdTransactions transactionMetConditions = new GetLoansLoanIdTransactions();
for (int i = 0; i < transactions.size(); i++) {
LocalDate date = transactions.get(i).getDate();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
String dateActual = formatter.format(date);
Double amountActual = transactions.get(i).getAmount();
if (dateActual.equals(transactionDate) && amountActual.equals(transactionAmount)) {
transactionMetConditions = transactions.get(i);
break;
}
}
return transactionMetConditions.getId();
}
private void makeChargeAdjustmentCall(Long loanId, Long transactionId, String externalId, double transactionAmount) throws IOException {
PostLoansLoanIdChargesChargeIdRequest chargeAdjustmentRequest = LoanRequestFactory.defaultChargeAdjustmentRequest()
.amount(transactionAmount).externalId(externalId);
Response<PostLoansLoanIdChargesChargeIdResponse> chargeAdjustmentResponse = loanChargesApi
.executeLoanCharge2(loanId, transactionId, chargeAdjustmentRequest, "adjustment").execute();
testContext().set(TestContextKey.LOAN_CHARGE_ADJUSTMENT_RESPONSE, chargeAdjustmentResponse);
ErrorHelper.checkSuccessfulApiCall(chargeAdjustmentResponse);
}
private Long getTransactionIdForLastChargeMetConditions(String chargeTypeEnum, String date,
Response<GetLoansLoanIdResponse> loanDetailsResponse) {
List<GetLoansLoanIdLoanChargeData> charges = loanDetailsResponse.body().getCharges();
ChargeProductType chargeType = ChargeProductType.valueOf(chargeTypeEnum);
Long chargeProductId = chargeType.getValue();
List<GetLoansLoanIdLoanChargeData> resultList = new ArrayList<>();
charges.forEach(charge -> {
Long chargeId = charge.getChargeId();
LocalDate dueDate = charge.getDueDate();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
String chargeDueDate = formatter.format(dueDate);
if (chargeId.equals(chargeProductId) && chargeDueDate.equals(date)) {
resultList.add(charge);
}
});
GetLoansLoanIdLoanChargeData lastChargeResult = resultList.get(resultList.size() - 1);
return lastChargeResult.getId();
}
}
@@ -0,0 +1,174 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
import org.apache.fineract.avro.loan.v1.LoanTransactionEnumDataV1;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.GetLoansType;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.LoanTransactionsApi;
import org.apache.fineract.client.services.LoansApi;
import org.apache.fineract.test.data.paymenttype.DefaultPaymentType;
import org.apache.fineract.test.data.paymenttype.PaymentTypeResolver;
import org.apache.fineract.test.factory.LoanRequestFactory;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.loan.LoanBalanceChangedEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargebackTransactionEvent;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class LoanChargeBackStepDef extends AbstractStepDef {
@Autowired
private LoanTransactionsApi loanTransactionsApi;
@Autowired
private LoansApi loansApi;
@Autowired
private EventAssertion eventAssertion;
@Autowired
private PaymentTypeResolver paymentTypeResolver;
@When("Admin makes {string} chargeback with {double} EUR transaction amount")
public void makeLoanChargeback(String repaymentType, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
Long transactionId = Long.valueOf(repaymentResponse.body().getResourceId());
makeChargebackCall(loanId, transactionId, repaymentType, transactionAmount);
}
@When("Admin makes {string} chargeback with {double} EUR transaction amount for Payment nr. {double}")
public void makeLoanChargebackForPayment(String repaymentType, double transactionAmount, double paymentNr) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
List<GetLoansLoanIdTransactions> transactions = loanDetails.body().getTransactions();
List<Long> transactionIdList = new ArrayList<>();
for (GetLoansLoanIdTransactions f : transactions) {
String code = f.getType().getCode();
if (code.equals("loanTransactionType.repayment")) {
transactionIdList.add(f.getId());
}
}
Collections.sort(transactionIdList);
Long transactionId = transactionIdList.get((int) paymentNr - 1);
makeChargebackCall(loanId, transactionId, repaymentType, transactionAmount);
}
@When("Admin makes {string} chargeback with {double} EUR transaction amount for Downpayment nr. {double}")
public void makeLoanChargebackForDownpaymentayment(String repaymentType, double transactionAmount, double paymentNr)
throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
List<GetLoansLoanIdTransactions> transactions = loanDetails.body().getTransactions();
List<Long> transactionIdList = new ArrayList<>();
for (GetLoansLoanIdTransactions f : transactions) {
String code = f.getType().getCode();
if (code.equals("loanTransactionType.downPayment")) {
transactionIdList.add(f.getId());
}
}
Collections.sort(transactionIdList);
Long transactionId = transactionIdList.get((int) paymentNr - 1);
makeChargebackCall(loanId, transactionId, repaymentType, transactionAmount);
}
private void makeChargebackCall(Long loanId, Long transactionId, String repaymentType, double transactionAmount) throws IOException {
DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType);
Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
PostLoansLoanIdTransactionsTransactionIdRequest chargebackRequest = LoanRequestFactory.defaultChargebackRequest()
.paymentTypeId(paymentTypeValue).transactionAmount(transactionAmount);
Response<PostLoansLoanIdTransactionsResponse> chargebackResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, transactionId, chargebackRequest, "chargeback").execute();
testContext().set(TestContextKey.LOAN_CHARGEBACK_RESPONSE, chargebackResponse);
ErrorHelper.checkSuccessfulApiCall(chargebackResponse);
checkEvents(chargebackResponse);
}
private void checkEvents(Response<PostLoansLoanIdTransactionsResponse> chargebackResponse) throws IOException {
PostLoansLoanIdTransactionsResponse responseBody = chargebackResponse.body();
Long loanId = responseBody.getLoanId();
eventAssertion.assertEvent(LoanBalanceChangedEvent.class, loanId).extractingData(data -> data.getId()).isEqualTo(loanId);
checkLoanChargebackTransactionEvent(responseBody);
}
private void checkLoanChargebackTransactionEvent(PostLoansLoanIdTransactionsResponse chargebackResponse) throws IOException {
// get loanId and transactionId
Long loanId = chargebackResponse.getLoanId();
long transactionId = Long.valueOf(chargebackResponse.getResourceId());
// retrieve transaction details
Response<GetLoansLoanIdTransactionsTransactionIdResponse> transactionResponse = loanTransactionsApi
.retrieveTransaction(loanId, transactionId, "").execute();
ErrorHelper.checkSuccessfulApiCall(transactionResponse);
GetLoansLoanIdTransactionsTransactionIdResponse transactionResponseBody = transactionResponse.body();
// Get transaction type from response
GetLoansType transactionType = transactionResponseBody.getType();
// Build expected avro transaction type from api response type
LoanTransactionEnumDataV1 expectedEventTransactionType = LoanTransactionEnumDataV1.newBuilder()
.setId(transactionType.getId().intValue()).setCode(transactionType.getCode()).setValue("Chargeback")
.setDisbursement(transactionType.getDisbursement()).setRepaymentAtDisbursement(transactionType.getRepaymentAtDisbursement())
.setRepayment(transactionType.getRepayment()).setMerchantIssuedRefund(false).setPayoutRefund(false).setGoodwillCredit(false)
.setChargeRefund(false).setContra(transactionType.getContra()).setWaiveInterest(transactionType.getWaiveInterest())
.setWaiveCharges(transactionType.getWaiveCharges()).setAccrual(false).setWriteOff(transactionType.getWriteOff())
.setRecoveryRepayment(transactionType.getRecoveryRepayment()).setInitiateTransfer(false).setApproveTransfer(false)
.setWithdrawTransfer(false).setRejectTransfer(false).setChargePayment(false).setRefund(false).setRefundForActiveLoans(false)
.setCreditBalanceRefund(false).setChargeback(true).build();
// verify payload for loanId, transactionId, transactionType, amount
eventAssertion.assertEvent(LoanChargebackTransactionEvent.class, transactionId).extractingData(LoanTransactionDataV1::getLoanId)
.isEqualTo(loanId).extractingData(LoanTransactionDataV1::getType).isEqualTo(expectedEventTransactionType)
.extractingBigDecimal(LoanTransactionDataV1::getAmount).isEqualTo(BigDecimal.valueOf(transactionResponseBody.getAmount()));
}
}
@@ -0,0 +1,296 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.gson.Gson;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.avro.loan.v1.LoanChargeDataV1;
import org.apache.fineract.client.models.GetLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PutChargeTransactionChangesRequest;
import org.apache.fineract.client.models.PutChargeTransactionChangesResponse;
import org.apache.fineract.client.services.LoanChargesApi;
import org.apache.fineract.client.services.LoanTransactionsApi;
import org.apache.fineract.client.services.LoansApi;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.test.data.ChargeProductType;
import org.apache.fineract.test.data.ErrorMessageType;
import org.apache.fineract.test.factory.LoanChargeRequestFactory;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.ErrorResponse;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.loan.charge.LoanAddChargeEvent;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@Slf4j
public class LoanChargeStepDef extends AbstractStepDef {
public static final String DEFAULT_DATE_FORMAT = "dd MMMM yyyy";
public static final String DATE_FORMAT_EVENTS = "yyyy-MM-dd";
public static final Double DEFAULT_CHARGE_FEE_FLAT = 10D;
private static final Gson GSON = new JSON().getGson();
@Autowired
private LoanChargesApi loanChargesApi;
@Autowired
private LoanTransactionsApi loanTransactionsApi;
@Autowired
private LoansApi loansApi;
@Autowired
private EventAssertion eventAssertion;
@When("Admin adds {string} due date charge with {string} due date and {double} EUR transaction amount")
public void addChargeDueDate(String chargeType, String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
ChargeProductType chargeProductType = ChargeProductType.valueOf(chargeType);
Long chargeTypeId = chargeProductType.getValue();
if (chargeTypeId.equals(ChargeProductType.LOAN_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|| chargeTypeId.equals(ChargeProductType.LOAN_TRANCHE_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|| chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_PERCENTAGE_FEE.getValue())) {
throw new IllegalStateException(String.format("The requested %s charge is NOT due date type, cannot be used here", chargeType));
}
PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest().chargeId(chargeTypeId)
.dueDate(transactionDate).amount(transactionAmount);
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi.executeLoanCharge(loanId, loanIdChargesRequest, "")
.execute();
ErrorHelper.checkSuccessfulApiCall(loanChargeResponse);
testContext().set(TestContextKey.ADD_DUE_DATE_CHARGE_RESPONSE, loanChargeResponse);
addChargeEventCheck(loanChargeResponse);
}
@When("Admin adds {string} charge with {double} % of transaction amount")
public void addChargePercentage(String chargeType, double transactionPercentageAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
ChargeProductType chargeProductType = ChargeProductType.valueOf(chargeType);
Long chargeTypeId = chargeProductType.getValue();
if (!chargeTypeId.equals(ChargeProductType.LOAN_DISBURSEMENT_PERCENTAGE_FEE.getValue())
&& !chargeTypeId.equals(ChargeProductType.LOAN_TRANCHE_DISBURSEMENT_PERCENTAGE_FEE.getValue())
&& !chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_PERCENTAGE_FEE.getValue())) {
throw new IllegalStateException(String.format("The requested %s charge is due date type, cannot be used here", chargeType));
}
PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest().chargeId(chargeTypeId)
.amount(transactionPercentageAmount);
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi.executeLoanCharge(loanId, loanIdChargesRequest, "")
.execute();
ErrorHelper.checkSuccessfulApiCall(loanChargeResponse);
testContext().set(TestContextKey.ADD_DUE_DATE_CHARGE_RESPONSE, loanChargeResponse);
}
@Then("Admin is not able to add {string} due date charge with {string} due date and {double} EUR transaction amount because the of charged-off account")
public void addChargeDueDateOnChargedOff(String chargeType, String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
ChargeProductType chargeProductType = ChargeProductType.valueOf(chargeType);
Long chargeTypeId = chargeProductType.getValue();
PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest().chargeId(chargeTypeId)
.dueDate(transactionDate).amount(transactionAmount);
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi.executeLoanCharge(loanId, loanIdChargesRequest, "")
.execute();
testContext().set(TestContextKey.ADD_DUE_DATE_CHARGE_RESPONSE, loanChargeResponse);
ErrorResponse errorDetails = ErrorResponse.from(loanChargeResponse);
assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.addChargeForChargeOffLoanCodeMsg()).isEqualTo(403);
assertThat(errorDetails.getSingleError().getDeveloperMessage())
.isEqualTo(ErrorMessageHelper.addChargeForChargeOffLoanFailure(loanId));
}
@And("Admin adds a {double} % Processing charge to the loan with {string} locale on date: {string}")
public void addProcessingFee(double chargeAmount, String locale, String date) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest()
.chargeId(ChargeProductType.LOAN_PERCENTAGE_PROCESSING_FEE.value).amount(chargeAmount).dueDate(date)
.dateFormat(DEFAULT_DATE_FORMAT).locale(locale);
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi.executeLoanCharge(loanId, loanIdChargesRequest, "")
.execute();
ErrorHelper.checkSuccessfulApiCall(loanChargeResponse);
testContext().set(TestContextKey.ADD_PROCESSING_FEE_RESPONSE, loanChargeResponse);
}
@And("Admin adds an NSF fee because of payment bounce with {string} transaction date")
public void addNSFfee(String date) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest()
.chargeId(ChargeProductType.LOAN_NSF_FEE.value).amount(DEFAULT_CHARGE_FEE_FLAT).dueDate(date)
.dateFormat(DEFAULT_DATE_FORMAT);
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi.executeLoanCharge(loanId, loanIdChargesRequest, "")
.execute();
ErrorHelper.checkSuccessfulApiCall(loanChargeResponse);
testContext().set(TestContextKey.ADD_NSF_FEE_RESPONSE, loanChargeResponse);
}
@And("Admin waives charge")
public void waiveCharge() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = testContext().get(TestContextKey.ADD_NSF_FEE_RESPONSE);
Long chargeId = Long.valueOf(loanChargeResponse.body().getResourceId());
PostLoansLoanIdChargesChargeIdRequest waiveRequest = new PostLoansLoanIdChargesChargeIdRequest();
Response<PostLoansLoanIdChargesChargeIdResponse> waiveResponse = loanChargesApi
.executeLoanCharge2(loanId, chargeId, waiveRequest, "waive").execute();
ErrorHelper.checkSuccessfulApiCall(waiveResponse);
testContext().set(TestContextKey.WAIVE_CHARGE_RESPONSE, waiveResponse);
}
@And("Admin waives due date charge")
public void waiveDueDateCharge() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = testContext().get(TestContextKey.ADD_DUE_DATE_CHARGE_RESPONSE);
Long chargeId = Long.valueOf(loanChargeResponse.body().getResourceId());
PostLoansLoanIdChargesChargeIdRequest waiveRequest = new PostLoansLoanIdChargesChargeIdRequest();
Response<PostLoansLoanIdChargesChargeIdResponse> waiveResponse = loanChargesApi
.executeLoanCharge2(loanId, chargeId, waiveRequest, "waive").execute();
ErrorHelper.checkSuccessfulApiCall(waiveResponse);
testContext().set(TestContextKey.WAIVE_CHARGE_RESPONSE, waiveResponse);
}
@And("Admin makes waive undone for charge")
public void undoWaiveForCharge() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
List<GetLoansLoanIdTransactions> transactions = loanDetails.body().getTransactions();
Long transactionId = 0L;
for (GetLoansLoanIdTransactions f : transactions) {
String code = f.getType().getCode();
if (code.equals("loanTransactionType.waiveCharges")) {
transactionId = f.getId();
}
}
PutChargeTransactionChangesRequest undoWaiveRequest = new PutChargeTransactionChangesRequest();
Response<PutChargeTransactionChangesResponse> undoWaiveResponse = loanTransactionsApi
.undoWaiveCharge(loanId, transactionId, undoWaiveRequest).execute();
ErrorHelper.checkSuccessfulApiCall(undoWaiveResponse);
testContext().set(TestContextKey.UNDO_WAIVE_RESPONSE, undoWaiveResponse);
}
@Then("Charge is successfully added to the loan")
public void loanChargeStatus() throws IOException {
Response<PostLoansLoanIdChargesResponse> response = testContext().get(TestContextKey.ADD_NSF_FEE_RESPONSE);
assertThat(response.isSuccessful()).as(ErrorMessageHelper.requestFailed(response)).isTrue();
assertThat(response.code()).as(ErrorMessageHelper.requestFailedWithCode(response)).isEqualTo(200);
}
@Then("Charge is successfully added to the loan with {float} EUR")
public void checkLoanChargeAmount(float chargeAmount) throws IOException {
Response<PostLoansLoanIdChargesResponse> response = testContext().get(TestContextKey.ADD_PROCESSING_FEE_RESPONSE);
Response<GetLoansLoanIdChargesChargeIdResponse> loanChargeAmount = loanChargesApi
.retrieveLoanCharge(response.body().getLoanId(), Long.valueOf(response.body().getResourceId())).execute();
ErrorHelper.checkSuccessfulApiCall(response);
assertThat(loanChargeAmount.body().getAmount()).as("Charge amount is wrong").isEqualTo(chargeAmount);
}
private void addChargeEventCheck(Response<PostLoansLoanIdChargesResponse> loanChargeResponse) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT_EVENTS);
Response<GetLoansLoanIdChargesChargeIdResponse> chargeDetails = loanChargesApi
.retrieveLoanCharge(loanChargeResponse.body().getLoanId(), loanChargeResponse.body().getResourceId()).execute();
GetLoansLoanIdChargesChargeIdResponse body = chargeDetails.body();
eventAssertion.assertEvent(LoanAddChargeEvent.class, loanChargeResponse.body().getResourceId())
.extractingData(LoanChargeDataV1::getName).isEqualTo(body.getName())
.extractingData(loanChargeDataV1 -> loanChargeDataV1.getAmount().longValue()).isEqualTo(body.getAmount().longValue())
.extractingData(LoanChargeDataV1::getDueDate).isEqualTo(formatter.format(body.getDueDate()));
}
@Then("Loan charge transaction with the following data results a {int} error and {string} error message")
public void chargeOffTransactionError(int errorCodeExpected, String errorMessageType, DataTable table) throws IOException {
List<List<String>> data = table.asLists();
List<String> chargeData = data.get(1);
String chargeType = chargeData.get(0);
String transactionDate = chargeData.get(1);
Double transactionAmount = Double.valueOf(chargeData.get(2));
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
ErrorMessageType errorMsgType = ErrorMessageType.valueOf(errorMessageType);
String errorMessageExpectedRaw = errorMsgType.getValue();
String errorMessageExpected = String.format(errorMessageExpectedRaw, loanId);
ChargeProductType chargeProductType = ChargeProductType.valueOf(chargeType);
Long chargeTypeId = chargeProductType.getValue();
if (chargeTypeId.equals(ChargeProductType.LOAN_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|| chargeTypeId.equals(ChargeProductType.LOAN_TRANCHE_DISBURSEMENT_PERCENTAGE_FEE.getValue())
|| chargeTypeId.equals(ChargeProductType.LOAN_INSTALLMENT_PERCENTAGE_FEE.getValue())) {
throw new IllegalStateException(String.format("The requested %s charge is NOT due date type, cannot be used here", chargeType));
}
PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest().chargeId(chargeTypeId)
.dueDate(transactionDate).amount(transactionAmount);
Response<PostLoansLoanIdChargesResponse> loanChargeResponse = loanChargesApi.executeLoanCharge(loanId, loanIdChargesRequest, "")
.execute();
int errorCodeActual = loanChargeResponse.code();
String errorBody = loanChargeResponse.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(errorBody, ErrorResponse.class);
String errorMessageActual = errorResponse.getErrors().get(0).getDeveloperMessage();
assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected);
assertThat(errorMessageActual).as(ErrorMessageHelper.wrongErrorMessage(errorMessageActual, errorMessageExpected))
.isEqualTo(errorMessageExpected);
log.info("ERROR CODE: {}", errorCodeActual);
log.info("ERROR MESSAGE: {}", errorMessageActual);
}
}
@@ -0,0 +1,771 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
import org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1;
import org.apache.fineract.client.models.GetDelinquencyActionsResponse;
import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
import org.apache.fineract.client.models.GetDelinquencyTagHistoryResponse;
import org.apache.fineract.client.models.GetLoansLoanIdDelinquencyPausePeriod;
import org.apache.fineract.client.models.GetLoansLoanIdDelinquencySummary;
import org.apache.fineract.client.models.GetLoansLoanIdLoanInstallmentLevelDelinquency;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetUsersUserIdResponse;
import org.apache.fineract.client.models.PostLoansDelinquencyActionRequest;
import org.apache.fineract.client.models.PostLoansDelinquencyActionResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PostUsersResponse;
import org.apache.fineract.client.services.LoansApi;
import org.apache.fineract.client.services.UsersApi;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.test.api.ApiProperties;
import org.apache.fineract.test.data.DelinquencyRange;
import org.apache.fineract.test.data.LoanStatus;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.ErrorResponse;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.EventCheckHelper;
import org.apache.fineract.test.messaging.event.loan.delinquency.LoanDelinquencyRangeChangeEvent;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@Slf4j
public class LoanDelinquencyStepDef extends AbstractStepDef {
public static final String DATE_FORMAT = "dd MMMM yyyy";
public static final String DEFAULT_LOCALE = "en";
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
private static final Gson GSON = new JSON().getGson();
@Autowired
private LoansApi loansApi;
@Autowired
private EventAssertion eventAssertion;
@Autowired
private EventCheckHelper eventCheckHelper;
@Autowired
private ApiProperties apiProperties;
@Autowired
private UsersApi usersApi;
@Then("Admin checks that delinquency range is: {string} and has delinquentDate {string}")
public void checkDelinquencyRange(String range, String delinquentDateExpected) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetails);
Integer loanStatus = loanDetails.body().getStatus().getId();
if (!LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.value.equals(loanStatus) && !LoanStatus.APPROVED.value.equals(loanStatus)) {
String delinquentDateExpectedValue = "".equals(delinquentDateExpected) ? null : delinquentDateExpected;
eventAssertion.assertEvent(LoanDelinquencyRangeChangeEvent.class, loanId)//
.extractingData(LoanAccountDelinquencyRangeDataV1::getDelinquentDate)//
.isEqualTo(delinquentDateExpectedValue);//
}
DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(range);
String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue();
String actualDelinquencyRangeValue = DelinquencyRange.NO_DELINQUENCY.value;
GetDelinquencyRangesResponse actualDelinquencyRange = loanDetails.body().getDelinquencyRange();
if (actualDelinquencyRange != null) {
actualDelinquencyRangeValue = actualDelinquencyRange.getClassification();
}
assertThat(actualDelinquencyRangeValue)
.as(ErrorMessageHelper.delinquencyRangeError(actualDelinquencyRangeValue, expectedDelinquencyRangeValue))
.isEqualTo(expectedDelinquencyRangeValue);
}
@Then("Admin checks that {string}th delinquency range is: {string} and added on: {string} and has delinquentDate {string}")
public void checkDelinquencyRange(String nthInList, String range, String addedOnDate, String delinquentDateExpected)
throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
String delinquentDateExpectedValue = "".equals(delinquentDateExpected) ? null : delinquentDateExpected;
eventAssertion.assertEvent(LoanDelinquencyRangeChangeEvent.class, loanId)
.extractingData(LoanAccountDelinquencyRangeDataV1::getDelinquentDate).isEqualTo(delinquentDateExpectedValue);
DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(range);
String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue();
Response<List<GetDelinquencyTagHistoryResponse>> delinquencyHistoryDetails = loansApi.getDelinquencyTagHistory(loanId).execute();
ErrorHelper.checkSuccessfulApiCall(delinquencyHistoryDetails);
String actualDelinquencyRangeValue = DelinquencyRange.NO_DELINQUENCY.value;
String actualDelinquencyAddedOnDate = "";
int i = Integer.parseInt(nthInList) - 1;
GetDelinquencyTagHistoryResponse delinquencyTag = delinquencyHistoryDetails.body().get(i);
if (delinquencyTag != null) {
actualDelinquencyRangeValue = delinquencyTag.getDelinquencyRange().getClassification();
actualDelinquencyAddedOnDate = formatter.format(delinquencyTag.getAddedOnDate());
}
assertThat(actualDelinquencyRangeValue)
.as(ErrorMessageHelper.delinquencyRangeError(actualDelinquencyRangeValue, expectedDelinquencyRangeValue))
.isEqualTo(expectedDelinquencyRangeValue);
assertThat(actualDelinquencyAddedOnDate).as(ErrorMessageHelper.delinquencyRangeError(actualDelinquencyAddedOnDate, addedOnDate))
.isEqualTo(addedOnDate);
}
@Then("Loan delinquency history has the following details:")
public void delinquencyHistoryCheck(DataTable table) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
List<List<String>> dataExpected = table.asLists();
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<List<GetDelinquencyTagHistoryResponse>> delinquencyHistoryDetails = loansApi.getDelinquencyTagHistory(loanId).execute();
ErrorHelper.checkSuccessfulApiCall(delinquencyHistoryDetails);
List<GetDelinquencyTagHistoryResponse> body = delinquencyHistoryDetails.body();
for (int i = 0; i < body.size(); i++) {
List<String> line = dataExpected.get(i + 1);
DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(line.get(0));
String classificationExpected = expectedDelinquencyRange.getValue();
String addedOnDateExpected = line.get(1);
String liftedOnDateExpected = line.get(2);
String classificationActual = body.get(i).getDelinquencyRange().getClassification();
String addedOnDateActual = body.get(i).getAddedOnDate() == null ? null : formatter.format(body.get(i).getAddedOnDate());
String liftedOnDateActual = body.get(i).getLiftedOnDate() == null ? null : formatter.format(body.get(i).getLiftedOnDate());
assertThat(classificationActual)
.as(ErrorMessageHelper.wrongDataInDelinquencyHistoryClassification(classificationActual, classificationExpected))
.isEqualTo(classificationExpected);
assertThat(addedOnDateActual)
.as(ErrorMessageHelper.wrongDataInDelinquencyHistoryAddedOnDate(addedOnDateActual, addedOnDateExpected))
.isEqualTo(addedOnDateExpected);
assertThat(liftedOnDateActual)
.as(ErrorMessageHelper.wrongDataInDelinquencyHistoryLiftedOnDate(liftedOnDateActual, liftedOnDateExpected))
.isEqualTo(liftedOnDateExpected);
}
}
@When("Admin initiate a DELINQUENCY PAUSE with startDate: {string} and endDate: {string}")
public void delinquencyPause(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("pause")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response);
ErrorHelper.checkSuccessfulApiCall(response);
eventCheckHelper.loanAccountDelinquencyPauseChangedBusinessEventCheck(loanId);
}
@When("Created user with CREATE_DELINQUENCY_ACTION permission initiate a DELINQUENCY PAUSE with startDate: {string} and endDate: {string}")
public void delinquencyPauseWithCreatedUser(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("pause")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Map<String, String> headerMap = new HashMap<>();
Response<PostUsersResponse> createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE);
Long createdUserId = createUserResponse.body().getResourceId();
Response<GetUsersUserIdResponse> user = usersApi.retrieveOne31(createdUserId).execute();
ErrorHelper.checkSuccessfulApiCall(user);
String authorizationString = user.body().getUsername() + ":" + apiProperties.getPassword();
Base64 base64 = new Base64();
headerMap.put("Authorization",
"Basic " + new String(base64.encode(authorizationString.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request, headerMap).execute();
testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response);
ErrorHelper.checkSuccessfulApiCall(response);
eventCheckHelper.loanAccountDelinquencyPauseChangedBusinessEventCheck(loanId);
}
@Then("Created user with no CREATE_DELINQUENCY_ACTION permission gets an error when initiate a DELINQUENCY PAUSE with startDate: {string} and endDate: {string}")
public void delinquencyPauseWithCreatedUserNOPermissionError(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
int errorCodeExpected = 403;
String errorMessageExpected = "User has no authority to CREATE delinquency_actions";
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("pause")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Map<String, String> headerMap = new HashMap<>();
Response<PostUsersResponse> createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE);
Long createdUserId = createUserResponse.body().getResourceId();
Response<GetUsersUserIdResponse> user = usersApi.retrieveOne31(createdUserId).execute();
ErrorHelper.checkSuccessfulApiCall(user);
String authorizationString = user.body().getUsername() + ":" + apiProperties.getPassword();
Base64 base64 = new Base64();
headerMap.put("Authorization",
"Basic " + new String(base64.encode(authorizationString.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request, headerMap).execute();
testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response);
int errorCodeActual = response.code();
String errorBody = response.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(errorBody, ErrorResponse.class);
String errorMessageActual = errorResponse.getErrors().get(0).getDeveloperMessage();
assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected);
assertThat(errorMessageActual).as(ErrorMessageHelper.wrongErrorMessage(errorMessageActual, errorMessageExpected))
.isEqualTo(errorMessageExpected);
log.info("ERROR CODE: {}", errorCodeActual);
log.info("ERROR MESSAGE: {}", errorMessageActual);
}
@When("Admin initiate a DELINQUENCY RESUME with startDate: {string}")
public void delinquencyResume(String startDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("resume")//
.startDate(startDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response);
ErrorHelper.checkSuccessfulApiCall(response);
eventCheckHelper.loanAccountDelinquencyPauseChangedBusinessEventCheck(loanId);
}
@When("Admin initiate a DELINQUENCY PAUSE by loanExternalId with startDate: {string} and endDate: {string}")
public void delinquencyPauseByLoanExternalId(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
String loanExternalId = loanResponse.body().getResourceExternalId();
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("pause")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction1(loanExternalId, request).execute();
testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response);
ErrorHelper.checkSuccessfulApiCall(response);
eventCheckHelper.loanAccountDelinquencyPauseChangedBusinessEventCheck(loanId);
}
@When("Admin initiate a DELINQUENCY RESUME by loanExternalId with startDate: {string}")
public void delinquencyResumeByLoanExternalId(String startDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
String loanExternalId = loanResponse.body().getResourceExternalId();
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("resume")//
.startDate(startDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction1(loanExternalId, request).execute();
testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response);
ErrorHelper.checkSuccessfulApiCall(response);
eventCheckHelper.loanAccountDelinquencyPauseChangedBusinessEventCheck(loanId);
}
@Then("Delinquency-actions have the following data:")
public void getDelinquencyActionData(DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<List<String>> data = table.asLists();
int nrOfLinesExpected = data.size() - 1;
Response<List<GetDelinquencyActionsResponse>> response = loansApi.getLoanDelinquencyActions(loanId).execute();
int nrOfLinesActual = response.body().size();
assertThat(nrOfLinesActual)//
.as(ErrorMessageHelper.wrongNumberOfLinesInDelinquencyActions(nrOfLinesActual, nrOfLinesExpected))//
.isEqualTo(nrOfLinesExpected);//
for (int i = 1; i < data.size(); i++) {
List<String> expectedValues = data.get(i);
GetDelinquencyActionsResponse lineActual = response.body().get(i - 1);
List<String> actualValues = new ArrayList<>();
actualValues.add(Objects.requireNonNull(lineActual.getAction()));
actualValues.add(FORMATTER.format(Objects.requireNonNull(lineActual.getStartDate())));
actualValues.add(lineActual.getEndDate() == null ? null : FORMATTER.format(lineActual.getEndDate()));
assertThat(actualValues)//
.as(ErrorMessageHelper.wrongValueInLineDelinquencyActions(i, actualValues, expectedValues))//
.isEqualTo(expectedValues);//
}
}
@Then("Initiating a delinquency-action other than PAUSE or RESUME in action field results an error - startDate: {string}, endDate: {string}")
public void actionFieldError(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("TEST")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Invalid Delinquency Action: TEST";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Initiating a DELINQUENCY PAUSE with startDate before the actual business date results an error - startDate: {string}, endDate: {string}")
public void delinquencyPauseStartDateError(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("pause")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Start date of pause period must be in the future";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Initiating a DELINQUENCY PAUSE on a non-active loan results an error - startDate: {string}, endDate: {string}")
public void delinquencyPauseNonActiveLoanError(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("pause")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Delinquency actions can be created only for active loans.";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Initiating a DELINQUENCY RESUME on a non-active loan results an error - startDate: {string}")
public void delinquencyResumeNonActiveLoanError(String startDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("resume")//
.startDate(startDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Delinquency actions can be created only for active loans.";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Overlapping PAUSE periods result an error - startDate: {string}, endDate: {string}")
public void delinquencyPauseOverlappingError(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("pause")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Delinquency pause period cannot overlap with another pause period";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Initiating a DELINQUENCY RESUME without an active PAUSE period results an error - startDate: {string}")
public void delinquencyResumeWithoutPauseError(String startDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("resume")//
.startDate(startDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Resume Delinquency Action can only be created during an active pause";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Initiating a DELINQUENCY RESUME with start date other than actual business date results an error - startDate: {string}")
public void delinquencyResumeStartDateError(String startDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("resume")//
.startDate(startDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Start date of the Resume Delinquency action must be the current business date";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Initiating a DELINQUENCY RESUME with an endDate results an error - startDate: {string}, endDate: {string}")
public void delinquencyResumeWithEndDateError(String startDate, String endDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()//
.action("resume")//
.startDate(startDate)//
.endDate(endDate)//
.dateFormat(DATE_FORMAT)//
.locale(DEFAULT_LOCALE);//
Response<PostLoansDelinquencyActionResponse> response = loansApi.createLoanDelinquencyAction(loanId, request).execute();
int errorCodeExpected = 400;
String errorMessageExpected = "Resume Delinquency action can not have end date";
errorMessageAssertation(response, errorCodeExpected, errorMessageExpected);
}
@Then("Installment level delinquency event has correct data")
public void installmentLevelDelinquencyEventCheck() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
eventCheckHelper.installmentLevelDelinquencyRangeChangeEventCheck(loanId);
}
@Then("INSTALLMENT level delinquency is null")
public void installmentLevelDelinquencyNull() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetails);
List<GetLoansLoanIdLoanInstallmentLevelDelinquency> installmentLevelDelinquency = loanDetails.body().getDelinquent()
.getInstallmentLevelDelinquency() == null ? null : loanDetails.body().getDelinquent().getInstallmentLevelDelinquency();
assertThat(installmentLevelDelinquency).isNull();
}
@Then("Loan has the following LOAN level delinquency data:")
public void loanDelinquencyDataCheck(DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<String> expectedValuesList = table.asLists().get(1);
DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(expectedValuesList.get(0));
String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue();
expectedValuesList.set(0, expectedDelinquencyRangeValue);
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetails);
String actualDelinquencyRangeValue = loanDetails.body().getDelinquencyRange() == null ? "NO_DELINQUENCY"
: loanDetails.body().getDelinquencyRange().getClassification();
GetLoansLoanIdDelinquencySummary delinquent = loanDetails.body().getDelinquent();
List<String> actualValuesList = List.of(actualDelinquencyRangeValue, delinquent.getDelinquentAmount().toString(),
delinquent.getDelinquentDate() == null ? "null" : FORMATTER.format(delinquent.getDelinquentDate()),
delinquent.getDelinquentDays().toString(), delinquent.getPastDueDays().toString());
assertThat(actualValuesList).as(ErrorMessageHelper.wrongValueInLoanLevelDelinquencyData(actualValuesList, expectedValuesList))
.isEqualTo(expectedValuesList);
}
@Then("Loan has the following INSTALLMENT level delinquency data:")
public void loanDelinquencyInstallmentLevelDataCheck(DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetails);
List<GetLoansLoanIdLoanInstallmentLevelDelinquency> installmentLevelDelinquency = loanDetails.body().getDelinquent()
.getInstallmentLevelDelinquency();
List<List<String>> data = table.asLists();
assertThat(installmentLevelDelinquency.size())
.as(ErrorMessageHelper.nrOfLinesWrongInInstallmentLevelDelinquencyData(installmentLevelDelinquency.size(), data.size() - 1))
.isEqualTo(data.size() - 1);
for (int i = 1; i < data.size(); i++) {
DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(data.get(i).get(1));
String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue();
List<String> expectedValuesList = data.get(i);
expectedValuesList.set(1, expectedDelinquencyRangeValue);
List<String> actualValuesList = List.of(String.valueOf(installmentLevelDelinquency.get(i - 1).getRangeId()),
installmentLevelDelinquency.get(i - 1).getClassification(),
installmentLevelDelinquency.get(i - 1).getDelinquentAmount().setScale(2, RoundingMode.HALF_DOWN).toString());
assertThat(actualValuesList)
.as(ErrorMessageHelper.wrongValueInLineInInstallmentLevelDelinquencyData(i, actualValuesList, expectedValuesList))
.isEqualTo(expectedValuesList);
}
}
@Then("Loan Delinquency pause periods has the following data:")
public void loanDelinquencyPauseDataCheck(DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<List<String>> expectedData = table.asLists();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetails);
List<GetLoansLoanIdDelinquencyPausePeriod> delinquencyPausePeriods = loanDetails.body().getDelinquent()
.getDelinquencyPausePeriods();
assertThat(delinquencyPausePeriods.size())
.as(ErrorMessageHelper.nrOfLinesWrongInLoanDelinquencyPauseData(delinquencyPausePeriods.size(), expectedData.size() - 1))
.isEqualTo(expectedData.size() - 1);
for (int i = 1; i < expectedData.size(); i++) {
List<String> expectedValuesList = expectedData.get(i);
List<List<String>> actualValuesList = delinquencyPausePeriods.stream()
.map(t -> fetchValuesOfDelinquencyPausePeriods(table.row(0), t)).collect(Collectors.toList());
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValuesList));
assertThat(containsExpectedValues)
.as(ErrorMessageHelper.wrongValueInLineInDelinquencyPausePeriodData(i, actualValuesList, expectedValuesList)).isTrue();
}
}
@Then("Loan details delinquent.nextPaymentDueDate will be {string}")
public void nextPaymentDueDateCheck(String expectedDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetails);
String actualDate = FORMATTER.format(loanDetails.body().getDelinquent().getNextPaymentDueDate());
assertThat(actualDate).as(ErrorMessageHelper.wrongDataInNextPaymentDueDate(actualDate, expectedDate)).isEqualTo(expectedDate);
}
@Then("LoanAccountDelinquencyRangeDataV1 has delinquencyRange field with value {string}")
public void checkDelinquencyRangeInEvent(String expectedRange) {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(expectedRange);
String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue();
eventAssertion.assertEvent(LoanDelinquencyRangeChangeEvent.class, loanId)//
.extractingData(loanAccountDelinquencyRangeDataV1 -> { //
String actualDelinquencyRangeValue = loanAccountDelinquencyRangeDataV1.getDelinquencyRange().getClassification();//
assertThat(actualDelinquencyRangeValue)//
.as(ErrorMessageHelper.delinquencyRangeError(actualDelinquencyRangeValue, expectedDelinquencyRangeValue))//
.isEqualTo(expectedDelinquencyRangeValue);//
return null;
});
}
@Then("LoanDelinquencyRangeChangeBusinessEvent has the same Delinquency range, date and amount as in LoanDetails on both loan- and installment-level")
public void checkDelinquencyRangeInEvent() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
GetDelinquencyRangesResponse delinquencyRange = loanDetails.body().getDelinquencyRange();
GetLoansLoanIdDelinquencySummary delinquent = loanDetails.body().getDelinquent();
eventAssertion.assertEvent(LoanDelinquencyRangeChangeEvent.class, loanId)//
.extractingData(loanAccountDelinquencyRangeDataV1 -> { //
Long loanLevelDelinquencyRangeId = loanAccountDelinquencyRangeDataV1.getDelinquencyRange().getId();
String loanLevelDelinquencyRange = loanAccountDelinquencyRangeDataV1.getDelinquencyRange().getClassification();
String loanLevelDelinquentDate = loanAccountDelinquencyRangeDataV1.getDelinquentDate();
BigDecimal loanLevelTotalAmount = loanAccountDelinquencyRangeDataV1.getAmount().getTotalAmount();
Long loanLevelDelinquencyRangeIdExpected = delinquencyRange.getId();
String loanLevelDelinquencyRangeExpected = delinquencyRange.getClassification();
String loanLevelDelinquentDateExpected = FORMATTER.format(delinquent.getDelinquentDate());
BigDecimal loanLevelTotalAmountExpected = new BigDecimal(delinquent.getDelinquentAmount());
assertThat(loanLevelDelinquencyRangeId)//
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent4(loanLevelDelinquencyRangeId,
loanLevelDelinquencyRangeIdExpected))//
.isEqualTo(loanLevelDelinquencyRangeIdExpected);//
assertThat(loanLevelDelinquencyRange)//
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent5(loanLevelDelinquencyRange,
loanLevelDelinquencyRangeExpected))//
.isEqualTo(loanLevelDelinquencyRangeExpected);//
assertThat(loanLevelDelinquentDate)//
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent7(loanLevelDelinquentDate,
loanLevelDelinquentDateExpected))//
.isEqualTo(loanLevelDelinquentDateExpected);//
assertThat(loanLevelTotalAmount)//
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent6(loanLevelTotalAmount,
loanLevelTotalAmountExpected))//
.isEqualTo(loanLevelTotalAmountExpected);//
List<GetLoansLoanIdLoanInstallmentLevelDelinquency> installmentLevelDelinquencyBucketsExpected = delinquent
.getInstallmentLevelDelinquency();
List<LoanInstallmentDelinquencyBucketDataV1> installmentDelinquencyBuckets = loanAccountDelinquencyRangeDataV1
.getInstallmentDelinquencyBuckets();
for (int i = 0; i < installmentDelinquencyBuckets.size(); i++) {
Long installmentLevelDelinquencyRangeId = installmentDelinquencyBuckets.get(i).getDelinquencyRange().getId();
String installmentLevelDelinquencyRange = installmentDelinquencyBuckets.get(i).getDelinquencyRange()
.getClassification();
BigDecimal installmentLevelTotalAmount = installmentDelinquencyBuckets.get(i).getAmount().getTotalAmount();
Long installmentLevelDelinquencyRangeIdExpected = installmentLevelDelinquencyBucketsExpected.get(i).getRangeId();
String installmentLevelDelinquencyRangeExpected = installmentLevelDelinquencyBucketsExpected.get(i)
.getClassification();
BigDecimal installmentLevelTotalAmountExpected = installmentLevelDelinquencyBucketsExpected.get(i)
.getDelinquentAmount();
assertThat(installmentLevelDelinquencyRangeId)//
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent1(
installmentLevelDelinquencyRangeId, installmentLevelDelinquencyRangeIdExpected))//
.isEqualTo(installmentLevelDelinquencyRangeIdExpected);//
assertThat(installmentLevelDelinquencyRange)//
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent2(
installmentLevelDelinquencyRange, installmentLevelDelinquencyRangeExpected))//
.isEqualTo(installmentLevelDelinquencyRangeExpected);//
assertThat(installmentLevelTotalAmount)//
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent3(installmentLevelTotalAmount,
installmentLevelTotalAmountExpected))//
.isEqualTo(installmentLevelTotalAmountExpected);//
}
return null;
});
}
@Then("In Loan details delinquent.lastRepaymentAmount is {int} EUR with lastRepaymentDate {string}")
public void delinquentLastRepaymentAmountCheck(int expectedLastRepaymentAmount, String expectedLastRepaymentDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetails);
Double expectedLastRepaymentAmount1 = Double.valueOf(expectedLastRepaymentAmount);
Double actualLastRepaymentAmount = loanDetails.body().getDelinquent().getLastRepaymentAmount();
String actualLastRepaymentDate = FORMATTER.format(loanDetails.body().getDelinquent().getLastRepaymentDate());
assertThat(actualLastRepaymentAmount)//
.as(ErrorMessageHelper.wrongDataInDelinquentLastRepaymentAmount(actualLastRepaymentAmount, expectedLastRepaymentAmount1))//
.isEqualTo(expectedLastRepaymentAmount);//
assertThat(actualLastRepaymentDate)//
.as(ErrorMessageHelper.wrongDataInDelinquentLastRepaymentDate(actualLastRepaymentDate, expectedLastRepaymentDate))//
.isEqualTo(expectedLastRepaymentDate);//
log.info("loanDetails.delinquent.lastRepaymentAmount: {}", actualLastRepaymentAmount);
log.info("loanDetails.delinquent.lastRepaymentDate: {}", actualLastRepaymentDate);
}
@SuppressFBWarnings("SF_SWITCH_NO_DEFAULT")
private List<String> fetchValuesOfDelinquencyPausePeriods(List<String> header, GetLoansLoanIdDelinquencyPausePeriod t) {
List<String> actualValues = new ArrayList<>();
for (String headerName : header) {
switch (headerName) {
case "active" -> actualValues.add(t.getActive() == null ? null : t.getActive().toString());
case "pausePeriodStart" ->
actualValues.add(t.getPausePeriodStart() == null ? null : FORMATTER.format(t.getPausePeriodStart()));
case "pausePeriodEnd" -> actualValues.add(t.getPausePeriodEnd() == null ? null : FORMATTER.format(t.getPausePeriodEnd()));
}
}
return actualValues;
}
private void errorMessageAssertation(Response<PostLoansDelinquencyActionResponse> response, int errorCodeExpected,
String errorMessageExpected) throws IOException {
String errorToString = response.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(errorToString, ErrorResponse.class);
String errorMessageActual = errorResponse.getErrors().get(0).getDeveloperMessage();
int errorCodeActual = response.code();
assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected);
assertThat(errorMessageActual).as(ErrorMessageHelper.wrongErrorMessage(errorMessageActual, errorMessageExpected))
.isEqualTo(errorMessageExpected);
log.info("ERROR CODE: {}", errorCodeActual);
log.info("ERROR MESSAGE: {}", errorMessageActual);
}
@Then("LoanDelinquencyRangeChangeBusinessEvent is created")
public void checkLoanDelinquencyRangeChangeBusinessEventCreated() {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
eventAssertion.assertEventRaised(LoanDelinquencyRangeChangeEvent.class, loanId);
}
}
@@ -0,0 +1,107 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.LoanTransactionsApi;
import org.apache.fineract.test.factory.LoanRequestFactory;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.loan.LoanReAgeEvent;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@Slf4j
public class LoanReAgingStepDef extends AbstractStepDef {
@Autowired
private LoanTransactionsApi loanTransactionsApi;
@Autowired
private EventAssertion eventAssertion;
@When("Admin creates a Loan re-aging transaction with the following data:")
public void createReAgingTransaction(DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<String> data = table.asLists().get(1);
int frequencyNumber = Integer.parseInt(data.get(0));
String frequencyType = data.get(1);
String startDate = data.get(2);
int numberOfInstallments = Integer.parseInt(data.get(3));
PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory.defaultReAgingRequest().frequencyNumber(frequencyNumber)
.frequencyType(frequencyType).startDate(startDate).numberOfInstallments(numberOfInstallments);
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi.executeLoanTransaction(loanId, reAgingRequest, "reAge")
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.LOAN_REAGING_RESPONSE, response);
}
@When("Admin creates a Loan re-aging transaction by Loan external ID with the following data:")
public void createReAgingTransactionByLoanExternalId(DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
String loanExternalId = loanResponse.body().getResourceExternalId();
List<String> data = table.asLists().get(1);
int frequencyNumber = Integer.parseInt(data.get(0));
String frequencyType = data.get(1);
String startDate = data.get(2);
int numberOfInstallments = Integer.parseInt(data.get(3));
PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory.defaultReAgingRequest().frequencyNumber(frequencyNumber)
.frequencyType(frequencyType).startDate(startDate).numberOfInstallments(numberOfInstallments);
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi
.executeLoanTransaction1(loanExternalId, reAgingRequest, "reAge").execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.LOAN_REAGING_RESPONSE, response);
}
@When("Admin successfully undo Loan re-aging transaction")
public void undoReAgingTransaction() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi
.executeLoanTransaction(loanId, new PostLoansLoanIdTransactionsRequest(), "undoReAge").execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.LOAN_REAGING_UNDO_RESPONSE, response);
}
@Then("LoanReAgeBusinessEvent is created")
public void checkLoanReAmortizeBusinessEventCreated() {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
eventAssertion.assertEventRaised(LoanReAgeEvent.class, loanId);
}
}
@@ -0,0 +1,89 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.LoanTransactionsApi;
import org.apache.fineract.test.factory.LoanRequestFactory;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.loan.LoanReAmortizeEvent;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class LoanReAmortizationStepDef extends AbstractStepDef {
@Autowired
private LoanTransactionsApi loanTransactionsApi;
@Autowired
private EventAssertion eventAssertion;
@When("When Admin creates a Loan re-amortization transaction on current business date")
public void createLoanReAmortization() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdTransactionsRequest reAmortizationRequest = LoanRequestFactory.defaultLoanReAmortizationRequest();
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi
.executeLoanTransaction(loanId, reAmortizationRequest, "reAmortize").execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.LOAN_REAMORTIZATION_RESPONSE, response);
}
@When("When Admin creates a Loan re-amortization transaction on current business date by loan external ID")
public void createLoanReAmortizationByLoanExternalId() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
String loanExternalId = loanResponse.body().getResourceExternalId();
PostLoansLoanIdTransactionsRequest reAmortizationRequest = LoanRequestFactory.defaultLoanReAmortizationRequest();
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi
.executeLoanTransaction1(loanExternalId, reAmortizationRequest, "reAmortize").execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.LOAN_REAMORTIZATION_RESPONSE, response);
}
@When("When Admin undo Loan re-amortization transaction on current business date")
public void undoLoanReAmortization() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi
.executeLoanTransaction(loanId, new PostLoansLoanIdTransactionsRequest(), "undoReAmortize").execute();
ErrorHelper.checkSuccessfulApiCall(response);
testContext().set(TestContextKey.LOAN_REAMORTIZATION_UNDO_RESPONSE, response);
}
@Then("LoanReAmortizeBusinessEvent is created")
public void checkLoanReAmortizeBusinessEventCreated() {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
eventAssertion.assertEventRaised(LoanReAmortizeEvent.class, loanId);
}
}
@@ -0,0 +1,564 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import static org.apache.fineract.test.data.paymenttype.DefaultPaymentType.AUTOPAY;
import static org.assertj.core.api.Assertions.assertThat;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.codec.binary.Base64;
import org.apache.fineract.avro.loan.v1.LoanTransactionAdjustmentDataV1;
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.GetUsersUserIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PostUsersResponse;
import org.apache.fineract.client.services.LoanTransactionsApi;
import org.apache.fineract.client.services.LoansApi;
import org.apache.fineract.client.services.UsersApi;
import org.apache.fineract.test.api.ApiProperties;
import org.apache.fineract.test.data.TransactionType;
import org.apache.fineract.test.data.paymenttype.DefaultPaymentType;
import org.apache.fineract.test.data.paymenttype.PaymentTypeResolver;
import org.apache.fineract.test.factory.LoanRequestFactory;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.ErrorResponse;
import org.apache.fineract.test.messaging.EventAssertion;
import org.apache.fineract.test.messaging.event.EventCheckHelper;
import org.apache.fineract.test.messaging.event.loan.LoanBalanceChangedEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanAdjustTransactionBusinessEvent;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
public class LoanRepaymentStepDef extends AbstractStepDef {
public static final String DATE_FORMAT = "dd MMMM yyyy";
public static final String DEFAULT_LOCALE = "en";
public static final String DEFAULT_ACCOUNT_NB = "1234567890";
public static final String DEFAULT_CHECK_NB = "1234567890";
public static final String DEFAULT_RECEIPT_NB = "1234567890";
public static final String DEFAULT_BANK_NB = "1234567890";
@Autowired
private LoanTransactionsApi loanTransactionsApi;
@Autowired
private LoansApi loansApi;
@Autowired
private EventAssertion eventAssertion;
@Autowired
private ApiProperties apiProperties;
@Autowired
private UsersApi usersApi;
@Autowired
private PaymentTypeResolver paymentTypeResolver;
@Autowired
private EventCheckHelper eventCheckHelper;
@And("Customer makes {string} repayment on {string} with {double} EUR transaction amount")
public void makeLoanRepayment(String repaymentType, String transactionDate, double transactionAmount) throws IOException {
makeRepayment(repaymentType, transactionDate, transactionAmount, null);
}
@And("Customer makes {string} repayment on {string} with {double} EUR transaction amount and check external owner")
public void makeLoanRepaymentAndCheckOwner(String repaymentType, String transactionDate, double transactionAmount) throws IOException {
String transferExternalOwnerId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
makeRepayment(repaymentType, transactionDate, transactionAmount, transferExternalOwnerId);
}
private void makeRepayment(String repaymentType, String transactionDate, double transactionAmount, String transferExternalOwnerId)
throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType);
long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
PostLoansLoanIdTransactionsRequest repaymentRequest = LoanRequestFactory.defaultRepaymentRequest().transactionDate(transactionDate)
.transactionAmount(transactionAmount).paymentTypeId(paymentTypeValue).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Map<String, String> headerMap = new HashMap<>();
String idempotencyKey = UUID.randomUUID().toString();
testContext().set(TestContextKey.TRANSACTION_IDEMPOTENCY_KEY, idempotencyKey);
headerMap.put("Idempotency-Key", idempotencyKey);
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = loanTransactionsApi
.executeLoanTransaction(loanId, repaymentRequest, "repayment", headerMap).execute();
testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse);
ErrorHelper.checkSuccessfulApiCall(repaymentResponse);
EventAssertion.EventAssertionBuilder<LoanTransactionDataV1> transactionEvent = eventCheckHelper
.transactionEventCheck(repaymentResponse, TransactionType.REPAYMENT, transferExternalOwnerId);
testContext().set(TestContextKey.TRANSACTION_EVENT, transactionEvent);
eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId);
}
@And("Created user makes {string} repayment on {string} with {double} EUR transaction amount")
public void makeRepaymentWithGivenUser(String repaymentType, String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType);
Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
PostLoansLoanIdTransactionsRequest repaymentRequest = LoanRequestFactory.defaultRepaymentRequest().transactionDate(transactionDate)
.transactionAmount(transactionAmount).paymentTypeId(paymentTypeValue).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Map<String, String> headerMap = new HashMap<>();
String idempotencyKey = UUID.randomUUID().toString();
testContext().set(TestContextKey.TRANSACTION_IDEMPOTENCY_KEY, idempotencyKey);
headerMap.put("Idempotency-Key", idempotencyKey);
Response<PostUsersResponse> createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE);
Long createdUserId = createUserResponse.body().getResourceId();
Response<GetUsersUserIdResponse> user = usersApi.retrieveOne31(createdUserId).execute();
ErrorHelper.checkSuccessfulApiCall(user);
String authorizationString = user.body().getUsername() + ":" + apiProperties.getPassword();
Base64 base64 = new Base64();
headerMap.put("Authorization",
"Basic " + new String(base64.encode(authorizationString.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = loanTransactionsApi
.executeLoanTransaction(loanId, repaymentRequest, "repayment", headerMap).execute();
testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse);
ErrorHelper.checkSuccessfulApiCall(repaymentResponse);
eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId);
}
@And("Customer makes externalID controlled {string} repayment on {string} with {double} EUR transaction amount")
public void makeRepaymentByExternalId(String repaymentType, String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
String resourceExternalId = loanResponse.body().getResourceExternalId();
DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType);
Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
PostLoansLoanIdTransactionsRequest repaymentRequest = LoanRequestFactory.defaultRepaymentRequest().transactionDate(transactionDate)
.transactionAmount(transactionAmount).paymentTypeId(paymentTypeValue).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Map<String, String> headerMap = new HashMap<>();
String idempotencyKey = UUID.randomUUID().toString();
testContext().set(TestContextKey.TRANSACTION_IDEMPOTENCY_KEY, idempotencyKey);
headerMap.put("Idempotency-Key", idempotencyKey);
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = loanTransactionsApi
.executeLoanTransaction1(resourceExternalId, repaymentRequest, "repayment", headerMap).execute();
testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse);
ErrorHelper.checkSuccessfulApiCall(repaymentResponse);
eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId);
}
@And("Created user makes externalID controlled {string} repayment on {string} with {double} EUR transaction amount")
public void makeRepaymentWithGivenUserByExternalId(String repaymentType, String transactionDate, double transactionAmount)
throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
String resourceExternalId = loanResponse.body().getResourceExternalId();
DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType);
Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
PostLoansLoanIdTransactionsRequest repaymentRequest = LoanRequestFactory.defaultRepaymentRequest().transactionDate(transactionDate)
.transactionAmount(transactionAmount).paymentTypeId(paymentTypeValue).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Map<String, String> headerMap = new HashMap<>();
String idempotencyKey = UUID.randomUUID().toString();
testContext().set(TestContextKey.TRANSACTION_IDEMPOTENCY_KEY, idempotencyKey);
headerMap.put("Idempotency-Key", idempotencyKey);
Response<PostUsersResponse> createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE);
Long createdUserId = createUserResponse.body().getResourceId();
Response<GetUsersUserIdResponse> user = usersApi.retrieveOne31(createdUserId).execute();
ErrorHelper.checkSuccessfulApiCall(user);
String authorizationString = user.body().getUsername() + ":" + apiProperties.getPassword();
Base64 base64 = new Base64();
headerMap.put("Authorization",
"Basic " + new String(base64.encode(authorizationString.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = loanTransactionsApi
.executeLoanTransaction1(resourceExternalId, repaymentRequest, "repayment", headerMap).execute();
testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse);
ErrorHelper.checkSuccessfulApiCall(repaymentResponse);
eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId);
}
@And("Customer not able to make {string} repayment on {string} with {double} EUR transaction amount")
public void makeLoanRepaymentFails(String repaymentType, String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType);
Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
PostLoansLoanIdTransactionsRequest repaymentRequest = LoanRequestFactory.defaultRepaymentRequest().transactionDate(transactionDate)
.transactionAmount(transactionAmount).paymentTypeId(paymentTypeValue).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = loanTransactionsApi
.executeLoanTransaction(loanId, repaymentRequest, "repayment").execute();
ErrorResponse errorDetails = ErrorResponse.from(repaymentResponse);
assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(400);
assertThat(errorDetails.getSingleError().getDeveloperMessage()).isEqualTo(ErrorMessageHelper.loanRepaymentOnClosedLoanFailureMsg());
}
@Then("Customer not able to make a repayment undo on {string} due to charge off")
public void makeLoanRepaymentUndoAfterChargeOff(String transactionDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
Response<PostLoansLoanIdTransactionsResponse> transactionResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
Long loanId = loanResponse.body().getLoanId();
Long transactionId = transactionResponse.body().getResourceId();
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
PostLoansLoanIdTransactionsTransactionIdRequest repaymentUndoRequest = LoanRequestFactory.defaultRepaymentUndoRequest()
.transactionDate(transactionDate).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Response<PostLoansLoanIdTransactionsResponse> repaymentUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, repaymentResponse.body().getResourceId(), repaymentUndoRequest, "").execute();
ErrorResponse errorDetails = ErrorResponse.from(repaymentUndoResponse);
assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.repaymentUndoFailureDueToChargeOffCodeMsg()).isEqualTo(403);
assertThat(errorDetails.getSingleError().getDeveloperMessage())
.isEqualTo(ErrorMessageHelper.repaymentUndoFailureDueToChargeOff(transactionId));
}
@And("Customer makes {string} repayment on {string} with {double} EUR transaction amount \\(and transaction fails because of wrong date)")
public void makeLoanRepaymentWithWrongDate(String repaymentType, String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType);
Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
PostLoansLoanIdTransactionsRequest repaymentRequest = LoanRequestFactory.defaultRepaymentRequest().transactionDate(transactionDate)
.transactionAmount(transactionAmount).paymentTypeId(paymentTypeValue).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = loanTransactionsApi
.executeLoanTransaction(loanId, repaymentRequest, "repayment").execute();
testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse);
}
@When("Refund happens on {string} with {double} EUR transaction amount")
public void makeRefund(String transactionDate, double transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdTransactionsRequest refundRequest = LoanRequestFactory.defaultRefundRequest().transactionDate(transactionDate)
.transactionAmount(transactionAmount).paymentTypeId(paymentTypeResolver.resolve(AUTOPAY)).dateFormat(DATE_FORMAT)
.locale(DEFAULT_LOCALE).accountNumber(DEFAULT_ACCOUNT_NB).checkNumber(DEFAULT_CHECK_NB).receiptNumber(DEFAULT_RECEIPT_NB)
.bankNumber(DEFAULT_BANK_NB);
Response<PostLoansLoanIdTransactionsResponse> refundResponse = loanTransactionsApi
.executeLoanTransaction(loanId, refundRequest, "payoutRefund").execute();
ErrorHelper.checkSuccessfulApiCall(refundResponse);
testContext().set(TestContextKey.LOAN_REFUND_RESPONSE, refundResponse);
}
@When("Refund undo happens on {string}")
public void makeRefundUndo(String transactionDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> refundResponse = testContext().get(TestContextKey.LOAN_REFUND_RESPONSE);
PostLoansLoanIdTransactionsTransactionIdRequest refundUndoRequest = LoanRequestFactory.defaultRefundUndoRequest()
.transactionDate(transactionDate).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Response<PostLoansLoanIdTransactionsResponse> refundUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, refundResponse.body().getResourceId(), refundUndoRequest, "").execute();
ErrorHelper.checkSuccessfulApiCall(refundUndoResponse);
testContext().set(TestContextKey.LOAN_REPAYMENT_UNDO_RESPONSE, refundUndoResponse);
EventAssertion.EventAssertionBuilder<LoanTransactionAdjustmentDataV1> eventAssertionBuilder = eventAssertion
.assertEvent(LoanAdjustTransactionBusinessEvent.class, refundResponse.body().getResourceId());
eventAssertionBuilder
.extractingData(loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getId())
.isEqualTo(refundResponse.body().getResourceId());
eventAssertionBuilder
.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed())
.isEqualTo(Boolean.TRUE);
eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null);
}
@When("Customer makes a repayment undo on {string}")
public void makeLoanRepaymentUndo(String transactionDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
PostLoansLoanIdTransactionsTransactionIdRequest repaymentUndoRequest = LoanRequestFactory.defaultRepaymentUndoRequest()
.transactionDate(transactionDate).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Response<PostLoansLoanIdTransactionsResponse> repaymentUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, repaymentResponse.body().getResourceId(), repaymentUndoRequest, "").execute();
ErrorHelper.checkSuccessfulApiCall(repaymentUndoResponse);
testContext().set(TestContextKey.LOAN_REPAYMENT_UNDO_RESPONSE, repaymentUndoResponse);
EventAssertion.EventAssertionBuilder<LoanTransactionAdjustmentDataV1> eventAssertionBuilder = eventAssertion
.assertEvent(LoanAdjustTransactionBusinessEvent.class, repaymentResponse.body().getResourceId());
eventAssertionBuilder
.extractingData(loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getId())
.isEqualTo(repaymentResponse.body().getResourceId());
eventAssertionBuilder
.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed())
.isEqualTo(Boolean.TRUE);
eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null);
}
@Then("Loan {string} transaction adjust amount {double} must return {int} code")
public void makeLoanRepaymentAdjustFail(String transactionType, double transactionAmount, int codeExpected) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = testContext().get(transactionType);
PostLoansLoanIdTransactionsTransactionIdRequest repaymentUndoRequest = LoanRequestFactory.defaultRepaymentUndoRequest()
.transactionAmount(transactionAmount);
Response<PostLoansLoanIdTransactionsResponse> repaymentUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, repaymentResponse.body().getResourceId(), repaymentUndoRequest, "").execute();
assertThat(repaymentUndoResponse.code()).isEqualTo(codeExpected);
}
@When("Customer undo {string}th repayment on {string}")
public void undoNthRepayment(String nthItemStr, String transactionDate) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<GetLoansLoanIdTransactions> transactions = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute().body()
.getTransactions();
int nthItem = Integer.parseInt(nthItemStr) - 1;
GetLoansLoanIdTransactions targetTransaction = transactions.stream().filter(t -> Boolean.TRUE.equals(t.getType().getRepayment()))
.toList().get(nthItem);
PostLoansLoanIdTransactionsTransactionIdRequest repaymentUndoRequest = LoanRequestFactory.defaultRepaymentUndoRequest()
.transactionDate(transactionDate).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Response<PostLoansLoanIdTransactionsResponse> repaymentUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, targetTransaction.getId(), repaymentUndoRequest, "").execute();
ErrorHelper.checkSuccessfulApiCall(repaymentUndoResponse);
testContext().set(TestContextKey.LOAN_REPAYMENT_UNDO_RESPONSE, repaymentUndoResponse);
EventAssertion.EventAssertionBuilder<LoanTransactionAdjustmentDataV1> eventAssertionBuilder = eventAssertion
.assertEvent(LoanAdjustTransactionBusinessEvent.class, targetTransaction.getId());
eventAssertionBuilder
.extractingData(loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getId())
.isEqualTo(targetTransaction.getId());
eventAssertionBuilder
.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed())
.isEqualTo(Boolean.TRUE);
eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null);
}
@When("Customer undo {string}th transaction made on {string}")
public void undoNthTransaction(String nthItemStr, String transactionDate) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<GetLoansLoanIdTransactions> transactions = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute().body()
.getTransactions();
int nthItem = Integer.parseInt(nthItemStr) - 1;
GetLoansLoanIdTransactions targetTransaction = transactions.stream()
.filter(t -> transactionDate.equals(formatter.format(t.getDate()))).toList().get(nthItem);
PostLoansLoanIdTransactionsTransactionIdRequest transactionUndoRequest = LoanRequestFactory.defaultTransactionUndoRequest()
.transactionDate(transactionDate);
Response<PostLoansLoanIdTransactionsResponse> transactionUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, targetTransaction.getId(), transactionUndoRequest, "").execute();
ErrorHelper.checkSuccessfulApiCall(transactionUndoResponse);
testContext().set(TestContextKey.LOAN_TRANSACTION_UNDO_RESPONSE, transactionUndoResponse);
EventAssertion.EventAssertionBuilder<LoanTransactionAdjustmentDataV1> eventAssertionBuilder = eventAssertion
.assertEvent(LoanAdjustTransactionBusinessEvent.class, targetTransaction.getId());
eventAssertionBuilder
.extractingData(loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getId())
.isEqualTo(targetTransaction.getId());
eventAssertionBuilder
.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed())
.isEqualTo(Boolean.TRUE);
eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null);
}
@When("Customer undo {string}th {string} transaction made on {string}")
public void undoNthTransactionType(String nthItemStr, String transactionType, String transactionDate) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<GetLoansLoanIdTransactions> transactions = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute().body()
.getTransactions();
int nthItem = Integer.parseInt(nthItemStr) - 1;
GetLoansLoanIdTransactions targetTransaction = transactions//
.stream()//
.filter(t -> transactionDate.equals(formatter.format(t.getDate())) && transactionType.equals(t.getType().getValue()))//
.toList()//
.get(nthItem);//
PostLoansLoanIdTransactionsTransactionIdRequest transactionUndoRequest = LoanRequestFactory.defaultTransactionUndoRequest()
.transactionDate(transactionDate);
Response<PostLoansLoanIdTransactionsResponse> transactionUndoResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, targetTransaction.getId(), transactionUndoRequest, "").execute();
ErrorHelper.checkSuccessfulApiCall(transactionUndoResponse);
testContext().set(TestContextKey.LOAN_TRANSACTION_UNDO_RESPONSE, transactionUndoResponse);
EventAssertion.EventAssertionBuilder<LoanTransactionAdjustmentDataV1> eventAssertionBuilder = eventAssertion
.assertEvent(LoanAdjustTransactionBusinessEvent.class, targetTransaction.getId());
eventAssertionBuilder
.extractingData(loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getId())
.isEqualTo(targetTransaction.getId());
eventAssertionBuilder
.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed())
.isEqualTo(Boolean.TRUE);
eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null);
}
@Then("Repayment transaction is created with {double} amount and {string} type")
public void loanRepaymentStatus(double repaymentAmount, String paymentType) throws IOException {
Response<PostLoansLoanIdTransactionsResponse> repaymentResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<GetLoansLoanIdTransactionsTransactionIdResponse> transactionResponse = loanTransactionsApi
.retrieveTransaction(loanId, repaymentResponse.body().getResourceId(), "").execute();
ErrorHelper.checkSuccessfulApiCall(transactionResponse);
assertThat(transactionResponse.body().getAmount()).isEqualTo(repaymentAmount);
assertThat(transactionResponse.body().getPaymentDetailData().getPaymentType().getName()).isEqualTo(paymentType);
}
@Then("Repayment failed because the repayment date is after the business date")
public void repaymentDateFailure() {
Response<PostLoansLoanIdTransactionsResponse> response = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE);
ErrorResponse errorDetails = ErrorResponse.from(response);
assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(403);
assertThat(errorDetails.getSingleError().getDeveloperMessage()).isEqualTo(ErrorMessageHelper.transactionDateInFutureFailureMsg());
}
@Then("Amounts are distributed equally in loan repayment schedule in case of total amount {double}")
public void amountsEquallyDistributedInSchedule(double totalAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId1 = loanResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> getLoansLoanIdResponseCall = loansApi
.retrieveLoan(loanId1, false, "all", "guarantors,futureSchedule", "").execute();
ErrorHelper.checkSuccessfulApiCall(getLoansLoanIdResponseCall);
List<GetLoansLoanIdRepaymentPeriod> periods = getLoansLoanIdResponseCall.body().getRepaymentSchedule().getPeriods();
BigDecimal expectedAmount = new BigDecimal(totalAmount / (periods.size() - 1)).setScale(0, RoundingMode.HALF_DOWN);
BigDecimal lastExpectedAmount = new BigDecimal(totalAmount).setScale(0, RoundingMode.HALF_DOWN);
for (int i = 1; i < periods.size(); i++) {
BigDecimal actualAmount = new BigDecimal(periods.get(i).getPrincipalOriginalDue()).setScale(0, RoundingMode.HALF_DOWN);
if (i == periods.size() - 1) {
assertThat(actualAmount.compareTo(lastExpectedAmount))
.as(ErrorMessageHelper.wrongAmountInRepaymentSchedule(i, actualAmount, lastExpectedAmount)).isEqualTo(0);
} else {
assertThat(actualAmount.compareTo(expectedAmount))
.as(ErrorMessageHelper.wrongAmountInRepaymentSchedule(i, actualAmount, expectedAmount)).isEqualTo(0);
lastExpectedAmount = lastExpectedAmount.subtract(actualAmount);
}
}
}
@When("Customer adjust {string}th repayment on {string} with amount {string} and check external owner")
public void adjustNthRepaymentWithExternalOwner(String nthItemStr, String transactionDate, String amount) throws IOException {
String transferExternalOwnerId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
adjustNthRepaymentWithExternalOwnerCheck(nthItemStr, transactionDate, amount, transferExternalOwnerId);
}
@When("Customer adjust {string}th repayment on {string} with amount {string}")
public void adjustNthRepayment(String nthItemStr, String transactionDate, String amount) throws IOException {
adjustNthRepaymentWithExternalOwnerCheck(nthItemStr, transactionDate, amount, null);
}
private void adjustNthRepaymentWithExternalOwnerCheck(String nthItemStr, String transactionDate, String amount, String externalOwnerId)
throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<GetLoansLoanIdTransactions> transactions = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute().body()
.getTransactions();
int nthItem = Integer.parseInt(nthItemStr) - 1;
GetLoansLoanIdTransactions targetTransaction = transactions.stream().filter(t -> Boolean.TRUE.equals(t.getType().getRepayment()))
.toList().get(nthItem);
double amountValue = Double.parseDouble(amount);
PostLoansLoanIdTransactionsTransactionIdRequest repaymentUndoRequest = LoanRequestFactory.defaultRepaymentAdjustRequest(amountValue)
.transactionDate(transactionDate).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE);
Response<PostLoansLoanIdTransactionsResponse> repaymentAdjustmentResponse = loanTransactionsApi
.adjustLoanTransaction(loanId, targetTransaction.getId(), repaymentUndoRequest, "").execute();
ErrorHelper.checkSuccessfulApiCall(repaymentAdjustmentResponse);
testContext().set(TestContextKey.LOAN_REPAYMENT_UNDO_RESPONSE, repaymentAdjustmentResponse);
EventAssertion.EventAssertionBuilder<LoanTransactionAdjustmentDataV1> eventAssertionBuilder = eventAssertion
.assertEvent(LoanAdjustTransactionBusinessEvent.class, targetTransaction.getId());
eventAssertionBuilder
.extractingData(loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getId())
.isEqualTo(targetTransaction.getId());
eventAssertionBuilder.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getAmount().doubleValue())
.isEqualTo(targetTransaction.getAmount());
eventAssertionBuilder
.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed())
.isEqualTo(Boolean.TRUE);
eventAssertionBuilder
.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getExternalOwnerId())
.isEqualTo(externalOwnerId);
if (amountValue > 0) {
eventAssertionBuilder
.extractingData(loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getNewTransactionDetail().getId())
.isEqualTo(repaymentAdjustmentResponse.body().getResourceId());
eventAssertionBuilder.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getNewTransactionDetail().getAmount().doubleValue())
.isEqualTo(amountValue);
eventAssertionBuilder.extractingData(
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getNewTransactionDetail().getExternalOwnerId())
.isEqualTo(externalOwnerId);
}
}
}
@@ -0,0 +1,144 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.test.stepdef.loan;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.gson.Gson;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
import org.apache.fineract.client.models.PostUpdateRescheduleLoansResponse;
import org.apache.fineract.client.services.RescheduleLoansApi;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.test.data.LoanRescheduleErrorMessage;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.ErrorResponse;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.springframework.beans.factory.annotation.Autowired;
import retrofit2.Response;
@Slf4j
public class LoanRescheduleStepDef extends AbstractStepDef {
private static final Gson GSON = new JSON().getGson();
@Autowired
private RescheduleLoansApi rescheduleLoansApi;
@When("Admin creates and approves Loan reschedule with the following data:")
public void createAndApproveLoanReschedule(DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<List<String>> data = table.asLists();
List<String> rescheduleData = data.get(1);
String rescheduleFromDate = rescheduleData.get(0);
String submittedOnDate = rescheduleData.get(1);
String adjustedDueDate = rescheduleData.get(2);
Integer graceOfPrincipal = Integer.parseInt(rescheduleData.get(3)) == 0 ? null : Integer.valueOf(rescheduleData.get(3));
Integer graceOnInterest = Integer.parseInt(rescheduleData.get(4)) == 0 ? null : Integer.valueOf(rescheduleData.get(4));
Integer extraTerms = Integer.parseInt(rescheduleData.get(5)) == 0 ? null : Integer.valueOf(rescheduleData.get(5));
BigDecimal newInterestRate = new BigDecimal(rescheduleData.get(6));
PostCreateRescheduleLoansRequest request = new PostCreateRescheduleLoansRequest()//
.loanId(loanId)//
.rescheduleFromDate(rescheduleFromDate)//
.submittedOnDate(submittedOnDate)//
.adjustedDueDate(adjustedDueDate)//
.graceOnPrincipal(graceOfPrincipal)//
.graceOnInterest(graceOnInterest)//
.extraTerms(extraTerms)//
.newInterestRate(newInterestRate)//
.rescheduleReasonId(54L)//
.rescheduleReasonComment("")//
.dateFormat("dd MMMM yyyy")//
.locale("en");//
Response<PostCreateRescheduleLoansResponse> createResponse = rescheduleLoansApi.createLoanRescheduleRequest(request).execute();
ErrorHelper.checkSuccessfulApiCall(createResponse);
Long scheduleId = createResponse.body().getResourceId();
PostUpdateRescheduleLoansRequest approveRequest = new PostUpdateRescheduleLoansRequest()//
.approvedOnDate(submittedOnDate)//
.dateFormat("dd MMMM yyyy")//
.locale("en");//
Response<PostUpdateRescheduleLoansResponse> approveResponse = rescheduleLoansApi
.updateLoanRescheduleRequest(scheduleId, approveRequest, "approve").execute();
ErrorHelper.checkSuccessfulApiCall(approveResponse);
}
@Then("Loan reschedule with the following data results a {int} error and {string} error message")
public void createLoanRescheduleError(int errorCodeExpected, String errorMessageType, DataTable table) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<List<String>> data = table.asLists();
List<String> rescheduleData = data.get(1);
String rescheduleFromDate = rescheduleData.get(0);
String submittedOnDate = rescheduleData.get(1);
String adjustedDueDate = rescheduleData.get(2);
Integer graceOfPrincipal = Integer.parseInt(rescheduleData.get(3)) == 0 ? null : Integer.valueOf(rescheduleData.get(3));
Integer graceOnInterest = Integer.parseInt(rescheduleData.get(4)) == 0 ? null : Integer.valueOf(rescheduleData.get(4));
Integer extraTerms = Integer.parseInt(rescheduleData.get(5)) == 0 ? null : Integer.valueOf(rescheduleData.get(5));
BigDecimal newInterestRate = new BigDecimal(rescheduleData.get(6));
PostCreateRescheduleLoansRequest request = new PostCreateRescheduleLoansRequest()//
.loanId(loanId)//
.rescheduleFromDate(rescheduleFromDate)//
.submittedOnDate(submittedOnDate)//
.adjustedDueDate(adjustedDueDate)//
.graceOnPrincipal(graceOfPrincipal)//
.graceOnInterest(graceOnInterest)//
.extraTerms(extraTerms)//
.newInterestRate(newInterestRate)//
.rescheduleReasonId(54L)//
.rescheduleReasonComment("")//
.dateFormat("dd MMMM yyyy")//
.locale("en");//
Response<PostCreateRescheduleLoansResponse> createResponse = rescheduleLoansApi.createLoanRescheduleRequest(request).execute();
LoanRescheduleErrorMessage loanRescheduleErrorMessage = LoanRescheduleErrorMessage.valueOf(errorMessageType);
String errorMessageExpected = loanRescheduleErrorMessage.getValue(loanId);
String errorToString = createResponse.errorBody().string();
ErrorResponse errorResponse = GSON.fromJson(errorToString, ErrorResponse.class);
String errorMessageActual = errorResponse.getErrors().get(0).getDeveloperMessage();
int errorCodeActual = createResponse.code();
assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected);
assertThat(errorMessageActual).as(ErrorMessageHelper.wrongErrorMessage(errorMessageActual, errorMessageExpected))
.isEqualTo(errorMessageExpected);
log.info("ERROR CODE: {}", errorCodeActual);
log.info("ERROR MESSAGE: {}", errorMessageActual);
}
}