FINERACT-2380: create feign client

This commit is contained in:
Attila Budai
2025-10-17 14:25:39 +02:00
committed by Adam Saghy
parent f193bbbdf1
commit 553347b320
56 changed files with 7906 additions and 21 deletions
@@ -0,0 +1,65 @@
/**
* 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.integrationtests.client;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.feign.FineractFeignClientConfig;
import org.apache.fineract.integrationtests.ConfigProperties;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
/**
* Simple smoke tests for Feign-based Fineract client
*
*/
public class FeignClientSmokeTest extends FeignIntegrationTest {
private String getTestUrl() {
return System.getProperty("fineract.it.url", ConfigProperties.Backend.PROTOCOL + "://" + ConfigProperties.Backend.HOST + ":"
+ ConfigProperties.Backend.PORT + "/fineract-provider/api");
}
@Test
@Order(1)
public void testFeignClientBuilder() {
FineractFeignClient.Builder builder = FineractFeignClient.builder().baseUrl(getTestUrl())
.credentials(ConfigProperties.Backend.USERNAME, ConfigProperties.Backend.PASSWORD);
assertThat(builder).isNotNull();
}
@Test
@Order(2)
public void testFeignClientConfig() {
FineractFeignClientConfig.Builder configBuilder = FineractFeignClientConfig.builder().baseUrl(getTestUrl())
.credentials(ConfigProperties.Backend.USERNAME, ConfigProperties.Backend.PASSWORD).debugEnabled(true);
FineractFeignClientConfig config = configBuilder.build();
assertThat(config).isNotNull();
}
@Test
@Order(3)
public void testFeignClientCanInstantiateApis() {
assertThat(fineractClient()).isNotNull();
assertThat(fineractClient().offices()).isNotNull();
assertThat(fineractClient().clients()).isNotNull();
}
}
@@ -0,0 +1,150 @@
/**
* 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.integrationtests.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import feign.Response;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import org.apache.fineract.client.feign.FeignException;
import org.apache.fineract.client.feign.FineractMultipartEncoder;
import org.apache.fineract.client.models.DocumentData;
import org.apache.fineract.client.models.PostEntityTypeEntityIdDocumentsResponse;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignClientHelper;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class FeignDocumentTest extends FeignIntegrationTest {
final File testFile = new File(getClass().getResource("/michael.vorburger-crepes.jpg").getFile());
Long clientId;
Long documentId;
@Test
@Order(1)
void setupClient() {
FeignClientHelper clientHelper = new FeignClientHelper(fineractClient());
clientId = clientHelper.createClient("Feign", "Test");
assertThat(clientId).isNotNull();
}
@Test
@Order(2)
void testCreateDocument() throws IOException {
String name = "Feign Test Document";
String description = "Testing DocumentsApiFixed with Feign client";
byte[] fileData = Files.readAllBytes(testFile.toPath());
FineractMultipartEncoder.MultipartData multipartData = new FineractMultipartEncoder.MultipartData()
.addFile("file", testFile.getName(), fileData, "image/jpeg").addText("name", name).addText("description", description);
PostEntityTypeEntityIdDocumentsResponse response = ok(
() -> fineractClient().documentsFixed().createDocument("clients", clientId, multipartData));
assertThat(response).isNotNull();
assertThat(response.getResourceId()).isNotNull();
documentId = response.getResourceId();
}
@Test
@Order(3)
void testRetrieveAllDocuments() {
var documents = ok(() -> fineractClient().documentsFixed().retrieveAllDocuments("clients", clientId));
assertThat(documents).isNotNull();
assertThat(documents).isNotEmpty();
}
@Test
@Order(4)
void testGetDocument() {
DocumentData doc = ok(() -> fineractClient().documentsFixed().getDocument("clients", clientId, documentId));
assertThat(doc).isNotNull();
assertThat(doc.getName()).isEqualTo("Feign Test Document");
assertThat(doc.getFileName()).isEqualTo(testFile.getName());
assertThat(doc.getDescription()).isEqualTo("Testing DocumentsApiFixed with Feign client");
assertThat(doc.getId()).isEqualTo(documentId);
assertThat(doc.getParentEntityType()).isEqualTo("clients");
assertThat(doc.getParentEntityId()).isEqualTo(clientId);
assertThat(doc.getType()).isEqualTo("image/jpeg");
}
@Test
@Order(5)
void testDownloadFile() throws IOException {
Response response = fineractClient().documentsFixed().downloadFile("clients", clientId, documentId);
assertNotNull(response);
assertEquals(200, response.status());
try (InputStream inputStream = response.body().asInputStream()) {
byte[] bytes = inputStream.readAllBytes();
assertThat(bytes.length).isEqualTo((int) testFile.length());
}
}
@Test
@Order(6)
void testUpdateDocument() {
String newName = "Updated Feign Test";
String newDescription = "Updated via Feign client";
FineractMultipartEncoder.MultipartData multipartData = new FineractMultipartEncoder.MultipartData().addText("name", newName)
.addText("description", newDescription);
var updateResponse = ok(() -> fineractClient().documentsFixed().updateDocument("clients", clientId, documentId, multipartData));
assertThat(updateResponse).isNotNull();
DocumentData doc = ok(() -> fineractClient().documentsFixed().getDocument("clients", clientId, documentId));
assertThat(doc.getName()).isEqualTo(newName);
assertThat(doc.getDescription()).isEqualTo(newDescription);
}
@Test
@Order(99)
void testDeleteDocument() {
var deleteResponse = ok(() -> fineractClient().documentsFixed().deleteDocument("clients", clientId, documentId));
assertThat(deleteResponse).isNotNull();
FeignException exception = assertThrows(FeignException.class,
() -> fineractClient().documentsFixed().getDocument("clients", clientId, documentId));
assertEquals(404, exception.status());
}
@Test
@Order(9999)
void testCreateDocumentBadArgs() {
FineractMultipartEncoder.MultipartData multipartData = new FineractMultipartEncoder.MultipartData().addText("name", "test.pdf");
FeignException exception = assertThrows(FeignException.class,
() -> fineractClient().documentsFixed().createDocument("clients", clientId, multipartData));
assertEquals(400, exception.status());
}
}
@@ -0,0 +1,111 @@
/**
* 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.integrationtests.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import feign.Response;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.HashMap;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.CreateStaffResponse;
import org.apache.fineract.client.models.StaffRequest;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class FeignImageTest extends FeignIntegrationTest {
final File testImage = new File(getClass().getResource("/michael.vorburger-crepes.jpg").getFile());
Long staffId;
@Override
protected FineractFeignClient fineractClient() {
return org.apache.fineract.integrationtests.common.FineractFeignClientHelper.createNewFineractFeignClient("mifos", "password",
true);
}
@Test
@Order(1)
void setupStaff() {
StaffRequest request = new StaffRequest();
request.setOfficeId(1L);
request.setFirstname("Feign");
request.setLastname("ImageTest" + System.currentTimeMillis());
request.setJoiningDate(LocalDate.now(ZoneId.of("UTC")).toString());
request.setDateFormat("yyyy-MM-dd");
request.setLocale("en_US");
CreateStaffResponse response = ok(() -> fineractClient().staff().create3(request));
assertThat(response).isNotNull();
assertThat(response.getResourceId()).isNotNull();
staffId = response.getResourceId();
}
@Test
@Order(2)
void testCreateStaffImage() throws Exception {
String dataUrl = org.apache.fineract.client.feign.services.ImagesApi.prepareFileUpload(testImage);
Response response = fineractClient().images().create("staff", staffId, dataUrl);
assertNotNull(response);
assertEquals(200, response.status());
}
@Test
@Order(3)
void testRetrieveStaffImage() throws IOException {
Response response = fineractClient().images().get("staff", staffId, new HashMap<>());
assertNotNull(response);
assertEquals(200, response.status());
try (InputStream inputStream = response.body().asInputStream()) {
byte[] bytes = inputStream.readAllBytes();
assertThat(bytes.length).isGreaterThan(0);
}
}
@Test
@Order(4)
void testUpdateStaffImage() {
Response response = fineractClient().images().update("staff", staffId,
org.apache.fineract.client.feign.services.ImagesApi.prepareFileUpload(testImage));
assertNotNull(response);
assertEquals(200, response.status());
}
@Test
@Order(99)
void testDeleteStaffImage() {
Response response = fineractClient().images().delete("staff", staffId);
assertNotNull(response);
assertEquals(200, response.status());
}
}
@@ -0,0 +1,103 @@
/**
* 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.integrationtests.client;
import java.math.BigDecimal;
import java.util.Optional;
import java.util.function.Supplier;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.feign.util.FeignCalls;
import org.apache.fineract.integrationtests.common.FineractFeignClientHelper;
import org.assertj.core.api.AbstractBigDecimalAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractDoubleAssert;
import org.assertj.core.api.AbstractFloatAssert;
import org.assertj.core.api.AbstractIntegerAssert;
import org.assertj.core.api.AbstractLongAssert;
import org.assertj.core.api.AbstractStringAssert;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.IterableAssert;
import org.assertj.core.api.ObjectAssert;
import org.assertj.core.api.OptionalAssert;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
/**
* Base Integration Test class for Feign-based client
*
* @author Apache Fineract
*/
@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public abstract class FeignIntegrationTest {
protected FineractFeignClient fineractClient() {
return FineractFeignClientHelper.getFineractFeignClient();
}
protected <T> T ok(Supplier<T> call) {
return FeignCalls.ok(call);
}
protected void executeVoid(Runnable call) {
FeignCalls.executeVoid(call);
}
public static IterableAssert<?> assertThat(Iterable<?> actual) {
return Assertions.assertThat(actual);
}
public static AbstractBigDecimalAssert<?> assertThat(BigDecimal actual) {
return Assertions.assertThat(actual);
}
public static <T> ObjectAssert<T> assertThat(T actual) {
return Assertions.assertThat(actual);
}
public static AbstractLongAssert<?> assertThat(Long actual) {
return Assertions.assertThat(actual);
}
public static AbstractDoubleAssert<?> assertThat(Double actual) {
return Assertions.assertThat(actual);
}
public static AbstractFloatAssert<?> assertThat(Float actual) {
return Assertions.assertThat(actual);
}
public static AbstractIntegerAssert<?> assertThat(Integer actual) {
return Assertions.assertThat(actual);
}
public static AbstractBooleanAssert<?> assertThat(Boolean actual) {
return Assertions.assertThat(actual);
}
public static AbstractStringAssert<?> assertThat(String actual) {
return Assertions.assertThat(actual);
}
public static <T> OptionalAssert<T> assertThat(Optional<T> actual) {
return Assertions.assertThat(actual);
}
}
@@ -0,0 +1,219 @@
/**
* 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.integrationtests.client.feign;
import java.time.LocalDate;
import java.util.function.Function;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdStatus;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.integrationtests.client.FeignIntegrationTest;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignAccountHelper;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignBusinessDateHelper;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignClientHelper;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignJournalEntryHelper;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanHelper;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignTransactionHelper;
import org.apache.fineract.integrationtests.client.feign.modules.LoanProductTemplates;
import org.apache.fineract.integrationtests.client.feign.modules.LoanRequestBuilders;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestAccounts;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestValidators;
import org.apache.fineract.integrationtests.common.FineractFeignClientHelper;
import org.junit.jupiter.api.BeforeAll;
public abstract class FeignLoanTestBase extends FeignIntegrationTest implements LoanProductTemplates {
protected static FeignAccountHelper accountHelper;
protected static FeignLoanHelper loanHelper;
protected static FeignTransactionHelper transactionHelper;
protected static FeignJournalEntryHelper journalHelper;
protected static FeignBusinessDateHelper businessDateHelper;
protected static FeignClientHelper clientHelper;
protected static LoanTestAccounts accounts;
@BeforeAll
public static void setupHelpers() {
FineractFeignClient client = FineractFeignClientHelper.getFineractFeignClient();
accountHelper = new FeignAccountHelper(client);
loanHelper = new FeignLoanHelper(client);
transactionHelper = new FeignTransactionHelper(client);
journalHelper = new FeignJournalEntryHelper(client);
businessDateHelper = new FeignBusinessDateHelper(client);
clientHelper = new FeignClientHelper(client);
}
protected LoanTestAccounts getAccounts() {
if (accounts == null) {
accounts = new LoanTestAccounts(accountHelper);
}
return accounts;
}
@Override
public Long getAssetAccountId(String accountName) {
return getAccounts().getAssetAccountId(accountName);
}
@Override
public Long getLiabilityAccountId(String accountName) {
return getAccounts().getLiabilityAccountId(accountName);
}
@Override
public Long getIncomeAccountId(String accountName) {
return getAccounts().getIncomeAccountId(accountName);
}
@Override
public Long getExpenseAccountId(String accountName) {
return getAccounts().getExpenseAccountId(accountName);
}
protected Long createClient(String firstName, String lastName) {
return clientHelper.createClient(firstName, lastName);
}
protected Long createLoanProduct(PostLoanProductsRequest request) {
return loanHelper.createLoanProduct(request);
}
protected Long applyForLoan(PostLoansRequest request) {
return loanHelper.applyForLoan(request);
}
protected Long approveLoan(Long loanId, PostLoansLoanIdRequest request) {
return loanHelper.approveLoan(loanId, request);
}
protected Long disburseLoan(Long loanId, PostLoansLoanIdRequest request) {
return loanHelper.disburseLoan(loanId, request);
}
protected GetLoansLoanIdResponse getLoanDetails(Long loanId) {
return loanHelper.getLoanDetails(loanId);
}
protected void undoApproval(Long loanId) {
loanHelper.undoApproval(loanId);
}
protected void undoDisbursement(Long loanId) {
loanHelper.undoDisbursement(loanId);
}
protected Long addRepayment(Long loanId, PostLoansLoanIdTransactionsRequest request) {
return transactionHelper.addRepayment(loanId, request);
}
protected Long addInterestWaiver(Long loanId, PostLoansLoanIdTransactionsRequest request) {
return transactionHelper.addInterestWaiver(loanId, request);
}
protected Long chargeOff(Long loanId, PostLoansLoanIdTransactionsRequest request) {
return transactionHelper.chargeOff(loanId, request);
}
protected Long addChargeback(Long loanId, Long transactionId, PostLoansLoanIdTransactionsRequest request) {
return transactionHelper.addChargeback(loanId, transactionId, request);
}
protected void undoRepayment(Long loanId, Long transactionId, String transactionDate) {
transactionHelper.undoRepayment(loanId, transactionId, transactionDate);
}
protected void verifyJournalEntries(Long loanId, LoanTestData.Journal... expectedEntries) {
journalHelper.verifyJournalEntries(loanId, expectedEntries);
}
protected void verifyJournalEntriesSequentially(Long loanId, LoanTestData.Journal... expectedEntries) {
journalHelper.verifyJournalEntriesSequentially(loanId, expectedEntries);
}
protected void runAt(String date, Runnable action) {
businessDateHelper.runAt(date, action);
}
protected void updateBusinessDate(String type, String date) {
businessDateHelper.updateBusinessDate(type, date);
}
protected void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
double principalPaid, double principalOutstanding) {
LoanTestValidators.validateRepaymentPeriod(loanDetails, index, dueDate, principalDue, principalPaid, principalOutstanding, 0.0,
0.0);
}
protected void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
LoanTestValidators.validateRepaymentPeriod(loanDetails, index, dueDate, principalDue, principalPaid, principalOutstanding,
paidInAdvance, paidLate);
}
protected void verifyLoanStatus(GetLoansLoanIdResponse loanDetails, Function<GetLoansLoanIdStatus, Boolean> extractor) {
LoanTestValidators.verifyLoanStatus(loanDetails, extractor);
}
protected Long createApproveAndDisburseLoan(Long clientId, Long productId, String date, Double principal, Integer numberOfRepayments) {
PostLoansRequest applyRequest = LoanRequestBuilders.applyLoan(clientId, productId, date, principal, numberOfRepayments);
Long loanId = applyForLoan(applyRequest);
PostLoansLoanIdRequest approveRequest = LoanRequestBuilders.approveLoan(principal, date);
approveLoan(loanId, approveRequest);
PostLoansLoanIdRequest disburseRequest = LoanRequestBuilders.disburseLoan(principal, date);
disburseLoan(loanId, disburseRequest);
return loanId;
}
protected Long createApprovedLoan(Long clientId, Long productId, String date, Double principal, Integer numberOfRepayments) {
PostLoansRequest applyRequest = LoanRequestBuilders.applyLoan(clientId, productId, date, principal, numberOfRepayments);
Long loanId = applyForLoan(applyRequest);
PostLoansLoanIdRequest approveRequest = LoanRequestBuilders.approveLoan(principal, date);
approveLoan(loanId, approveRequest);
return loanId;
}
protected LoanTestData.Journal debit(Long glAccountId, double amount) {
return LoanTestData.Journal.debit(glAccountId, amount);
}
protected LoanTestData.Journal credit(Long glAccountId, double amount) {
return LoanTestData.Journal.credit(glAccountId, amount);
}
protected PostLoansLoanIdTransactionsRequest repayment(double amount, String date) {
return LoanRequestBuilders.repayLoan(amount, date);
}
protected PostLoansLoanIdTransactionsRequest waiveInterest(double amount, String date) {
return LoanRequestBuilders.waiveInterest(amount, date);
}
protected PostLoansLoanIdTransactionsRequest chargeOff(String date) {
return LoanRequestBuilders.chargeOff(date);
}
}
@@ -0,0 +1,95 @@
/**
* 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.integrationtests.client.feign.helpers;
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import java.util.Collections;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.GetGLAccountsResponse;
import org.apache.fineract.client.models.PostGLAccountsRequest;
import org.apache.fineract.client.models.PostGLAccountsResponse;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
public class FeignAccountHelper {
private final FineractFeignClient fineractClient;
public FeignAccountHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public Account createAssetAccount(String name) {
return createAccount(name, "1", "ASSET");
}
public Account createLiabilityAccount(String name) {
return createAccount(name, "2", "LIABILITY");
}
public Account createIncomeAccount(String name) {
return createAccount(name, "4", "INCOME");
}
public Account createExpenseAccount(String name) {
return createAccount(name, "5", "EXPENSE");
}
private Account createAccount(String name, String glCode, String type) {
String uniqueName = Utils.uniqueRandomStringGenerator(name + "_", 4);
String accountCode = Utils.uniqueRandomStringGenerator("GL_" + glCode, 6);
PostGLAccountsRequest request = new PostGLAccountsRequest()//
.name(uniqueName)//
.glCode(accountCode)//
.manualEntriesAllowed(true)//
.type(getAccountTypeId(type))//
.usage(1);
PostGLAccountsResponse response = ok(() -> fineractClient.generalLedgerAccount().createGLAccount1(request));
GetGLAccountsResponse account = ok(
() -> fineractClient.generalLedgerAccount().retreiveAccount(response.getResourceId(), Collections.emptyMap()));
return new Account(account.getId().intValue(), getAccountType(type));
}
private Integer getAccountTypeId(String type) {
return switch (type) {
case "ASSET" -> 1;
case "LIABILITY" -> 2;
case "EQUITY" -> 3;
case "INCOME" -> 4;
case "EXPENSE" -> 5;
default -> throw new IllegalArgumentException("Unknown account type: " + type);
};
}
private Account.AccountType getAccountType(String type) {
return switch (type) {
case "ASSET" -> Account.AccountType.ASSET;
case "LIABILITY" -> Account.AccountType.LIABILITY;
case "EQUITY" -> Account.AccountType.EQUITY;
case "INCOME" -> Account.AccountType.INCOME;
case "EXPENSE" -> Account.AccountType.EXPENSE;
default -> throw new IllegalArgumentException("Unknown account type: " + type);
};
}
}
@@ -0,0 +1,62 @@
/**
* 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.integrationtests.client.feign.helpers;
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import java.util.Collections;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.BusinessDateResponse;
import org.apache.fineract.client.models.BusinessDateUpdateRequest;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData;
public class FeignBusinessDateHelper {
private final FineractFeignClient fineractClient;
public FeignBusinessDateHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public BusinessDateResponse getBusinessDate(String type) {
return ok(() -> fineractClient.businessDateManagement().getBusinessDate(type));
}
public void updateBusinessDate(String type, String date) {
BusinessDateUpdateRequest request = new BusinessDateUpdateRequest()//
.type(BusinessDateUpdateRequest.TypeEnum.fromValue(type))//
.date(date)//
.dateFormat("yyyy-MM-dd")//
.locale(LoanTestData.LOCALE);
ok(() -> fineractClient.businessDateManagement().updateBusinessDate(request, Collections.emptyMap()));
}
public void runAt(String date, Runnable action) {
BusinessDateResponse originalDate = getBusinessDate("BUSINESS_DATE");
try {
updateBusinessDate("BUSINESS_DATE", date);
action.run();
} finally {
if (originalDate != null && originalDate.getDate() != null) {
updateBusinessDate("BUSINESS_DATE", originalDate.getDate().toString());
}
}
}
}
@@ -0,0 +1,65 @@
/**
* 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.integrationtests.client.feign.helpers;
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import java.util.Collections;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.GetClientsClientIdResponse;
import org.apache.fineract.client.models.PostClientsRequest;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData;
import org.apache.fineract.integrationtests.common.Utils;
public class FeignClientHelper {
private final FineractFeignClient fineractClient;
public FeignClientHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public Long createClient(String firstName, String lastName) {
String externalId = Utils.randomStringGenerator("EXT_", 7);
String activationDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
PostClientsRequest request = new PostClientsRequest()//
.officeId(1L)//
.legalFormId(1L)//
.firstname(firstName)//
.lastname(lastName)//
.externalId(externalId)//
.active(true)//
.activationDate(activationDate)//
.dateFormat(LoanTestData.DATETIME_PATTERN)//
.locale(LoanTestData.LOCALE);
return createClient(request);
}
public Long createClient(PostClientsRequest request) {
PostClientsResponse response = ok(() -> fineractClient.clients().create6(request));
return response.getClientId();
}
public GetClientsClientIdResponse getClient(Long clientId) {
return ok(() -> fineractClient.clients().retrieveOne11(clientId, Collections.emptyMap()));
}
}
@@ -0,0 +1,69 @@
/**
* 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.integrationtests.client.feign.helpers;
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.List;
import java.util.Map;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
import org.apache.fineract.client.models.JournalEntryTransactionItem;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData;
public class FeignJournalEntryHelper {
private final FineractFeignClient fineractClient;
public FeignJournalEntryHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public GetJournalEntriesTransactionIdResponse getJournalEntriesForLoan(Long loanId) {
return ok(() -> fineractClient.journalEntries().retrieveAll1(Map.of("loanId", loanId)));
}
public void verifyJournalEntries(Long loanId, LoanTestData.Journal... expectedEntries) {
GetJournalEntriesTransactionIdResponse journalEntries = getJournalEntriesForLoan(loanId);
assertNotNull(journalEntries);
assertNotNull(journalEntries.getPageItems());
List<JournalEntryTransactionItem> actualEntries = journalEntries.getPageItems();
assertEquals(expectedEntries.length, actualEntries.size(),
"Expected " + expectedEntries.length + " journal entries but found " + actualEntries.size());
for (int i = 0; i < expectedEntries.length; i++) {
LoanTestData.Journal expected = expectedEntries[i];
JournalEntryTransactionItem actual = actualEntries.get(i);
Double expectedAmount = expected.amount;
Double actualAmount = actual.getAmount();
assertEquals(0, Double.compare(expectedAmount, actualAmount),
"Journal entry " + i + " amount mismatch: expected " + expectedAmount + " but got " + actualAmount);
assertEquals(expected.account.getAccountID().longValue(), actual.getGlAccountId(), "Journal entry " + i + " account mismatch");
assertEquals(expected.type, actual.getEntryType().getValue(), "Journal entry " + i + " type mismatch");
}
}
public void verifyJournalEntriesSequentially(Long loanId, LoanTestData.Journal... expectedEntries) {
verifyJournalEntries(loanId, expectedEntries);
}
}
@@ -0,0 +1,109 @@
/**
* 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.integrationtests.client.feign.helpers;
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import java.math.BigDecimal;
import java.util.Map;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
public class FeignLoanHelper {
private final FineractFeignClient fineractClient;
public FeignLoanHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public Long createLoanProduct(PostLoanProductsRequest request) {
PostLoanProductsResponse response = ok(() -> fineractClient.loanProducts().createLoanProduct(request));
return response.getResourceId();
}
public Long applyForLoan(PostLoansRequest request) {
PostLoansResponse response = ok(() -> fineractClient.loans().calculateLoanScheduleOrSubmitLoanApplication(request, null));
return response.getLoanId();
}
public Long approveLoan(Long loanId, PostLoansLoanIdRequest request) {
PostLoansLoanIdResponse response = ok(() -> fineractClient.loans().stateTransitions(loanId, request, Map.of("command", "approve")));
return response.getLoanId();
}
public Long disburseLoan(Long loanId, PostLoansLoanIdRequest request) {
PostLoansLoanIdResponse response = ok(
() -> fineractClient.loans().stateTransitions(loanId, request, Map.of("command", "disburse")));
return response.getLoanId();
}
public GetLoansLoanIdResponse getLoanDetails(Long loanId) {
return ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "all", "exclude", "guarantors,futureSchedule")));
}
public void undoApproval(Long loanId) {
PostLoansLoanIdRequest request = new PostLoansLoanIdRequest();
ok(() -> fineractClient.loans().stateTransitions(loanId, request, Map.of("command", "undoApproval")));
}
public void undoDisbursement(Long loanId) {
PostLoansLoanIdRequest request = new PostLoansLoanIdRequest();
ok(() -> fineractClient.loans().stateTransitions(loanId, request, Map.of("command", "undoDisbursal")));
}
public Long applyAndApproveLoan(Long clientId, Long productId, String submittedOnDate, Double principal, Integer numberOfRepayments) {
PostLoansRequest applyRequest = new PostLoansRequest()//
.clientId(clientId)//
.productId(productId)//
.loanType("individual")//
.submittedOnDate(submittedOnDate)//
.expectedDisbursementDate(submittedOnDate)//
.principal(BigDecimal.valueOf(principal))//
.loanTermFrequency(numberOfRepayments)//
.loanTermFrequencyType(2)//
.numberOfRepayments(numberOfRepayments)//
.repaymentEvery(1)//
.repaymentFrequencyType(2)//
.interestRatePerPeriod(BigDecimal.ZERO)//
.amortizationType(1)//
.interestType(0)//
.interestCalculationPeriodType(1)//
.transactionProcessingStrategyCode("mifos-standard-strategy")//
.locale("en")//
.dateFormat("dd MMMM yyyy");
Long loanId = applyForLoan(applyRequest);
PostLoansLoanIdRequest approveRequest = new PostLoansLoanIdRequest()//
.approvedLoanAmount(BigDecimal.valueOf(principal))//
.approvedOnDate(submittedOnDate)//
.locale("en")//
.dateFormat("dd MMMM yyyy");
approveLoan(loanId, approveRequest);
return loanId;
}
}
@@ -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.integrationtests.client.feign.helpers;
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import java.util.Map;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
public class FeignTransactionHelper {
private final FineractFeignClient fineractClient;
public FeignTransactionHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public Long addRepayment(Long loanId, PostLoansLoanIdTransactionsRequest request) {
PostLoansLoanIdTransactionsResponse response = ok(
() -> fineractClient.loanTransactions().executeLoanTransaction(loanId, request, Map.of("command", "repayment")));
return response.getResourceId();
}
public Long addInterestWaiver(Long loanId, PostLoansLoanIdTransactionsRequest request) {
PostLoansLoanIdTransactionsResponse response = ok(
() -> fineractClient.loanTransactions().executeLoanTransaction(loanId, request, Map.of("command", "waiveInterest")));
return response.getResourceId();
}
public Long chargeOff(Long loanId, PostLoansLoanIdTransactionsRequest request) {
PostLoansLoanIdTransactionsResponse response = ok(
() -> fineractClient.loanTransactions().executeLoanTransaction(loanId, request, Map.of("command", "chargeOff")));
return response.getResourceId();
}
public Long addChargeback(Long loanId, Long transactionId, PostLoansLoanIdTransactionsRequest request) {
PostLoansLoanIdTransactionsTransactionIdRequest chargebackRequest = new PostLoansLoanIdTransactionsTransactionIdRequest()//
.transactionDate(request.getTransactionDate())//
.transactionAmount(request.getTransactionAmount())//
.locale(request.getLocale())//
.dateFormat(request.getDateFormat());
PostLoansLoanIdTransactionsResponse response = ok(() -> fineractClient.loanTransactions().adjustLoanTransaction(loanId,
transactionId, chargebackRequest, Map.of("command", "chargeback")));
return response.getResourceId();
}
public void undoRepayment(Long loanId, Long transactionId, String transactionDate) {
PostLoansLoanIdTransactionsTransactionIdRequest request = new PostLoansLoanIdTransactionsTransactionIdRequest();
request.setTransactionDate(transactionDate);
request.setTransactionAmount(0.0);
request.setDateFormat("dd MMMM yyyy");
request.setLocale("en");
ok(() -> fineractClient.loanTransactions().adjustLoanTransaction(loanId, transactionId, request, Map.of("command", "undo")));
}
}
@@ -0,0 +1,240 @@
/**
* 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.integrationtests.client.feign.modules;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import org.apache.fineract.client.models.AllowAttributeOverrides;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.AmortizationType;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.DaysInMonthType;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.DaysInYearType;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.InterestCalculationPeriodType;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.InterestRateFrequencyType;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.InterestType;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.RepaymentFrequencyType;
import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.RescheduleStrategyMethod;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
public interface LoanProductTemplates {
Long getAssetAccountId(String accountName);
Long getLiabilityAccountId(String accountName);
Long getIncomeAccountId(String accountName);
Long getExpenseAccountId(String accountName);
default PostLoanProductsRequest onePeriod30DaysNoInterest() {
return new PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6))//
.shortName(Utils.uniqueRandomStringGenerator("", 4))//
.description("Loan Product Description")//
.includeInBorrowerCycle(false)//
.currencyCode("USD")//
.digitsAfterDecimal(2)//
.inMultiplesOf(0)//
.installmentAmountInMultiplesOf(1)//
.useBorrowerCycle(false)//
.minPrincipal(100.0)//
.principal(1000.0)//
.maxPrincipal(100000.0)//
.minNumberOfRepayments(1)//
.numberOfRepayments(1)//
.maxNumberOfRepayments(30)//
.isLinkedToFloatingInterestRates(false)//
.minInterestRatePerPeriod(0.0)//
.interestRatePerPeriod(0.0)//
.maxInterestRatePerPeriod(100.0)//
.interestRateFrequencyType(InterestRateFrequencyType.MONTHS)//
.repaymentEvery(30)//
.repaymentFrequencyType(RepaymentFrequencyType.DAYS_L)//
.amortizationType(AmortizationType.EQUAL_INSTALLMENTS)//
.interestType(InterestType.DECLINING_BALANCE)//
.isEqualAmortization(false)//
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)//
.transactionProcessingStrategyCode(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY)//
.loanScheduleType(LoanScheduleType.CUMULATIVE.toString())//
.daysInYearType(DaysInYearType.ACTUAL)//
.daysInMonthType(DaysInMonthType.ACTUAL)//
.canDefineInstallmentAmount(true)//
.graceOnArrearsAgeing(3)//
.overdueDaysForNPA(179)//
.accountMovesOutOfNPAOnlyOnArrearsCompletion(false)//
.principalThresholdForLastInstallment(50)//
.allowVariableInstallments(false)//
.canUseForTopup(false)//
.isInterestRecalculationEnabled(false)//
.holdGuaranteeFunds(false)//
.multiDisburseLoan(true)//
.allowAttributeOverrides(new AllowAttributeOverrides()//
.amortizationType(true)//
.interestType(true)//
.transactionProcessingStrategyCode(true)//
.interestCalculationPeriodType(true)//
.inArrearsTolerance(true)//
.repaymentEvery(true)//
.graceOnPrincipalAndInterestPayment(true)//
.graceOnArrearsAgeing(true))//
.allowPartialPeriodInterestCalcualtion(true)//
.maxTrancheCount(10)//
.outstandingLoanBalance(10000.0)//
.charges(Collections.emptyList())//
.accountingRule(3)//
.fundSourceAccountId(getLiabilityAccountId("fundSource"))//
.loanPortfolioAccountId(getAssetAccountId("loansReceivable"))//
.transfersInSuspenseAccountId(getAssetAccountId("suspense"))//
.interestOnLoanAccountId(getIncomeAccountId("interestIncome"))//
.incomeFromFeeAccountId(getIncomeAccountId("feeIncome"))//
.incomeFromPenaltyAccountId(getIncomeAccountId("penaltyIncome"))//
.incomeFromRecoveryAccountId(getIncomeAccountId("recoveries"))//
.writeOffAccountId(getExpenseAccountId("writtenOff"))//
.overpaymentLiabilityAccountId(getLiabilityAccountId("overpayment"))//
.receivableInterestAccountId(getAssetAccountId("interestReceivable"))//
.receivableFeeAccountId(getAssetAccountId("feeReceivable"))//
.receivablePenaltyAccountId(getAssetAccountId("penaltyReceivable"))//
.goodwillCreditAccountId(getExpenseAccountId("goodwillExpense"))//
.incomeFromGoodwillCreditInterestAccountId(getIncomeAccountId("interestIncomeChargeOff"))//
.incomeFromGoodwillCreditFeesAccountId(getIncomeAccountId("feeChargeOff"))//
.incomeFromGoodwillCreditPenaltyAccountId(getIncomeAccountId("feeChargeOff"))//
.incomeFromChargeOffInterestAccountId(getIncomeAccountId("interestIncomeChargeOff"))//
.incomeFromChargeOffFeesAccountId(getIncomeAccountId("feeChargeOff"))//
.incomeFromChargeOffPenaltyAccountId(getIncomeAccountId("penaltyChargeOff"))//
.chargeOffExpenseAccountId(getExpenseAccountId("chargeOff"))//
.chargeOffFraudExpenseAccountId(getExpenseAccountId("chargeOffFraud"))//
.dateFormat(LoanTestData.DATETIME_PATTERN)//
.locale("en_GB")//
.disallowExpectedDisbursements(true)//
.allowApprovedDisbursedAmountsOverApplied(true)//
.overAppliedCalculationType("percentage")//
.overAppliedNumber(50);
}
default PostLoanProductsRequest fourInstallmentsCumulative() {
return fourInstallmentsCumulativeTemplate().loanScheduleType(LoanScheduleType.CUMULATIVE.toString());
}
default PostLoanProductsRequest fourInstallmentsProgressive() {
return fourInstallmentsCumulativeTemplate().loanScheduleType("PROGRESSIVE").loanScheduleProcessingType("HORIZONTAL")
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD);
}
default PostLoanProductsRequest fourInstallmentsProgressiveWithCapitalizedIncome() {
return fourInstallmentsProgressive().enableIncomeCapitalization(true)//
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)//
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)//
.deferredIncomeLiabilityAccountId(getLiabilityAccountId("deferredIncomeLiability"))//
.incomeFromCapitalizationAccountId(getIncomeAccountId("feeIncome"))//
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE);
}
default PostLoanProductsRequest fourInstallmentsCumulativeTemplate() {
return new PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("4I_PRODUCT_", 6))//
.shortName(Utils.uniqueRandomStringGenerator("", 4))//
.description("4 installment product")//
.includeInBorrowerCycle(false)//
.useBorrowerCycle(false)//
.currencyCode("EUR")//
.digitsAfterDecimal(2)//
.principal(1000.0)//
.minPrincipal(100.0)//
.maxPrincipal(10000.0)//
.numberOfRepayments(4)//
.repaymentEvery(1)//
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L)//
.interestRatePerPeriod(10.0)//
.minInterestRatePerPeriod(0.0)//
.maxInterestRatePerPeriod(120.0)//
.interestRateFrequencyType(InterestRateFrequencyType.YEARS)//
.isLinkedToFloatingInterestRates(false)//
.allowVariableInstallments(false)//
.amortizationType(AmortizationType.EQUAL_INSTALLMENTS)//
.interestType(InterestType.DECLINING_BALANCE)//
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)//
.allowPartialPeriodInterestCalcualtion(false)//
.creditAllocation(List.of())//
.overdueDaysForNPA(179)//
.daysInMonthType(DaysInMonthType.DAYS_30)//
.daysInYearType(DaysInYearType.DAYS_360)//
.isInterestRecalculationEnabled(false)//
.canDefineInstallmentAmount(true)//
.repaymentStartDateType(1)//
.charges(List.of())//
.principalVariationsForBorrowerCycle(List.of())//
.interestRateVariationsForBorrowerCycle(List.of())//
.numberOfRepaymentVariationsForBorrowerCycle(List.of())//
.accountingRule(3)//
.canUseForTopup(false)//
.fundSourceAccountId(getLiabilityAccountId("fundSource"))//
.loanPortfolioAccountId(getAssetAccountId("loansReceivable"))//
.transfersInSuspenseAccountId(getAssetAccountId("suspense"))//
.interestOnLoanAccountId(getIncomeAccountId("interestIncome"))//
.incomeFromFeeAccountId(getIncomeAccountId("feeIncome"))//
.incomeFromPenaltyAccountId(getIncomeAccountId("penaltyIncome"))//
.incomeFromRecoveryAccountId(getIncomeAccountId("recoveries"))//
.writeOffAccountId(getExpenseAccountId("writtenOff"))//
.overpaymentLiabilityAccountId(getLiabilityAccountId("overpayment"))//
.receivableInterestAccountId(getAssetAccountId("interestReceivable"))//
.receivableFeeAccountId(getAssetAccountId("feeReceivable"))//
.receivablePenaltyAccountId(getAssetAccountId("penaltyReceivable"))//
.goodwillCreditAccountId(getExpenseAccountId("goodwillExpense"))//
.incomeFromGoodwillCreditInterestAccountId(getIncomeAccountId("interestIncomeChargeOff"))//
.incomeFromGoodwillCreditFeesAccountId(getIncomeAccountId("feeChargeOff"))//
.incomeFromGoodwillCreditPenaltyAccountId(getIncomeAccountId("feeChargeOff"))//
.incomeFromChargeOffInterestAccountId(getIncomeAccountId("interestIncomeChargeOff"))//
.incomeFromChargeOffFeesAccountId(getIncomeAccountId("feeChargeOff"))//
.incomeFromChargeOffPenaltyAccountId(getIncomeAccountId("penaltyChargeOff"))//
.chargeOffExpenseAccountId(getExpenseAccountId("chargeOff"))//
.chargeOffFraudExpenseAccountId(getExpenseAccountId("chargeOffFraud"))//
.dateFormat(LoanTestData.DATETIME_PATTERN)//
.locale("en")//
.enableAccrualActivityPosting(false)//
.multiDisburseLoan(false)//
.disallowExpectedDisbursements(false)//
.allowApprovedDisbursedAmountsOverApplied(true)//
.overAppliedCalculationType("percentage")//
.overAppliedNumber(50)//
.principalThresholdForLastInstallment(50)//
.holdGuaranteeFunds(false)//
.accountMovesOutOfNPAOnlyOnArrearsCompletion(false)//
.allowAttributeOverrides(new AllowAttributeOverrides()//
.amortizationType(true)//
.interestType(true)//
.transactionProcessingStrategyCode(true)//
.interestCalculationPeriodType(true)//
.inArrearsTolerance(true)//
.repaymentEvery(true)//
.graceOnPrincipalAndInterestPayment(true)//
.graceOnArrearsAgeing(true))//
.isEqualAmortization(false)//
.enableDownPayment(false)//
.enableInstallmentLevelDelinquency(false)//
.transactionProcessingStrategyCode(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY);
}
default PostLoanProductsRequest customizeProduct(PostLoanProductsRequest template,
Function<PostLoanProductsRequest, PostLoanProductsRequest> customizer) {
return customizer.apply(template);
}
}
@@ -0,0 +1,135 @@
/**
* 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.integrationtests.client.feign.modules;
import java.math.BigDecimal;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansRequest;
public final class LoanRequestBuilders {
private LoanRequestBuilders() {}
public static PostLoansRequest applyLoan(Long clientId, Long productId, String submittedOnDate, Double principal,
Integer numberOfRepayments) {
return new PostLoansRequest()//
.clientId(clientId)//
.productId(productId)//
.loanType("individual")//
.submittedOnDate(submittedOnDate)//
.expectedDisbursementDate(submittedOnDate)//
.principal(BigDecimal.valueOf(principal))//
.loanTermFrequency(numberOfRepayments)//
.loanTermFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS)//
.numberOfRepayments(numberOfRepayments)//
.repaymentEvery(1)//
.repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS)//
.interestRatePerPeriod(BigDecimal.ZERO)//
.amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS)//
.interestType(LoanTestData.InterestType.DECLINING_BALANCE)//
.interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)//
.transactionProcessingStrategyCode("mifos-standard-strategy")//
.locale(LoanTestData.LOCALE)//
.dateFormat(LoanTestData.DATETIME_PATTERN);
}
public static PostLoansRequest applyCumulativeLoan(Long clientId, Long productId, String submittedOnDate, Double principal,
Integer numberOfRepayments, Double interestRate) {
return new PostLoansRequest()//
.clientId(clientId)//
.productId(productId)//
.submittedOnDate(submittedOnDate)//
.expectedDisbursementDate(submittedOnDate)//
.principal(BigDecimal.valueOf(principal))//
.loanTermFrequency(numberOfRepayments)//
.loanTermFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS)//
.numberOfRepayments(numberOfRepayments)//
.repaymentEvery(1)//
.repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS)//
.interestRatePerPeriod(BigDecimal.valueOf(interestRate))//
.amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS)//
.interestType(LoanTestData.InterestType.DECLINING_BALANCE)//
.interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.DAILY)//
.transactionProcessingStrategyCode("DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY")//
.loanType("individual")//
.locale(LoanTestData.LOCALE)//
.dateFormat(LoanTestData.DATETIME_PATTERN);
}
public static PostLoansRequest applyProgressiveLoan(Long clientId, Long productId, String submittedOnDate, Double principal,
Integer numberOfRepayments, Double interestRate) {
return applyCumulativeLoan(clientId, productId, submittedOnDate, principal, numberOfRepayments, interestRate);
}
public static PostLoansLoanIdRequest approveLoan(Double approvedAmount, String approvedOnDate) {
return new PostLoansLoanIdRequest()//
.approvedLoanAmount(BigDecimal.valueOf(approvedAmount))//
.approvedOnDate(approvedOnDate)//
.locale(LoanTestData.LOCALE)//
.dateFormat(LoanTestData.DATETIME_PATTERN);
}
public static PostLoansLoanIdRequest disburseLoan(Double disbursedAmount, String disbursedOnDate) {
return new PostLoansLoanIdRequest()//
.actualDisbursementDate(disbursedOnDate)//
.transactionAmount(BigDecimal.valueOf(disbursedAmount))//
.locale(LoanTestData.LOCALE)//
.dateFormat(LoanTestData.DATETIME_PATTERN);
}
public static PostLoansLoanIdTransactionsRequest repayLoan(Double amount, String transactionDate) {
PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest();
request.setTransactionDate(transactionDate);
request.setTransactionAmount(amount);
request.setLocale(LoanTestData.LOCALE);
request.setDateFormat(LoanTestData.DATETIME_PATTERN);
return request;
}
public static PostLoansLoanIdTransactionsRequest makeWaiver(Double amount, String transactionDate) {
PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest();
request.setTransactionDate(transactionDate);
request.setTransactionAmount(amount);
request.setLocale(LoanTestData.LOCALE);
request.setDateFormat(LoanTestData.DATETIME_PATTERN);
return request;
}
public static PostLoansLoanIdTransactionsRequest chargeOff(String transactionDate) {
PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest();
request.setTransactionDate(transactionDate);
request.setLocale(LoanTestData.LOCALE);
request.setDateFormat(LoanTestData.DATETIME_PATTERN);
return request;
}
public static PostLoansLoanIdTransactionsRequest addChargeback(Long transactionId, Double amount, String transactionDate) {
PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest();
request.setTransactionDate(transactionDate);
request.setTransactionAmount(amount);
request.setLocale(LoanTestData.LOCALE);
request.setDateFormat(LoanTestData.DATETIME_PATTERN);
return request;
}
public static PostLoansLoanIdTransactionsRequest waiveInterest(Double amount, String transactionDate) {
return makeWaiver(amount, transactionDate);
}
}
@@ -0,0 +1,204 @@
/**
* 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.integrationtests.client.feign.modules;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignAccountHelper;
import org.apache.fineract.integrationtests.common.accounting.Account;
public class LoanTestAccounts implements LoanProductTemplates {
private final Account loansReceivableAccount;
private final Account interestReceivableAccount;
private final Account feeReceivableAccount;
private final Account penaltyReceivableAccount;
private final Account suspenseAccount;
private final Account fundSource;
private final Account overpaymentAccount;
private final Account interestIncomeAccount;
private final Account feeIncomeAccount;
private final Account penaltyIncomeAccount;
private final Account feeChargeOffAccount;
private final Account penaltyChargeOffAccount;
private final Account recoveriesAccount;
private final Account interestIncomeChargeOffAccount;
private final Account chargeOffExpenseAccount;
private final Account chargeOffFraudExpenseAccount;
private final Account writtenOffAccount;
private final Account goodwillExpenseAccount;
private final Account goodwillIncomeAccount;
private final Account deferredIncomeLiabilityAccount;
private final Account buyDownExpenseAccount;
public LoanTestAccounts(FeignAccountHelper accountHelper) {
this.loansReceivableAccount = accountHelper.createAssetAccount("loanPortfolio");
this.interestReceivableAccount = accountHelper.createAssetAccount("interestReceivable");
this.feeReceivableAccount = accountHelper.createAssetAccount("feeReceivable");
this.penaltyReceivableAccount = accountHelper.createAssetAccount("penaltyReceivable");
this.suspenseAccount = accountHelper.createAssetAccount("suspense");
this.fundSource = accountHelper.createLiabilityAccount("fundSource");
this.overpaymentAccount = accountHelper.createLiabilityAccount("overpayment");
this.interestIncomeAccount = accountHelper.createIncomeAccount("interestIncome");
this.feeIncomeAccount = accountHelper.createIncomeAccount("feeIncome");
this.penaltyIncomeAccount = accountHelper.createIncomeAccount("penaltyIncome");
this.feeChargeOffAccount = accountHelper.createIncomeAccount("feeChargeOff");
this.penaltyChargeOffAccount = accountHelper.createIncomeAccount("penaltyChargeOff");
this.recoveriesAccount = accountHelper.createIncomeAccount("recoveries");
this.interestIncomeChargeOffAccount = accountHelper.createIncomeAccount("interestIncomeChargeOff");
this.chargeOffExpenseAccount = accountHelper.createExpenseAccount("chargeOff");
this.chargeOffFraudExpenseAccount = accountHelper.createExpenseAccount("chargeOffFraud");
this.writtenOffAccount = accountHelper.createExpenseAccount("writtenOffAccount");
this.goodwillExpenseAccount = accountHelper.createExpenseAccount("goodwillExpenseAccount");
this.goodwillIncomeAccount = accountHelper.createIncomeAccount("goodwillIncomeAccount");
this.deferredIncomeLiabilityAccount = accountHelper.createLiabilityAccount("deferredIncomeLiabilityAccount");
this.buyDownExpenseAccount = accountHelper.createExpenseAccount("buyDownExpenseAccount");
}
@Override
public Long getAssetAccountId(String accountName) {
return switch (accountName) {
case "loansReceivable" -> loansReceivableAccount.getAccountID().longValue();
case "interestReceivable" -> interestReceivableAccount.getAccountID().longValue();
case "feeReceivable" -> feeReceivableAccount.getAccountID().longValue();
case "penaltyReceivable" -> penaltyReceivableAccount.getAccountID().longValue();
case "suspense" -> suspenseAccount.getAccountID().longValue();
default -> throw new IllegalArgumentException("Unknown asset account: " + accountName);
};
}
@Override
public Long getLiabilityAccountId(String accountName) {
return switch (accountName) {
case "fundSource" -> fundSource.getAccountID().longValue();
case "overpayment" -> overpaymentAccount.getAccountID().longValue();
case "deferredIncomeLiability" -> deferredIncomeLiabilityAccount.getAccountID().longValue();
default -> throw new IllegalArgumentException("Unknown liability account: " + accountName);
};
}
@Override
public Long getIncomeAccountId(String accountName) {
return switch (accountName) {
case "interestIncome" -> interestIncomeAccount.getAccountID().longValue();
case "feeIncome" -> feeIncomeAccount.getAccountID().longValue();
case "penaltyIncome" -> penaltyIncomeAccount.getAccountID().longValue();
case "feeChargeOff" -> feeChargeOffAccount.getAccountID().longValue();
case "penaltyChargeOff" -> penaltyChargeOffAccount.getAccountID().longValue();
case "recoveries" -> recoveriesAccount.getAccountID().longValue();
case "interestIncomeChargeOff" -> interestIncomeChargeOffAccount.getAccountID().longValue();
case "goodwillIncome" -> goodwillIncomeAccount.getAccountID().longValue();
default -> throw new IllegalArgumentException("Unknown income account: " + accountName);
};
}
@Override
public Long getExpenseAccountId(String accountName) {
return switch (accountName) {
case "chargeOff" -> chargeOffExpenseAccount.getAccountID().longValue();
case "chargeOffFraud" -> chargeOffFraudExpenseAccount.getAccountID().longValue();
case "writtenOff" -> writtenOffAccount.getAccountID().longValue();
case "goodwillExpense" -> goodwillExpenseAccount.getAccountID().longValue();
case "buyDownExpense" -> buyDownExpenseAccount.getAccountID().longValue();
default -> throw new IllegalArgumentException("Unknown expense account: " + accountName);
};
}
public Account getLoansReceivableAccount() {
return loansReceivableAccount;
}
public Account getInterestReceivableAccount() {
return interestReceivableAccount;
}
public Account getFeeReceivableAccount() {
return feeReceivableAccount;
}
public Account getPenaltyReceivableAccount() {
return penaltyReceivableAccount;
}
public Account getSuspenseAccount() {
return suspenseAccount;
}
public Account getFundSource() {
return fundSource;
}
public Account getOverpaymentAccount() {
return overpaymentAccount;
}
public Account getInterestIncomeAccount() {
return interestIncomeAccount;
}
public Account getFeeIncomeAccount() {
return feeIncomeAccount;
}
public Account getPenaltyIncomeAccount() {
return penaltyIncomeAccount;
}
public Account getFeeChargeOffAccount() {
return feeChargeOffAccount;
}
public Account getPenaltyChargeOffAccount() {
return penaltyChargeOffAccount;
}
public Account getRecoveriesAccount() {
return recoveriesAccount;
}
public Account getInterestIncomeChargeOffAccount() {
return interestIncomeChargeOffAccount;
}
public Account getChargeOffExpenseAccount() {
return chargeOffExpenseAccount;
}
public Account getChargeOffFraudExpenseAccount() {
return chargeOffFraudExpenseAccount;
}
public Account getWrittenOffAccount() {
return writtenOffAccount;
}
public Account getGoodwillExpenseAccount() {
return goodwillExpenseAccount;
}
public Account getGoodwillIncomeAccount() {
return goodwillIncomeAccount;
}
public Account getDeferredIncomeLiabilityAccount() {
return deferredIncomeLiabilityAccount;
}
public Account getBuyDownExpenseAccount() {
return buyDownExpenseAccount;
}
}
@@ -0,0 +1,231 @@
/**
* 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.integrationtests.client.feign.modules;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.fineract.integrationtests.common.accounting.Account;
public final class LoanTestData {
public static final String DATETIME_PATTERN = "dd MMMM yyyy";
public static final String LOCALE = "en";
private LoanTestData() {}
@ToString
@AllArgsConstructor
public static class Transaction {
public Double amount;
public String type;
public String date;
public Boolean reversed;
}
@ToString
@AllArgsConstructor
public static class TransactionExt {
public Double amount;
public String type;
public String date;
public Double outstandingPrincipal;
public Double principalPortion;
public Double interestPortion;
public Double feePortion;
public Double penaltyPortion;
public Double unrecognizedPortion;
public Double overpaymentPortion;
public Boolean reversed;
}
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class Journal {
public Double amount;
public Account account;
public String type;
public static Journal debit(Long glAccountId, double amount) {
Journal journal = new Journal();
journal.amount = amount;
journal.account = new Account(glAccountId.intValue(), null);
journal.type = "DEBIT";
return journal;
}
public static Journal credit(Long glAccountId, double amount) {
Journal journal = new Journal();
journal.amount = amount;
journal.account = new Account(glAccountId.intValue(), null);
journal.type = "CREDIT";
return journal;
}
}
@ToString
@AllArgsConstructor
public static class Installment {
public Double principalAmount;
public Double interestAmount;
public Double feeAmount;
public Double penaltyAmount;
public Double totalOutstandingAmount;
public Boolean completed;
public String dueDate;
public OutstandingAmounts outstandingAmounts;
public Double loanBalance;
}
@AllArgsConstructor
@ToString
public static class OutstandingAmounts {
public Double principalOutstanding;
public Double interestOutstanding;
public Double feeOutstanding;
public Double penaltyOutstanding;
public Double totalOutstanding;
}
public static final class AmortizationType {
public static final Integer EQUAL_INSTALLMENTS = 1;
private AmortizationType() {}
}
public static final class InterestType {
public static final Integer DECLINING_BALANCE = 0;
public static final Integer FLAT = 1;
private InterestType() {}
}
public static final class InterestRecalculationCompoundingMethod {
public static final Integer NONE = 0;
private InterestRecalculationCompoundingMethod() {}
}
public static final class RepaymentFrequencyType {
public static final Integer MONTHS = 2;
public static final Long MONTHS_L = 2L;
public static final String MONTHS_STRING = "MONTHS";
public static final Integer DAYS = 0;
public static final Long DAYS_L = 0L;
public static final String DAYS_STRING = "DAYS";
private RepaymentFrequencyType() {}
}
public static final class RecalculationRestFrequencyType {
public static final Integer SAME_AS_REPAYMENT_PERIOD = 1;
public static final Integer DAILY = 2;
private RecalculationRestFrequencyType() {}
}
public static final class InterestCalculationPeriodType {
public static final Integer DAILY = 0;
public static final Integer SAME_AS_REPAYMENT_PERIOD = 1;
private InterestCalculationPeriodType() {}
}
public static final class InterestRateFrequencyType {
public static final Integer MONTHS = 2;
public static final Integer YEARS = 3;
public static final Integer WHOLE_TERM = 4;
private InterestRateFrequencyType() {}
}
public static final class TransactionProcessingStrategyCode {
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
private TransactionProcessingStrategyCode() {}
}
public static final class RescheduleStrategyMethod {
public static final Integer RESCHEDULE_NEXT_REPAYMENTS = 1;
public static final Integer REDUCE_EMI_AMOUNT = 3;
public static final Integer ADJUST_LAST_UNPAID_PERIOD = 4;
private RescheduleStrategyMethod() {}
}
public static final class DaysInYearType {
public static final Integer INVALID = 0;
public static final Integer ACTUAL = 1;
public static final Integer DAYS_360 = 360;
public static final Integer DAYS_364 = 364;
public static final Integer DAYS_365 = 365;
private DaysInYearType() {}
}
public static final class DaysInMonthType {
public static final Integer INVALID = 0;
public static final Integer ACTUAL = 1;
public static final Integer DAYS_30 = 30;
private DaysInMonthType() {}
}
public static final class FuturePaymentAllocationRule {
public static final String LAST_INSTALLMENT = "LAST_INSTALLMENT";
public static final String NEXT_INSTALLMENT = "NEXT_INSTALLMENT";
public static final String NEXT_LAST_INSTALLMENT = "NEXT_LAST_INSTALLMENT";
private FuturePaymentAllocationRule() {}
}
public static final class SupportedInterestRefundTypesItem {
public static final String MERCHANT_ISSUED_REFUND = "MERCHANT_ISSUED_REFUND";
public static final String PAYOUT_REFUND = "PAYOUT_REFUND";
private SupportedInterestRefundTypesItem() {}
}
public static final class DaysInYearCustomStrategy {
public static final String FEB_29_PERIOD_ONLY = "FEB_29_PERIOD_ONLY";
public static final String FULL_LEAP_YEAR = "FULL_LEAP_YEAR";
private DaysInYearCustomStrategy() {}
}
}
@@ -0,0 +1,126 @@
/**
* 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.integrationtests.client.feign.modules;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdStatus;
import org.apache.fineract.integrationtests.common.Utils;
public final class LoanTestValidators {
private LoanTestValidators() {}
public static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
assertEquals(dueDate, period.getDueDate());
assertEquals(principalDue, Utils.getDoubleValue(period.getPrincipalDue()));
assertEquals(principalPaid, Utils.getDoubleValue(period.getPrincipalPaid()));
assertEquals(principalOutstanding, Utils.getDoubleValue(period.getPrincipalOutstanding()));
assertEquals(paidInAdvance, Utils.getDoubleValue(period.getTotalPaidInAdvanceForPeriod()));
assertEquals(paidLate, Utils.getDoubleValue(period.getTotalPaidLateForPeriod()));
}
public static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, double principalDue, double principalPaid,
double principalOutstanding, double paidInAdvance, double paidLate) {
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
assertEquals(principalDue, Utils.getDoubleValue(period.getPrincipalDue()));
assertEquals(principalPaid, Utils.getDoubleValue(period.getPrincipalPaid()));
assertEquals(principalOutstanding, Utils.getDoubleValue(period.getPrincipalOutstanding()));
assertEquals(paidInAdvance, Utils.getDoubleValue(period.getTotalPaidInAdvanceForPeriod()));
assertEquals(paidLate, Utils.getDoubleValue(period.getTotalPaidLateForPeriod()));
}
public static void validateFullyUnpaidRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, String dueDate,
double principalDue, double feeDue, double penaltyDue, double interestDue) {
validateRepaymentPeriod(loanDetails, index,
LocalDate.parse(dueDate, DateTimeFormatter.ofPattern(LoanTestData.DATETIME_PATTERN, Locale.ENGLISH)), principalDue, 0,
principalDue, feeDue, 0, feeDue, penaltyDue, 0, penaltyDue, interestDue, 0, interestDue, 0, 0);
}
public static void validateFullyPaidRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, String dueDate,
double principalDue, double feeDue, double penaltyDue, double interestDue) {
validateRepaymentPeriod(loanDetails, index,
LocalDate.parse(dueDate, DateTimeFormatter.ofPattern(LoanTestData.DATETIME_PATTERN, Locale.ENGLISH)), principalDue,
principalDue, 0, feeDue, feeDue, 0, penaltyDue, penaltyDue, 0, interestDue, interestDue, 0, 0, 0);
}
public static void validateFullyPaidRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, String dueDate,
double principalDue, double feeDue, double penaltyDue, double interestDue, double paidLate) {
validateRepaymentPeriod(loanDetails, index,
LocalDate.parse(dueDate, DateTimeFormatter.ofPattern(LoanTestData.DATETIME_PATTERN, Locale.ENGLISH)), principalDue,
principalDue, 0, feeDue, feeDue, 0, penaltyDue, penaltyDue, 0, interestDue, interestDue, 0, 0, paidLate);
}
public static void validateFullyPaidRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, String dueDate,
double principalDue, double feeDue, double penaltyDue, double interestDue, double paidLate, double paidInAdvance) {
validateRepaymentPeriod(loanDetails, index,
LocalDate.parse(dueDate, DateTimeFormatter.ofPattern(LoanTestData.DATETIME_PATTERN, Locale.ENGLISH)), principalDue,
principalDue, 0, feeDue, feeDue, 0, penaltyDue, penaltyDue, 0, interestDue, interestDue, 0, paidInAdvance, paidLate);
}
public static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
double feeDue, double penaltyDue, double interestDue) {
validateRepaymentPeriod(loanDetails, index, dueDate, principalDue, 0, principalDue, feeDue, 0, feeDue, penaltyDue, 0, penaltyDue,
interestDue, 0, interestDue, 0, 0);
}
public static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
double principalPaid, double principalOutstanding, double feeDue, double feePaid, double feeOutstanding, double penaltyDue,
double penaltyPaid, double penaltyOutstanding, double interestDue, double interestPaid, double interestOutstanding,
double paidInAdvance, double paidLate) {
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
assertEquals(dueDate, period.getDueDate());
assertEquals(principalDue, Utils.getDoubleValue(period.getPrincipalDue()));
assertEquals(principalPaid, Utils.getDoubleValue(period.getPrincipalPaid()));
assertEquals(principalOutstanding, Utils.getDoubleValue(period.getPrincipalOutstanding()));
assertEquals(feeDue, Utils.getDoubleValue(period.getFeeChargesDue()));
assertEquals(feePaid, Utils.getDoubleValue(period.getFeeChargesPaid()));
assertEquals(feeOutstanding, Utils.getDoubleValue(period.getFeeChargesOutstanding()));
assertEquals(penaltyDue, Utils.getDoubleValue(period.getPenaltyChargesDue()));
assertEquals(penaltyPaid, Utils.getDoubleValue(period.getPenaltyChargesPaid()));
assertEquals(penaltyOutstanding, Utils.getDoubleValue(period.getPenaltyChargesOutstanding()));
assertEquals(interestDue, Utils.getDoubleValue(period.getInterestDue()));
assertEquals(interestPaid, Utils.getDoubleValue(period.getInterestPaid()));
assertEquals(interestOutstanding, Utils.getDoubleValue(period.getInterestOutstanding()));
assertEquals(paidInAdvance, Utils.getDoubleValue(period.getTotalPaidInAdvanceForPeriod()));
assertEquals(paidLate, Utils.getDoubleValue(period.getTotalPaidLateForPeriod()));
}
public static void verifyLoanStatus(GetLoansLoanIdResponse loanDetails, Function<GetLoansLoanIdStatus, Boolean> extractor) {
assertNotNull(loanDetails);
assertNotNull(loanDetails.getStatus());
Boolean actualValue = extractor.apply(loanDetails.getStatus());
assertNotNull(actualValue);
assertTrue(actualValue);
}
}
@@ -0,0 +1,159 @@
/**
* 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.integrationtests.client.feign.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.time.LocalDate;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.Test;
public class FeignLoanCreationTest extends FeignLoanTestBase {
@Test
void testCreateAndDisburseLoan_OnePeriodNoInterest() {
Long clientId = createClient("John", "Doe");
assertNotNull(clientId);
PostLoanProductsRequest productRequest = onePeriod30DaysNoInterest();
Long productId = createLoanProduct(productRequest);
assertNotNull(productId);
String todayDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
Long loanId = createApproveAndDisburseLoan(clientId, productId, todayDate, 1000.0, 1);
assertNotNull(loanId);
GetLoansLoanIdResponse loan = getLoanDetails(loanId);
assertNotNull(loan);
verifyLoanStatus(loan, status -> status.getActive());
LocalDate expectedRepaymentDate = Utils.getLocalDateOfTenant().plusMonths(1);
validateRepaymentPeriod(loan, 1, expectedRepaymentDate, 1000.0, 0.0, 1000.0);
verifyJournalEntries(loanId, debit(getAccounts().getLoansReceivableAccount().getAccountID().longValue(), 1000.0),
credit(getAccounts().getFundSource().getAccountID().longValue(), 1000.0));
}
@Test
void testLoanRepayment_FullRepayment() {
Long clientId = createClient("Jane", "Smith");
Long productId = createLoanProduct(onePeriod30DaysNoInterest());
String todayDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
Long loanId = createApproveAndDisburseLoan(clientId, productId, todayDate, 1000.0, 1);
Long repaymentId = addRepayment(loanId, repayment(1000.0, todayDate));
assertNotNull(repaymentId);
GetLoansLoanIdResponse loan = getLoanDetails(loanId);
verifyLoanStatus(loan, status -> status.getClosedObligationsMet());
assertEquals(0.0, Utils.getDoubleValue(loan.getSummary().getTotalOutstanding()));
assertEquals(1000.0, Utils.getDoubleValue(loan.getSummary().getTotalRepayment()));
}
@Test
void testLoanUndoApproval() {
Long clientId = createClient("Bob", "Johnson");
Long productId = createLoanProduct(onePeriod30DaysNoInterest());
String todayDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
Long loanId = createApprovedLoan(clientId, productId, todayDate, 1000.0, 1);
GetLoansLoanIdResponse loanBeforeUndo = getLoanDetails(loanId);
verifyLoanStatus(loanBeforeUndo, status -> status.getWaitingForDisbursal());
undoApproval(loanId);
GetLoansLoanIdResponse loanAfterUndo = getLoanDetails(loanId);
verifyLoanStatus(loanAfterUndo, status -> status.getPendingApproval());
}
@Test
void testLoanUndoDisbursement() {
Long clientId = createClient("Alice", "Williams");
Long productId = createLoanProduct(onePeriod30DaysNoInterest());
String todayDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
Long loanId = createApproveAndDisburseLoan(clientId, productId, todayDate, 1000.0, 1);
GetLoansLoanIdResponse loanBeforeUndo = getLoanDetails(loanId);
verifyLoanStatus(loanBeforeUndo, status -> status.getActive());
undoDisbursement(loanId);
GetLoansLoanIdResponse loanAfterUndo = getLoanDetails(loanId);
verifyLoanStatus(loanAfterUndo, status -> status.getWaitingForDisbursal());
}
@Test
void testFourInstallmentLoan_CumulativeInterest() {
Long clientId = createClient("Charlie", "Brown");
Long productId = createLoanProduct(fourInstallmentsCumulative());
String todayDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
Long loanId = createApproveAndDisburseLoan(clientId, productId, todayDate, 1000.0, 4);
GetLoansLoanIdResponse loan = getLoanDetails(loanId);
verifyLoanStatus(loan, status -> status.getActive());
assertNotNull(loan.getRepaymentSchedule());
assertNotNull(loan.getRepaymentSchedule().getPeriods());
assertEquals(5, loan.getRepaymentSchedule().getPeriods().size());
}
@Test
void testPartialRepayment() {
Long clientId = createClient("David", "Miller");
Long productId = createLoanProduct(onePeriod30DaysNoInterest());
String todayDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
Long loanId = createApproveAndDisburseLoan(clientId, productId, todayDate, 1000.0, 1);
Long repaymentId = addRepayment(loanId, repayment(500.0, todayDate));
assertNotNull(repaymentId);
GetLoansLoanIdResponse loan = getLoanDetails(loanId);
verifyLoanStatus(loan, status -> status.getActive());
assertEquals(500.0, Utils.getDoubleValue(loan.getSummary().getTotalOutstanding()));
assertEquals(500.0, Utils.getDoubleValue(loan.getSummary().getTotalRepayment()));
}
@Test
void testUndoRepayment() {
Long clientId = createClient("Eva", "Davis");
Long productId = createLoanProduct(onePeriod30DaysNoInterest());
String todayDate = Utils.dateFormatter.format(Utils.getLocalDateOfTenant());
Long loanId = createApproveAndDisburseLoan(clientId, productId, todayDate, 1000.0, 1);
Long repaymentId = addRepayment(loanId, repayment(1000.0, todayDate));
assertNotNull(repaymentId);
GetLoansLoanIdResponse loanAfterRepayment = getLoanDetails(loanId);
verifyLoanStatus(loanAfterRepayment, status -> status.getClosedObligationsMet());
undoRepayment(loanId, repaymentId, todayDate);
GetLoansLoanIdResponse loanAfterUndo = getLoanDetails(loanId);
verifyLoanStatus(loanAfterUndo, status -> status.getActive());
LocalDate expectedRepaymentDate = Utils.getLocalDateOfTenant().plusMonths(1);
validateRepaymentPeriod(loanAfterUndo, 1, expectedRepaymentDate, 1000.0, 0.0, 1000.0);
}
}
@@ -0,0 +1,45 @@
/**
* 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.integrationtests.client.feign.tests;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase;
import org.junit.jupiter.api.Test;
public class FeignLoanTestBaseSmokeTest extends FeignLoanTestBase {
@Test
void testInfrastructureInitialization() {
assertNotNull(accountHelper);
assertNotNull(loanHelper);
assertNotNull(transactionHelper);
assertNotNull(journalHelper);
assertNotNull(businessDateHelper);
assertNotNull(clientHelper);
assertNotNull(getAccounts());
assertNotNull(getAccounts().getLoansReceivableAccount());
}
@Test
void testCreateClient() {
Long clientId = createClient("Test", "User");
assertNotNull(clientId);
}
}
@@ -59,6 +59,7 @@ import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuil
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -106,6 +107,12 @@ public class CobPartitioningTest extends BaseLoanIntegrationTest {
setProperFinancialActivity(TRANSFER_ACCOUNT);
}
@AfterEach
public void tearDown() {
// Restore business date configuration after each test
cleanUpAndRestoreBusinessDate();
}
private static void setProperFinancialActivity(Account transferAccount) {
List<GetFinancialActivityAccountsResponse> financialMappings = FINANCIAL_ACTIVITY_ACCOUNT_HELPER.getAllFinancialActivityAccounts();
financialMappings.forEach(mapping -> FINANCIAL_ACTIVITY_ACCOUNT_HELPER.deleteFinancialActivityAccount(mapping.getId()));
@@ -144,13 +151,16 @@ public class CobPartitioningTest extends BaseLoanIntegrationTest {
// Force close loans 3, 4, ... , N-3, N-2
Collections.sort(loanIds);
List<Integer> forelosedLoanIds = new ArrayList<>();
final CountDownLatch closeLatch = new CountDownLatch(N - 5);
// Warm up (EclipseLink sometimes fails if JPQL cache is not warm up but concurrent queries are executed)
LOAN_TRANSACTION_HELPER.forecloseLoan("02 March 2020", loanIds.get(2));
forelosedLoanIds.add(loanIds.get(2));
for (int i = 3; i < N - 2; i++) {
final int idx = i;
futures.add(executorService.submit(() -> {
LOAN_TRANSACTION_HELPER.forecloseLoan("02 March 2020", loanIds.get(idx));
forelosedLoanIds.add(loanIds.get(idx));
closeLatch.countDown();
}));
}
@@ -163,24 +173,45 @@ public class CobPartitioningTest extends BaseLoanIntegrationTest {
});
closeLatch.await();
// Create list of active (non-foreclosed) loan IDs
List<Integer> activeLoanIds = new ArrayList<>(loanIds);
activeLoanIds.removeAll(forelosedLoanIds);
log.info("Active loan IDs after foreclosure: {}", activeLoanIds);
// Let's retrieve the partitions
List<Map<String, Object>> cobPartitions = CobHelper.getCobPartitions(REQUEST_SPEC, RESPONSE_SPEC, 3, "");
log.info("\nLoans created: {},\nRetrieved partitions: {}", loanIds, cobPartitions);
Assertions.assertEquals(2, cobPartitions.size());
List<Map<String, Object>> allPartitions = CobHelper.getCobPartitions(REQUEST_SPEC, RESPONSE_SPEC, 3, "");
log.info("\nLoans created by this test: {}\nAll partitions retrieved: {}", loanIds, allPartitions);
Assertions.assertEquals(0, cobPartitions.get(0).get("pageNo"));
Assertions.assertEquals(loanIds.get(0), cobPartitions.get(0).get("minId"));
Assertions.assertEquals(loanIds.get(8), cobPartitions.get(0).get("maxId"));
Assertions.assertEquals(3, cobPartitions.get(0).get("count"));
// Filter partitions to only include ACTIVE loans created by THIS test
// This ensures test isolation - we don't care about leftover loans from other tests
List<Map<String, Object>> testLoanPartitions = filterPartitionsForTestLoans(allPartitions, activeLoanIds);
log.info("Filtered partitions containing only our active test loans: {}", testLoanPartitions);
Assertions.assertEquals(1, cobPartitions.get(1).get("pageNo"));
Assertions.assertEquals(loanIds.get(9), cobPartitions.get(1).get("minId"));
Assertions.assertEquals(loanIds.get(9), cobPartitions.get(1).get("maxId"));
Assertions.assertEquals(1, cobPartitions.get(1).get("count"));
// Verify partitioning works correctly for our 4 active loans (10 created - 6 foreclosed)
// Expected: 2 partitions with page size 3
// Partition 0: loans 0, 1, 8 (3 loans)
// Partition 1: loan 9 (1 loan)
Assertions.assertEquals(2, testLoanPartitions.size(), "Expected 2 partitions for our 4 active test loans");
// Verify first partition
Assertions.assertEquals(0, testLoanPartitions.get(0).get("pageNo"));
Assertions.assertEquals(3, testLoanPartitions.get(0).get("count"));
Assertions.assertTrue(isLoanIdInRange(loanIds.get(0), testLoanPartitions.get(0)),
"First active loan should be in first partition");
Assertions.assertTrue(isLoanIdInRange(loanIds.get(1), testLoanPartitions.get(0)),
"Second active loan should be in first partition");
Assertions.assertTrue(isLoanIdInRange(loanIds.get(8), testLoanPartitions.get(0)),
"Ninth active loan should be in first partition");
// Verify second partition
Assertions.assertEquals(1, testLoanPartitions.get(1).get("pageNo"));
Assertions.assertEquals(1, testLoanPartitions.get(1).get("count"));
Assertions.assertTrue(isLoanIdInRange(loanIds.get(9), testLoanPartitions.get(1)), "Tenth loan should be in second partition");
executorService.shutdown();
} finally {
cleanUpAndRestoreBusinessDate();
} catch (Exception e) {
log.error("Test failed with exception", e);
throw e;
}
}
@@ -291,4 +322,41 @@ public class CobPartitioningTest extends BaseLoanIntegrationTest {
return collateral;
}
/**
* Filters partitions to only include those containing loans from the test's loan IDs. This ensures test isolation
* by ignoring any leftover loans from previous test runs.
*/
private List<Map<String, Object>> filterPartitionsForTestLoans(List<Map<String, Object>> allPartitions, List<Integer> testLoanIds) {
List<Map<String, Object>> filteredPartitions = new ArrayList<>();
for (Map<String, Object> partition : allPartitions) {
Integer minId = (Integer) partition.get("minId");
Integer maxId = (Integer) partition.get("maxId");
// Check if this partition contains any of our test loan IDs
boolean containsTestLoans = testLoanIds.stream().anyMatch(loanId -> loanId >= minId && loanId <= maxId);
if (containsTestLoans) {
// Count how many of OUR loans are in this partition's range
long testLoansInPartition = testLoanIds.stream().filter(loanId -> loanId >= minId && loanId <= maxId).count();
// Create a new partition entry with corrected count
Map<String, Object> filteredPartition = new HashMap<>(partition);
filteredPartition.put("count", (int) testLoansInPartition);
filteredPartitions.add(filteredPartition);
}
}
return filteredPartitions;
}
/**
* Checks if a loan ID falls within the min/max range of a partition.
*/
private boolean isLoanIdInRange(Integer loanId, Map<String, Object> partition) {
Integer minId = (Integer) partition.get("minId");
Integer maxId = (Integer) partition.get("maxId");
return loanId >= minId && loanId <= maxId;
}
}
@@ -0,0 +1,58 @@
/**
* 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.integrationtests.common;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.integrationtests.ConfigProperties;
public final class FineractFeignClientHelper {
private static final FineractFeignClient DEFAULT_FINERACT_FEIGN_CLIENT = createNewFineractFeignClient(ConfigProperties.Backend.USERNAME,
ConfigProperties.Backend.PASSWORD);
private FineractFeignClientHelper() {}
public static FineractFeignClient getFineractFeignClient() {
return DEFAULT_FINERACT_FEIGN_CLIENT;
}
public static FineractFeignClient createNewFineractFeignClient(String username, String password) {
return createNewFineractFeignClient(username, password, Function.identity()::apply);
}
public static FineractFeignClient createNewFineractFeignClient(String username, String password, boolean debugEnabled) {
return createNewFineractFeignClient(username, password, builder -> builder.debug(debugEnabled));
}
public static FineractFeignClient createNewFineractFeignClient(String username, String password,
Consumer<FineractFeignClient.Builder> customizer) {
String url = System.getProperty("fineract.it.url", buildURI());
FineractFeignClient.Builder builder = FineractFeignClient.builder().baseUrl(url).credentials(username, password)
.disableSslVerification(true);
customizer.accept(builder);
return builder.build();
}
private static String buildURI() {
return ConfigProperties.Backend.PROTOCOL + "://" + ConfigProperties.Backend.HOST + ":" + ConfigProperties.Backend.PORT
+ "/fineract-provider/api";
}
}