From b5f220d6754369f2bf1b13f32dc9dd4c5f75760e Mon Sep 17 00:00:00 2001 From: "Rym.Ghosn" Date: Fri, 31 Jul 2026 10:53:08 +0300 Subject: [PATCH] FINERACT-2726: Fix invalid SQL literal in entity-access ID list when no offices are mapped When a user has no entity-access mappings for a given entity type, getSQLQueryInClause_WithListOfIDsForEntityAccess appended the literal string "false" instead of a numeric sentinel to the SQL IN-clause ID list, causing an invalid query instead of correctly returning zero rows. Adds integration and e2e test coverage: with office-specific-products-enabled and restrict-products-to-user-office both enabled, retrieving savings products for a user whose office has no savings-product entity-access mapping now succeeds instead of failing with "invalid input syntax for type bigint". Signed-off-by: Rym.Ghosn --- .../stepdef/saving/SavingsAccountStepDef.java | 19 +++++++++++++++++++ .../resources/features/SavingsProduct.feature | 8 ++++++++ .../FineractEntityAccessReadServiceImpl.java | 4 ++-- ...SavingsProductCreationIntegrationTest.java | 17 +++++++++++++++++ .../common/savings/SavingsProductHelper.java | 6 ++++++ 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/SavingsProduct.feature diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java index 6f7a229c40b..aa42075b5ef 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java @@ -23,8 +23,10 @@ import static org.assertj.core.api.Assertions.fail; import io.cucumber.datatable.DataTable; +import io.cucumber.java.After; import io.cucumber.java.en.And; import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; import java.math.BigDecimal; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -32,6 +34,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.GetSavingsProductsResponse; import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PostSavingsAccountTransactionsRequest; import org.apache.fineract.client.models.PostSavingsAccountTransactionsResponse; @@ -47,6 +50,7 @@ import org.apache.fineract.test.factory.SavingsProductRequestFactory; import org.apache.fineract.test.helper.ErrorMessageHelper; import org.apache.fineract.test.helper.ErrorResponse; +import org.apache.fineract.test.helper.GlobalConfigurationHelper; import org.apache.fineract.test.helper.Utils; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; @@ -57,6 +61,9 @@ public class SavingsAccountStepDef extends AbstractStepDef { @Autowired private FineractFeignClient fineractClient; + @Autowired + private GlobalConfigurationHelper globalConfigurationHelper; + public static final String DATE_FORMAT = "dd MMMM yyyy"; private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT); private static final String EUR = "EUR"; @@ -69,6 +76,18 @@ public void createEurSavingsProduct() { testContext().set(TestContextKey.DEFAULT_SAVINGS_PRODUCT_CREATE_RESPONSE_EUR, savingsProductResponse); } + @After("@SavingsProductOfficeRestrictionFeature") + public void restoreOfficeSpecificProductRestrictionConfig() { + globalConfigurationHelper.disableGlobalConfiguration("restrict-products-to-user-office", 0L); + globalConfigurationHelper.disableGlobalConfiguration("office-specific-products-enabled", 0L); + } + + @When("Savings products are retrieved successfully") + public void retrieveSavingsProductsSuccessfully() { + List savingsProducts = ok(() -> fineractClient.savingsProduct().retrieveAllSavingsProducts()); + assertThat(savingsProducts).isNotNull(); + } + @And("Client creates a new EUR savings account with {string} submitted on date") public void createSavingsAccountEUR(String submittedOnDate) { PostClientsResponse clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); diff --git a/fineract-e2e-tests-runner/src/test/resources/features/SavingsProduct.feature b/fineract-e2e-tests-runner/src/test/resources/features/SavingsProduct.feature new file mode 100644 index 00000000000..34a152ec2dd --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/SavingsProduct.feature @@ -0,0 +1,8 @@ +@SavingsProduct +Feature: SavingsProduct + + @SavingsProductOfficeRestrictionFeature + Scenario: As a user I would like to retrieve savings products when office-specific product restriction is enabled and my office has no explicit product mapping + Given Global configuration "office-specific-products-enabled" is enabled + And Global configuration "restrict-products-to-user-office" is enabled + When Savings products are retrieved successfully diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java index 767a768e2f1..028e2ce2eec 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java @@ -96,8 +96,8 @@ public String getSQLQueryInClause_WithListOfIDsForEntityAccess(FineractEntityTyp } else { accessListCSVStrBuf = new StringBuilder(); - accessListCSVStrBuf.append("false"); // Append false so that no rows - // will be returned + accessListCSVStrBuf.append("-1"); // Append -1 so that no rows + // will be returned } if (accessListCSVStrBuf != null) { returnIdListStr = accessListCSVStrBuf.toString(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsProductCreationIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsProductCreationIntegrationTest.java index fe3c8b5287c..ed22f7d88b2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsProductCreationIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsProductCreationIntegrationTest.java @@ -24,6 +24,8 @@ import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import org.apache.fineract.client.models.GetSavingsProductsProductIdResponse; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; @@ -139,6 +141,21 @@ public void testSavingsProductWithOverdraftUpdate_AllowsOverdraft() { } + @Test + public void testRetrieveSavingsProductsWithOfficeSpecificRestrictionEnabledAndNoEntityAccessMapping() { + final GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper(); + try { + globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.OFFICE_SPECIFIC_PRODUCTS_ENABLED, true); + globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.RESTRICT_PRODUCTS_TO_USER_OFFICE, true); + + final String savingsProducts = SavingsProductHelper.retrieveAllSavingsProducts(requestSpec, responseSpec); + Assertions.assertNotNull(savingsProducts); + } finally { + globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.RESTRICT_PRODUCTS_TO_USER_OFFICE, false); + globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.OFFICE_SPECIFIC_PRODUCTS_ENABLED, false); + } + } + public static Integer createSavingsProductWithAccrualAccountingWithOverdraftAllowed(final String interestReceivableAccount, final String minOpenningBalance, final Account... accounts) { LOG.info("------------------------------CREATING NEW SAVINGS PRODUCT WITH OVERDRAFT ---------------------------------------"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java index 89a4f7487d1..2c299acc630 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java @@ -503,4 +503,10 @@ public static GetSavingsProductsProductIdResponse getSavingsProductById(final Re return GSON.fromJson(response, GetSavingsProductsProductIdResponse.class); } + @Deprecated(forRemoval = true) + public static String retrieveAllSavingsProducts(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { + LOG.info("-------------------- RETRIEVING ALL SAVINGS PRODUCTS --------------------------"); + return Utils.performServerGet(requestSpec, responseSpec, CREATE_SAVINGS_PRODUCT_URL); + } + }