You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2023/04/15 08:29:50 UTC

[fineract] branch develop updated: FINERACT-1724: Fix error code for Batch API

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new d570e359a FINERACT-1724: Fix error code for Batch API
d570e359a is described below

commit d570e359aea66c79f4b8f42d8975a951b25e6b0b
Author: Adam Saghy <ad...@gmail.com>
AuthorDate: Fri Apr 14 19:09:08 2023 +0200

    FINERACT-1724: Fix error code for Batch API
---
 .../fineract/batch/exception/ErrorHandler.java     | 30 ++++----
 .../jobs/exception/LoanIdsHardLockedException.java | 29 ++++++++
 .../jobs/filter/LoanCOBApiFilter.java              |  9 +--
 .../fineract/integrationtests/BatchApiTest.java    | 84 ++++++++++++++++++++++
 .../inlinecob/InlineLoanCOBTest.java               | 45 +-----------
 .../useradministration/users/UserHelper.java       | 46 ++++++++++++
 6 files changed, 181 insertions(+), 62 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
index cb16b7435..342842c58 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.batch.exception;
 
 import com.google.gson.Gson;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
 import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
 import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
 import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
@@ -32,8 +33,10 @@ import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformInternalS
 import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformResourceNotFoundExceptionMapper;
 import org.apache.fineract.infrastructure.core.exceptionmapper.UnsupportedParameterExceptionMapper;
 import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
+import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
 import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
 import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
+import org.apache.http.HttpStatus;
 import org.springframework.dao.NonTransientDataAccessException;
 import org.springframework.transaction.TransactionException;
 
@@ -49,7 +52,7 @@ import org.springframework.transaction.TransactionException;
  */
 public class ErrorHandler extends RuntimeException {
 
-    private static Gson jsonHelper = GoogleGsonSerializerHelper.createGsonBuilder(true).create();
+    private static final Gson jsonHelper = GoogleGsonSerializerHelper.createGsonBuilder(true).create();
 
     /**
      * Sole Constructor
@@ -69,63 +72,66 @@ public class ErrorHandler extends RuntimeException {
         if (exception instanceof AbstractPlatformDomainRuleException) {
             PlatformDomainRuleExceptionMapper mapper = new PlatformDomainRuleExceptionMapper();
             final String errorBody = jsonHelper.toJson(mapper.toResponse((AbstractPlatformDomainRuleException) exception).getEntity());
-            return new ErrorInfo(500, 9999, errorBody);
+            return new ErrorInfo(HttpStatus.SC_INTERNAL_SERVER_ERROR, 9999, errorBody);
         } else if (exception instanceof AbstractPlatformResourceNotFoundException) {
 
             final PlatformResourceNotFoundExceptionMapper mapper = new PlatformResourceNotFoundExceptionMapper();
             final String errorBody = jsonHelper
                     .toJson(mapper.toResponse((AbstractPlatformResourceNotFoundException) exception).getEntity());
 
-            return new ErrorInfo(404, 1001, errorBody);
+            return new ErrorInfo(HttpStatus.SC_NOT_FOUND, 1001, errorBody);
 
         } else if (exception instanceof UnsupportedParameterException) {
 
             final UnsupportedParameterExceptionMapper mapper = new UnsupportedParameterExceptionMapper();
             final String errorBody = jsonHelper.toJson(mapper.toResponse((UnsupportedParameterException) exception).getEntity());
 
-            return new ErrorInfo(400, 2001, errorBody);
+            return new ErrorInfo(HttpStatus.SC_BAD_REQUEST, 2001, errorBody);
 
         } else if (exception instanceof PlatformApiDataValidationException) {
 
             final PlatformApiDataValidationExceptionMapper mapper = new PlatformApiDataValidationExceptionMapper();
             final String errorBody = jsonHelper.toJson(mapper.toResponse((PlatformApiDataValidationException) exception).getEntity());
 
-            return new ErrorInfo(400, 2002, errorBody);
+            return new ErrorInfo(HttpStatus.SC_BAD_REQUEST, 2002, errorBody);
 
         } else if (exception instanceof PlatformDataIntegrityException) {
 
             final PlatformDataIntegrityExceptionMapper mapper = new PlatformDataIntegrityExceptionMapper();
             final String errorBody = jsonHelper.toJson(mapper.toResponse((PlatformDataIntegrityException) exception).getEntity());
 
-            return new ErrorInfo(403, 3001, errorBody);
+            return new ErrorInfo(HttpStatus.SC_FORBIDDEN, 3001, errorBody);
 
         } else if (exception instanceof LinkedAccountRequiredException) {
 
             final PlatformDomainRuleExceptionMapper mapper = new PlatformDomainRuleExceptionMapper();
             final String errorBody = jsonHelper.toJson(mapper.toResponse((LinkedAccountRequiredException) exception).getEntity());
 
-            return new ErrorInfo(403, 3002, errorBody);
+            return new ErrorInfo(HttpStatus.SC_FORBIDDEN, 3002, errorBody);
 
         } else if (exception instanceof MultiDisbursementDataRequiredException) {
 
             final PlatformDomainRuleExceptionMapper mapper = new PlatformDomainRuleExceptionMapper();
             final String errorBody = jsonHelper.toJson(mapper.toResponse((MultiDisbursementDataRequiredException) exception).getEntity());
 
-            return new ErrorInfo(403, 3003, errorBody);
+            return new ErrorInfo(HttpStatus.SC_FORBIDDEN, 3003, errorBody);
 
         } else if (exception instanceof TransactionException) {
-            return new ErrorInfo(400, 4001, "{\"Exception\": " + exception.getMessage() + "}");
+            return new ErrorInfo(HttpStatus.SC_BAD_REQUEST, 4001, "{\"Exception\": " + exception.getMessage() + "}");
 
         } else if (exception instanceof PlatformInternalServerException) {
 
             final PlatformInternalServerExceptionMapper mapper = new PlatformInternalServerExceptionMapper();
             final String errorBody = jsonHelper.toJson(mapper.toResponse((PlatformInternalServerException) exception).getEntity());
 
-            return new ErrorInfo(500, 5001, errorBody);
+            return new ErrorInfo(HttpStatus.SC_INTERNAL_SERVER_ERROR, 5001, errorBody);
         } else if (exception instanceof NonTransientDataAccessException) {
-            return new ErrorInfo(400, 4001, "{\"Exception\": " + exception.getMessage() + "}");
+            return new ErrorInfo(HttpStatus.SC_BAD_REQUEST, 4001, "{\"Exception\": " + exception.getMessage() + "}");
+        } else if (exception instanceof LoanIdsHardLockedException e) {
+            String message = ApiGlobalErrorResponse.loanIsLocked(e.getLoanIdFromRequest()).toJson();
+            return new ErrorInfo(HttpStatus.SC_CONFLICT, 4090, message);
         }
 
-        return new ErrorInfo(500, 9999, "{\"Exception\": " + exception.toString() + "}");
+        return new ErrorInfo(HttpStatus.SC_INTERNAL_SERVER_ERROR, 9999, "{\"Exception\": " + exception.toString() + "}");
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/LoanIdsHardLockedException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/LoanIdsHardLockedException.java
new file mode 100644
index 000000000..8a6d51b0e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/LoanIdsHardLockedException.java
@@ -0,0 +1,29 @@
+/**
+ * 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.infrastructure.jobs.exception;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Getter
+public class LoanIdsHardLockedException extends RuntimeException {
+
+    private final Long loanIdFromRequest;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
index 02135d9f2..1df21bd9e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
@@ -31,7 +31,6 @@ import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -45,6 +44,7 @@ import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
@@ -86,13 +86,6 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchReque
 
     private final PlatformTransactionManager transactionManager;
 
-    @RequiredArgsConstructor
-    @Getter
-    private static class LoanIdsHardLockedException extends RuntimeException {
-
-        private final Long loanIdFromRequest;
-    }
-
     private static class Reject {
 
         private final String message;
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
index 3e55560c3..39789ddbb 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
@@ -32,6 +32,7 @@ import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.time.LocalDate;
 import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -51,6 +52,7 @@ import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
 import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanAccountLockHelper;
 import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
 import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
@@ -59,6 +61,7 @@ import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
 import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
 import org.apache.fineract.integrationtests.common.system.CodeHelper;
 import org.apache.fineract.integrationtests.common.system.DatatableHelper;
+import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
 import org.apache.http.HttpStatus;
 import org.junit.jupiter.api.Assertions;
@@ -2262,6 +2265,87 @@ public class BatchApiTest {
         Assertions.assertEquals("true", changes.get("fraud").getAsString());
     }
 
+    /**
+     * Tests that a loan is hard locked and if a repayment triggered in as a batch request, it returns the proper error
+     *
+     * @see org.apache.fineract.batch.command.internal.ModifyLoanApplicationByExternalIdCommandStrategy
+     */
+    @Test
+    public void shoulRetrieveTheProperErrorDuringLockedLoan() {
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(202).build();
+        LoanAccountLockHelper loanAccountLockHelper = new LoanAccountLockHelper(this.requestSpec, responseSpec);
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("1000.00") //
+                .withNumberOfRepayments("24") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("2") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .currencyDetails("0", "100").build(null);
+
+        final Long applyLoanRequestId = RandomUtils.nextLong(100, 1000);
+        final Long approveLoanRequestId = applyLoanRequestId + 1;
+        final Long disburseLoanRequestId = approveLoanRequestId + 1;
+        final Long getLoanRequestId = disburseLoanRequestId + 1;
+
+        // Create product
+        final Integer productId = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON);
+
+        // Create client
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientId);
+
+        final BatchRequest applyLoanRequest = BatchHelper.applyLoanRequestWithClientId(applyLoanRequestId, clientId, productId);
+
+        final BatchRequest approveLoanRequest = BatchHelper.transistionLoanStateByExternalId(approveLoanRequestId, applyLoanRequestId,
+                LocalDate.now(ZoneId.systemDefault()).minusDays(10), "approve");
+
+        final BatchRequest disburseLoanRequest = BatchHelper.transistionLoanStateByExternalId(disburseLoanRequestId, approveLoanRequestId,
+                LocalDate.now(ZoneId.systemDefault()).minusDays(8), "disburse");
+
+        final BatchRequest getLoanRequest = BatchHelper.getLoanByExternalIdRequest(getLoanRequestId, approveLoanRequestId,
+                "associations=all");
+
+        // Create batch requests list
+        final List<BatchRequest> batchRequests = Arrays.asList(applyLoanRequest, approveLoanRequest, disburseLoanRequest, getLoanRequest);
+
+        final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+        final List<BatchResponse> responses = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec,
+                jsonifiedRequest);
+
+        Assertions.assertEquals(HttpStatus.SC_OK, responses.get(0).getStatusCode(), "Verify Status Code 200 for Apply Loan");
+        Assertions.assertEquals(HttpStatus.SC_OK, responses.get(1).getStatusCode(), "Verify Status Code 200 for Approve Loan");
+        Assertions.assertEquals(HttpStatus.SC_OK, responses.get(2).getStatusCode(), "Verify Status Code 200 for Disburse Loan");
+        Assertions.assertEquals(HttpStatus.SC_OK, responses.get(3).getStatusCode(), "Verify Status Code 200 for Get Loan");
+
+        final Long loanId = new FromJsonHelper().parse(responses.get(2).getBody()).getAsJsonObject().get("resourceId").getAsLong();
+
+        loanAccountLockHelper.placeSoftLockOnLoanAccount(loanId.intValue(), "LOAN_COB_CHUNK_PROCESSING");
+
+        RequestSpecification requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(this.requestSpec, this.responseSpec);
+
+        // Create a repayment Request
+        final BatchRequest br = new BatchRequest();
+
+        br.setRequestId(1L);
+        br.setRelativeUrl(String.format("loans/" + loanId + "/transactions?command=repayment"));
+        br.setMethod("POST");
+        String dateString = LocalDate.now(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
+        br.setBody(String.format(
+                "{\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", " + "\"transactionDate\": \"%s\",  \"transactionAmount\": \"500\"}",
+                dateString));
+
+        final String jsonifiedRepaymentRequest = BatchHelper.toJsonString(List.of(br));
+
+        final List<BatchResponse> repaymentResponse = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec,
+                this.responseSpec, jsonifiedRepaymentRequest);
+
+        Assertions.assertEquals(HttpStatus.SC_CONFLICT, repaymentResponse.get(0).getStatusCode(), "Verify Status Code 409 for Locked Loan");
+    }
+
     /**
      * Delete datatable
      *
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java
index 41cbebee5..bfd709ff6 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java
@@ -40,18 +40,14 @@ import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
 import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
 import org.apache.fineract.client.models.GetDelinquencyTagHistoryResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
-import org.apache.fineract.client.models.GetOfficesResponse;
 import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
 import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
-import org.apache.fineract.client.models.PostUsersRequest;
-import org.apache.fineract.client.models.PostUsersResponse;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.integrationtests.common.BatchHelper;
 import org.apache.fineract.integrationtests.common.BusinessDateHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
 import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
-import org.apache.fineract.integrationtests.common.OfficeHelper;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
 import org.apache.fineract.integrationtests.common.loans.LoanAccountLockHelper;
@@ -62,7 +58,6 @@ import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtens
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
 import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
 import org.apache.fineract.integrationtests.common.products.DelinquencyRangesHelper;
-import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper;
 import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
 import org.apache.http.HttpStatus;
 import org.junit.jupiter.api.Assertions;
@@ -74,9 +69,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
 @ExtendWith(LoanTestLifecycleExtension.class)
 public class InlineLoanCOBTest {
 
-    private static final String REPAYMENT_LOAN_PERMISSION = "REPAYMENT_LOAN";
-    private static final String READ_LOAN_PERMISSION = "READ_LOAN";
-
     private ResponseSpecification responseSpec;
     private RequestSpecification requestSpec;
     private InlineLoanCOBHelper inlineLoanCOBHelper;
@@ -290,7 +282,7 @@ public class InlineLoanCOBTest {
 
             BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 10));
 
-            createNewSimpleUserWithoutBypassPermission();
+            requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
 
             loanAccountLockHelper.placeSoftLockOnLoanAccount(loanID, "LOAN_COB_PARTITIONING");
             loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
@@ -349,7 +341,7 @@ public class InlineLoanCOBTest {
 
             BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 10));
 
-            createNewSimpleUserWithoutBypassPermission();
+            requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
             loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
 
             loanTransactionHelper.makeRepayment("10 March 2020", 10.0f, loanID);
@@ -406,7 +398,7 @@ public class InlineLoanCOBTest {
 
             BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 10));
 
-            createNewSimpleUserWithoutBypassPermission();
+            requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
 
             loanAccountLockHelper.placeSoftLockOnLoanAccount(loanID, "LOAN_COB_PARTITIONING");
             loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
@@ -443,37 +435,6 @@ public class InlineLoanCOBTest {
         assertEquals("Size of the loan IDs list cannot be over 1000", responseUserMessage);
     }
 
-    private void createNewSimpleUserWithoutBypassPermission() {
-        GetOfficesResponse headOffice = OfficeHelper.getHeadOffice(requestSpec, responseSpec);
-        String username = Utils.uniqueRandomStringGenerator("NotificationUser", 4);
-        String password = Utils.randomStringGenerator("aA1", 10); // prefix is to conform with the password rules
-        String simpleRoleId = createSimpleRole();
-        PostUsersRequest createUserRequest = new PostUsersRequest().username(username)
-                .firstname(Utils.randomStringGenerator("NotificationFN", 4)).lastname(Utils.randomStringGenerator("NotificationLN", 4))
-                .email("whatever@mifos.org").password(password).repeatPassword(password).sendPasswordToEmail(false)
-                .roles(List.of(simpleRoleId)).officeId(headOffice.getId());
-
-        PostUsersResponse userCreationResponse = UserHelper.createUser(requestSpec, responseSpec, createUserRequest);
-        Assertions.assertNotNull(userCreationResponse.getResourceId());
-
-        requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
-        requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(username, password));
-        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
-    }
-
-    private String createSimpleRole() {
-        Integer roleId = RolesHelper.createRole(requestSpec, responseSpec);
-        addRepaymentPermissionToRole(roleId);
-        return roleId.toString();
-    }
-
-    private void addRepaymentPermissionToRole(Integer roleId) {
-        HashMap<String, Boolean> permissionMap = new HashMap<>();
-        permissionMap.put(REPAYMENT_LOAN_PERMISSION, true);
-        permissionMap.put(READ_LOAN_PERMISSION, true);
-        RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap);
-    }
-
     private Integer createLoanProduct(final String chargeId) {
         final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
                 .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
index 316d0f317..67ae40926 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
@@ -19,15 +19,20 @@
 package org.apache.fineract.integrationtests.useradministration.users;
 
 import com.google.gson.Gson;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.http.ContentType;
 import io.restassured.path.json.JsonPath;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.util.HashMap;
 import java.util.List;
+import org.apache.fineract.client.models.GetOfficesResponse;
 import org.apache.fineract.client.models.PostUsersRequest;
 import org.apache.fineract.client.models.PostUsersResponse;
 import org.apache.fineract.client.util.JSON;
+import org.apache.fineract.integrationtests.common.OfficeHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper;
 import org.junit.jupiter.api.Assertions;
 
 public final class UserHelper {
@@ -35,6 +40,11 @@ public final class UserHelper {
     private static final String CREATE_USER_URL = "/fineract-provider/api/v1/users?" + Utils.TENANT_IDENTIFIER;
     private static final String USER_URL = "/fineract-provider/api/v1/users";
     private static final Gson GSON = new JSON().getGson();
+    private static final String REPAYMENT_LOAN_PERMISSION = "REPAYMENT_LOAN";
+    private static final String READ_LOAN_PERMISSION = "READ_LOAN";
+
+    private static boolean simpleUserCreated = false;
+    private static String simpleUsername;
 
     private UserHelper() {}
 
@@ -128,4 +138,40 @@ public final class UserHelper {
     private static String createRoleOperationURL(final Integer userId) {
         return USER_URL + "/" + userId + "?" + Utils.TENANT_IDENTIFIER;
     }
+
+    public static RequestSpecification getSimpleUserWithoutBypassPermission(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        String password = "aA1qwerty56";
+        if (!simpleUserCreated) {
+            GetOfficesResponse headOffice = OfficeHelper.getHeadOffice(requestSpec, responseSpec);
+            simpleUsername = Utils.uniqueRandomStringGenerator("NotificationUser", 4);
+            String simpleRoleId = createSimpleRole(requestSpec, responseSpec);
+            PostUsersRequest createUserRequest = new PostUsersRequest().username(simpleUsername)
+                    .firstname(Utils.randomStringGenerator("NotificationFN", 4)).lastname(Utils.randomStringGenerator("NotificationLN", 4))
+                    .email("whatever@mifos.org").password(password).repeatPassword(password).sendPasswordToEmail(false)
+                    .roles(List.of(simpleRoleId)).officeId(headOffice.getId());
+
+            PostUsersResponse userCreationResponse = UserHelper.createUser(requestSpec, responseSpec, createUserRequest);
+            Assertions.assertNotNull(userCreationResponse.getResourceId());
+            simpleUserCreated = true;
+        }
+        RequestSpecification responseRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        responseRequestSpec.header("Authorization",
+                "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(simpleUsername, password));
+        return responseRequestSpec;
+    }
+
+    private static String createSimpleRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        Integer roleId = RolesHelper.createRole(requestSpec, responseSpec);
+        addRepaymentPermissionToRole(requestSpec, responseSpec, roleId);
+        return roleId.toString();
+    }
+
+    private static void addRepaymentPermissionToRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            Integer roleId) {
+        HashMap<String, Boolean> permissionMap = new HashMap<>();
+        permissionMap.put(REPAYMENT_LOAN_PERMISSION, true);
+        permissionMap.put(READ_LOAN_PERMISSION, true);
+        RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap);
+    }
 }