From 770719e8ecbc40933bdec48e4e505fe60a546c01 Mon Sep 17 00:00:00 2001 From: Arnold Galovics Date: Fri, 26 Jan 2024 09:04:59 +0100 Subject: [PATCH] FINERACT-1971: Handling inline COB execution before batch API execution outside of the batch transaction --- .../BodyCachingHttpServletRequestWrapper.java | 88 +++++++++++++ .../jobs/filter/LoanCOBApiFilter.java | 7 +- .../jobs/filter/LoanCOBBatchPreprocessor.java | 74 ----------- .../jobs/filter/LoanCOBFilterHelper.java | 90 ++++++++++++- .../jobs/filter/LoanCOBApiFilterTest.java | 34 +++-- .../BaseLoanIntegrationTest.java | 120 +++++++++++++++++- .../integrationtests/BatchApiTest.java | 32 ++--- .../BatchLoanIntegrationTest.java | 101 +++++++++++++++ .../integrationtests/common/BatchHelper.java | 12 ++ .../common/error/ErrorResponse.java | 27 ++++ .../useradministration/users/UserHelper.java | 20 +-- 11 files changed, 484 insertions(+), 121 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/http/BodyCachingHttpServletRequestWrapper.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBBatchPreprocessor.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/common/error/ErrorResponse.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/http/BodyCachingHttpServletRequestWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/http/BodyCachingHttpServletRequestWrapper.java new file mode 100644 index 000000000..862d2620e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/http/BodyCachingHttpServletRequestWrapper.java @@ -0,0 +1,88 @@ +/** + * 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.core.http; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import org.apache.commons.io.IOUtils; + +public class BodyCachingHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private final byte[] cachedBody; + + @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW") + public BodyCachingHttpServletRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + this.cachedBody = IOUtils.toByteArray(request.getInputStream()); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return new CachedBodyServletInputStream(cachedBody); + } + + @Override + public BufferedReader getReader() throws IOException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); + return new BufferedReader(new InputStreamReader(byteArrayInputStream, UTF_8)); + } + + public static class CachedBodyServletInputStream extends ServletInputStream { + + private final InputStream inputStream; + + public CachedBodyServletInputStream(byte[] cachedBody) { + this.inputStream = new ByteArrayInputStream(cachedBody); + } + + @Override + public boolean isFinished() { + try { + return inputStream.available() == 0; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public int read() throws IOException { + return inputStream.read(); + } + } +} 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 59e4a0855..56a0be0c1 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 @@ -27,6 +27,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; +import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException; import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException; import org.apache.http.HttpStatus; @@ -62,7 +63,9 @@ public class LoanCOBApiFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - if (!helper.isOnApiList(request.getPathInfo(), request.getMethod())) { + request = new BodyCachingHttpServletRequestWrapper(request); + + if (!helper.isOnApiList(request)) { proceed(filterChain, request, response); } else { try { @@ -71,7 +74,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter { proceed(filterChain, request, response); } else { try { - List loanIds = helper.calculateRelevantLoanIds(request.getPathInfo()); + List loanIds = helper.calculateRelevantLoanIds(request); if (!loanIds.isEmpty() && helper.isLoanBehind(loanIds)) { helper.executeInlineCob(loanIds); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBBatchPreprocessor.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBBatchPreprocessor.java deleted file mode 100644 index b4b39bedb..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBBatchPreprocessor.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * 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.filter; - -import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned; - -import io.github.resilience4j.core.functions.Either; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.apache.fineract.batch.domain.BatchRequest; -import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; -import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor; -import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; -import org.springframework.context.annotation.Conditional; -import org.springframework.stereotype.Component; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.support.TransactionTemplate; - -@Component -@RequiredArgsConstructor -@Conditional(LoanCOBEnabledCondition.class) -public class LoanCOBBatchPreprocessor implements BatchRequestPreprocessor { - - private final LoanCOBFilterHelper helper; - - private final PlatformTransactionManager transactionManager; - - @Override - public Either preprocess(BatchRequest batchRequest) { - TransactionTemplate tr = new TransactionTemplate(transactionManager); - tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); - return tr.execute(status -> { - try { - String method = batchRequest.getMethod(); - String relativeUrl = "/" + batchRequest.getRelativeUrl(); - if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) { - // to support pre-versioned relative paths - relativeUrl = "/v1/" + batchRequest.getRelativeUrl(); - } - if (helper.isOnApiList(relativeUrl, method)) { - boolean bypassUser = helper.isBypassUser(); - if (!bypassUser) { - List result = helper.calculateRelevantLoanIds(relativeUrl); - if (!result.isEmpty() && helper.isLoanBehind(result)) { - helper.executeInlineCob(result); - } - } - } - } catch (LoanNotFoundException e) { - return Either.right(batchRequest); - } catch (RuntimeException e) { - return Either.left(e); - } - return Either.right(batchRequest); - }); - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java index 9b43a299c..c412a2dea 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java @@ -18,7 +18,15 @@ */ package org.apache.fineract.infrastructure.jobs.filter; +import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; @@ -28,6 +36,7 @@ import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate; import org.apache.fineract.cob.loan.RetrieveLoanIdService; @@ -62,6 +71,7 @@ public class LoanCOBFilterHelper { private final RetrieveLoanIdService retrieveLoanIdService; private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; + private final ObjectMapper objectMapper; private static final List HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); @@ -100,14 +110,51 @@ public class LoanCOBFilterHelper { return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("/v1/rescheduleloans/"); } - public boolean isOnApiList(String pathInfo, String method) { + public boolean isOnApiList(HttpServletRequest request) throws IOException { + String pathInfo = request.getPathInfo(); + String method = request.getMethod(); if (StringUtils.isBlank(pathInfo)) { return false; } + if (isBatchApi(pathInfo)) { + return isBatchApiMatching(request); + } else { + return isApiMatching(method, pathInfo); + } + } + + private boolean isBatchApiMatching(HttpServletRequest request) throws IOException { + for (BatchRequest batchRequest : getBatchRequests(request)) { + String method = batchRequest.getMethod(); + String pathInfo = batchRequest.getRelativeUrl(); + if (isApiMatching(method, pathInfo)) { + return true; + } + } + return false; + } + + private List getBatchRequests(HttpServletRequest request) throws IOException { + List batchRequests = objectMapper.readValue(request.getInputStream(), new TypeReference<>() {}); + for (BatchRequest batchRequest : batchRequests) { + String pathInfo = "/" + batchRequest.getRelativeUrl(); + if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) { + pathInfo = "/v1/" + batchRequest.getRelativeUrl(); + } + batchRequest.setRelativeUrl(pathInfo); + } + return batchRequests; + } + + private boolean isApiMatching(String method, String pathInfo) { return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find() && URL_FUNCTION.test(pathInfo); } + private boolean isBatchApi(String pathInfo) { + return pathInfo.startsWith("/v1/batches"); + } + private boolean isGlim(String pathInfo) { return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches(); } @@ -138,8 +185,47 @@ public class LoanCOBFilterHelper { return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates); } - public List calculateRelevantLoanIds(String pathInfo) { + public List calculateRelevantLoanIds(HttpServletRequest request) throws IOException { + String pathInfo = request.getPathInfo(); + if (isBatchApi(pathInfo)) { + return getLoanIdsFromBatchApi(request); + } else { + return getLoanIdsFromApi(pathInfo); + } + } + private List getLoanIdsFromBatchApi(HttpServletRequest request) throws IOException { + List loanIds = new ArrayList<>(); + for (BatchRequest batchRequest : getBatchRequests(request)) { + // check the URL for Loan related ID + String relativeUrl = batchRequest.getRelativeUrl(); + if (!relativeUrl.contains("$.resourceId")) { + // if resourceId reference is used, we simply don't know the resourceId without executing the requests + // first, so skipping it + loanIds.addAll(getLoanIdsFromApi(relativeUrl)); + } + + // check the body for Loan ID + Long loanId = getTopLevelLoanIdFromBatchRequest(batchRequest); + if (loanId != null) { + loanIds.add(loanId); + } + } + return loanIds; + } + + private Long getTopLevelLoanIdFromBatchRequest(BatchRequest batchRequest) throws JsonProcessingException { + String body = batchRequest.getBody(); + if (StringUtils.isNotBlank(body)) { + JsonNode jsonNode = objectMapper.readTree(body); + if (jsonNode.has("loanId")) { + return jsonNode.get("loanId").asLong(); + } + } + return null; + } + + private List getLoanIdsFromApi(String pathInfo) { List loanIds = getLoanIdList(pathInfo); if (isLoanHardLocked(loanIds)) { throw new LoanIdsHardLockedException(loanIds.get(0)); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java index 361310230..e4ad8e63d 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify; import com.sun.research.ws.wadl.HTTPMethods; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.PrintWriter; import java.math.BigDecimal; @@ -47,6 +48,7 @@ import org.apache.fineract.cob.service.LoanAccountLockService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; @@ -142,9 +144,10 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/jobs/2/inline"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); testObj.doFilterInternal(request, response, filterChain); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -162,6 +165,7 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/loans/invalid2LoanId/charges"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(context.authenticatedUser()).willReturn(appUser); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); @@ -169,7 +173,7 @@ class LoanCOBApiFilterTest { anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -181,11 +185,12 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/jobs/2/inline"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(context.authenticatedUser()).willReturn(appUser); given(appUser.isBypassUser()).willReturn(true); testObj.doFilterInternal(request, response, filterChain); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -203,6 +208,7 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/loans/2/charges"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(context.authenticatedUser()).willReturn(appUser); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); @@ -211,7 +217,7 @@ class LoanCOBApiFilterTest { anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -229,6 +235,7 @@ class LoanCOBApiFilterTest { String uuid = UUID.randomUUID().toString(); given(request.getPathInfo()).willReturn("/v1/loans/external-id/" + uuid + "/charges"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(context.authenticatedUser()).willReturn(appUser); given(loanRepository.findIdByExternalId(any())).willReturn(2L); @@ -238,7 +245,7 @@ class LoanCOBApiFilterTest { anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -256,6 +263,7 @@ class LoanCOBApiFilterTest { Long resourceId = 123L; given(request.getPathInfo()).willReturn("/v1/rescheduleloans/" + resourceId + "/charges"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); @@ -266,7 +274,7 @@ class LoanCOBApiFilterTest { anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -288,6 +296,7 @@ class LoanCOBApiFilterTest { given(result.getLastClosedBusinessDate()).willReturn(businessDate.minusDays(2)); given(request.getPathInfo()).willReturn("/v1/loans/2?command=approve"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); @@ -297,7 +306,7 @@ class LoanCOBApiFilterTest { testObj.doFilterInternal(request, response, filterChain); verify(inlineLoanCOBExecutorService, times(1)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB"); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -318,6 +327,7 @@ class LoanCOBApiFilterTest { given(result.getId()).willReturn(2L); given(request.getPathInfo()).willReturn("/v1/loans/2?command=approve"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); @@ -328,7 +338,7 @@ class LoanCOBApiFilterTest { testObj.doFilterInternal(request, response, filterChain); verify(inlineLoanCOBExecutorService, times(0)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB"); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -340,12 +350,13 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/loans"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(context.authenticatedUser()).willReturn(appUser); testObj.doFilterInternal(request, response, filterChain); verify(inlineLoanCOBExecutorService, times(0)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB"); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -357,12 +368,13 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/loans/catch-up"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(context.authenticatedUser()).willReturn(appUser); testObj.doFilterInternal(request, response, filterChain); verify(inlineLoanCOBExecutorService, times(0)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB"); - verify(filterChain, times(1)).doFilter(request, response); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), eq(response)); } @Test @@ -375,6 +387,7 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/loans/2/charges"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(true); given(response.getWriter()).willReturn(writer); given(context.authenticatedUser()).willReturn(appUser); @@ -396,6 +409,7 @@ class LoanCOBApiFilterTest { given(request.getPathInfo()).willReturn("/v1/loans/glimAccount/2"); given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(request.getInputStream()).willReturn(new BodyCachingHttpServletRequestWrapper.CachedBodyServletInputStream(new byte[0])); given(glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true, BigDecimal.valueOf(2))).willReturn(glimAccount); given(glimAccount.getChildLoan()).willReturn(Collections.singleton(loan)); given(loan.getId()).willReturn(loanId); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index b04dd36eb..a406ca770 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -29,11 +29,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; +import io.restassured.internal.RequestSpecificationImpl; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -42,7 +44,10 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.ToString; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; import org.apache.fineract.client.models.AdvancedPaymentData; import org.apache.fineract.client.models.AllowAttributeOverrides; import org.apache.fineract.client.models.BusinessDateRequest; @@ -59,6 +64,7 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansRequest; import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.client.util.CallFailedRuntimeException; +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.GlobalConfigurationHelper; @@ -71,6 +77,8 @@ import org.apache.fineract.integrationtests.common.loans.LoanProductHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper; +import org.apache.fineract.integrationtests.useradministration.users.UserHelper; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; @@ -89,13 +97,18 @@ public abstract class BaseLoanIntegrationTest { protected static final String DATETIME_PATTERN = "dd MMMM yyyy"; protected final ResponseSpecification responseSpec = createResponseSpecification(Matchers.is(200)); - protected final RequestSpecification requestSpec = createRequestSpecification(); + + private final String fullAdminAuthKey = getFullAdminAuthKey(); + + protected final RequestSpecification requestSpec = createRequestSpecification(fullAdminAuthKey); + private final String nonByPassUserAuthKey = getNonByPassUserAuthKey(requestSpec, responseSpec); protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec); protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); protected final LoanProductHelper loanProductHelper = new LoanProductHelper(); protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec); protected ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec); + protected final InlineLoanCOBHelper inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec); protected BusinessDateHelper businessDateHelper = new BusinessDateHelper(); protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); @@ -119,6 +132,16 @@ public abstract class BaseLoanIntegrationTest { protected final Account writtenOffAccount = accountHelper.createExpenseAccount(); protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount(); + private String getNonByPassUserAuthKey(RequestSpecification requestSpec, ResponseSpecification responseSpec) { + // creates the user + UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec); + return Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(UserHelper.SIMPLE_USER_NAME, UserHelper.SIMPLE_USER_PASSWORD); + } + + private String getFullAdminAuthKey() { + return Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(); + } + // Loan product with proper accounting setup protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() { return new PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6))// @@ -256,14 +279,14 @@ public abstract class BaseLoanIntegrationTest { .amortizationType(amortizationType); } - private static RequestSpecification createRequestSpecification() { - RequestSpecification request = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); - request.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); - request.header("Fineract-Platform-TenantId", "default"); - return request; + private RequestSpecification createRequestSpecification(String authKey) { + RequestSpecification requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + authKey); + requestSpec.header("Fineract-Platform-TenantId", "default"); + return requestSpec; } - protected static ResponseSpecification createResponseSpecification(Matcher statusCodeMatcher) { + protected ResponseSpecification createResponseSpecification(Matcher statusCodeMatcher) { return new ResponseSpecBuilder().expectStatusCode(statusCodeMatcher).build(); } @@ -317,6 +340,16 @@ public abstract class BaseLoanIntegrationTest { } } + protected void executeInlineCOB(Long loanId) { + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId)); + } + + protected void verifyLastClosedBusinessDate(Long loanId, String lastClosedBusinessDate) { + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + Assertions.assertNotNull(loanDetails.getLastClosedBusinessDate()); + Assertions.assertEquals(lastClosedBusinessDate, loanDetails.getLastClosedBusinessDate().format(dateTimeFormatter)); + } + protected void disburseLoan(Long loanId, BigDecimal amount, String date) { loanTransactionHelper.disburseLoan(loanId, new PostLoansLoanIdRequest().actualDisbursementDate(date).dateFormat(DATETIME_PATTERN) .transactionAmount(amount).locale("en")); @@ -408,6 +441,16 @@ public abstract class BaseLoanIntegrationTest { } } + protected void runAsNonByPass(Runnable runnable) { + RequestSpecificationImpl requestSpecImpl = (RequestSpecificationImpl) requestSpec; + try { + requestSpecImpl.replaceHeader("Authorization", "Basic " + nonByPassUserAuthKey); + runnable.run(); + } finally { + requestSpecImpl.replaceHeader("Authorization", "Basic " + fullAdminAuthKey); + } + } + protected PostLoansRequest applyLoanRequest(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount, int numberOfRepayments) { return applyLoanRequest(clientId, loanProductId, loanDisbursementDate, amount, numberOfRepayments, null); @@ -508,6 +551,69 @@ public abstract class BaseLoanIntegrationTest { return new Installment(principalAmount, interestAmount, feeAmount, totalOutstandingAmount, completed, dueDate); } + protected BatchRequestBuilder batchRequest() { + return new BatchRequestBuilder(requestSpec, responseSpec); + } + + @RequiredArgsConstructor + public static class BatchRequestBuilder { + + private final RequestSpecification requestSpec; + private final ResponseSpecification responseSpec; + private List requests = new ArrayList<>(); + + public BatchRequestBuilder rescheduleLoan(Long requestId, Long loanId, String submittedOnDate, String rescheduleFromDate, + String adjustedDueDate) { + BatchRequest bRequest = new BatchRequest(); + bRequest.setRequestId(requestId); + bRequest.setRelativeUrl("rescheduleloans"); + bRequest.setMethod("POST"); + + bRequest.setBody(""" + { + "loanId": %d, + "rescheduleFromDate": "%s", + "rescheduleReasonId": 1, + "submittedOnDate": "%s", + "rescheduleReasonComment": "", + "adjustedDueDate": "%s", + "graceOnPrincipal": "", + "graceOnInterest": "", + "extraTerms": "", + "newInterestRate": "", + "dateFormat": "%s", + "locale": "en" + } + """.formatted(loanId, rescheduleFromDate, submittedOnDate, adjustedDueDate, DATETIME_PATTERN)); + + requests.add(bRequest); + return this; + } + + public BatchRequestBuilder approveRescheduleLoan(Long requestId, Long rescheduleBatchRequestId, String approvedOnDate) { + BatchRequest bRequest = new BatchRequest(); + bRequest.setRequestId(requestId); + bRequest.setRelativeUrl("rescheduleloans/$.resourceId?command=approve"); + bRequest.setMethod("POST"); + bRequest.setReference(rescheduleBatchRequestId); + + bRequest.setBody(""" + { + "approvedOnDate": "%s", + "dateFormat": "%s", + "locale": "en" + } + """.formatted(approvedOnDate, DATETIME_PATTERN)); + + requests.add(bRequest); + return this; + } + + public List executeEnclosingTransaction() { + return BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, BatchHelper.toJsonString(requests)); + } + } + @ToString @AllArgsConstructor public static class Transaction { 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 24b59b233..fd2398012 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 @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests; +import static java.lang.Integer.parseInt; import static org.apache.http.HttpStatus.SC_FORBIDDEN; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -54,6 +55,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.error.ErrorResponse; import org.apache.fineract.integrationtests.common.loans.LoanAccountLockHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; @@ -75,14 +77,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Test class for {@link org.apache.fineract.batch.command.CommandStrategyProvider}. This tests the response provided by - * commandStrategy by injecting it with a {@code BatchRequest}. - * - * @author RishabhShukla - * @see org.apache.fineract.integrationtests.common.BatchHelper - * @see org.apache.fineract.batch.domain.BatchRequest - */ @ExtendWith(LoanTestLifecycleExtension.class) public class BatchApiTest { @@ -1197,8 +1191,8 @@ public class BatchApiTest { } /** - * Test for the successful create client, creat, approve and get loan. A '200' status code is expected on successful - * responses. + * Test for the successful create client, create, approve and get loan. A '200' status code is expected on + * successful responses. * * @see org.apache.fineract.batch.command.internal.CreateClientCommandStrategy * @see org.apache.fineract.batch.command.internal.ApplyLoanCommandStrategy @@ -2461,10 +2455,11 @@ public class BatchApiTest { final String jsonifiedRepaymentRequest = BatchHelper.toJsonString(List.of(br)); - final List repaymentResponse = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, - this.responseSpec, jsonifiedRepaymentRequest); - - Assertions.assertEquals(HttpStatus.SC_CONFLICT, repaymentResponse.get(0).getStatusCode(), "Verify Status Code 409 for Locked Loan"); + // verify HTTP 409 + ResponseSpecification conflictResponseSpec = new ResponseSpecBuilder().expectStatusCode(409).build(); + ErrorResponse errorResponse = BatchHelper.postBatchRequestsWithoutEnclosingTransactionError(requestSpec, conflictResponseSpec, + jsonifiedRepaymentRequest); + assertEquals(409, parseInt(errorResponse.getHttpStatusCode())); } /** @@ -2542,10 +2537,11 @@ public class BatchApiTest { final String jsonifiedRepaymentRequest = BatchHelper.toJsonString(List.of(br)); - final List repaymentResponse = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, - this.responseSpec, jsonifiedRepaymentRequest); - - Assertions.assertEquals(HttpStatus.SC_CONFLICT, repaymentResponse.get(0).getStatusCode(), "Verify Status Code 409 for Locked Loan"); + // verify HTTP 409 + ResponseSpecification conflictResponseSpec = new ResponseSpecBuilder().expectStatusCode(409).build(); + ErrorResponse errorResponse = BatchHelper.postBatchRequestsWithoutEnclosingTransactionError(requestSpec, conflictResponseSpec, + jsonifiedRepaymentRequest); + assertEquals(409, parseInt(errorResponse.getHttpStatusCode())); } @Test diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java new file mode 100644 index 000000000..3fcc49dd7 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java @@ -0,0 +1,101 @@ +/** + * 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; + +import java.math.BigDecimal; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.client.models.PostLoansLoanIdResponse; +import org.apache.fineract.client.models.PostLoansRequest; +import org.apache.fineract.client.models.PostLoansResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class BatchLoanIntegrationTest extends BaseLoanIntegrationTest { + + @Test + public void test_InlineLoanCOB_ShouldExecute_WhenLoanIsBehind_And_RescheduleIsRequestedViaBatchApi() { + AtomicLong createdLoanId = new AtomicLong(); + + runAt("01 January 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + int numberOfRepayments = 24; + int repaymentEvery = 1; + + // Create Loan Product + PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() // + .numberOfRepayments(numberOfRepayments) // + .repaymentEvery(repaymentEvery) // + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); // + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + Long loanProductId = loanProductResponse.getResourceId(); + + // Apply and Approve Loan + double amount = 1250.0; + + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)// + .repaymentEvery(repaymentEvery)// + .loanTermFrequency(numberOfRepayments)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)// + .loanTermFrequencyType(RepaymentFrequencyType.MONTHS); + + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + + PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), + approveLoanRequest(amount, "01 January 2023")); + + Long loanId = approvedLoanResult.getLoanId(); + + // disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023"); + + createdLoanId.set(loanId); + }); + + runAt("02 January 2023", () -> { + executeInlineCOB(createdLoanId.get()); + }); + + runAt("05 January 2023", () -> { + runAsNonByPass(() -> { + long loanId = createdLoanId.get(); + + List responses = batchRequest() // + .rescheduleLoan(1L, loanId, "01 January 2023", "01 February 2023", "01 March 2023") // + .approveRescheduleLoan(2L, 1L, "01 January 2023") // + .executeEnclosingTransaction(); // + + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(0).getStatusCode(), + "Verify Status Code 200 for Create Reschedule Loan request"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(1).getStatusCode(), + "Verify Status Code 200 for Approve Reschedule Loan request"); + + verifyLastClosedBusinessDate(loanId, "04 January 2023"); + }); + }); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java index ccdbc04fd..d744e74e6 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java @@ -35,6 +35,8 @@ import java.util.stream.Collectors; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.client.util.JSON; +import org.apache.fineract.integrationtests.common.error.ErrorResponse; import org.apache.fineract.integrationtests.common.savings.SavingsTransactionData; import org.junit.jupiter.api.Assertions; import org.slf4j.Logger; @@ -57,6 +59,8 @@ public final class BatchHelper { private static final String BATCH_API_WITHOUT_ENCLOSING_URL_EXT = BATCH_API_URL + "&enclosingTransaction=false"; private static final SecureRandom secureRandom = new SecureRandom(); + private static final Gson GSON = new JSON().getGson(); + private BatchHelper() { } @@ -108,6 +112,14 @@ public final class BatchHelper { return BatchHelper.fromJsonString(response); } + public static ErrorResponse postBatchRequestsWithoutEnclosingTransactionError(final RequestSpecification requestSpec, + final ResponseSpecification responseSpec, final String jsonifiedBatchRequests) { + final String response = Utils.performServerPost(requestSpec, responseSpec, BATCH_API_WITHOUT_ENCLOSING_URL_EXT, + jsonifiedBatchRequests, null); + LOG.info("BatchHelper Response {}", response); + return GSON.fromJson(response, ErrorResponse.class); + } + /** * Returns a list of BatchResponse with query parameter enclosing transaction set to true by posting the jsonified * BatchRequest to the server. diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/error/ErrorResponse.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/error/ErrorResponse.java new file mode 100644 index 000000000..66e546508 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/error/ErrorResponse.java @@ -0,0 +1,27 @@ +/** + * 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.error; + +import lombok.Data; + +@Data +public class ErrorResponse { + + private String httpStatusCode; +} 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 23ea7177f..bdbfa402a 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 @@ -43,8 +43,9 @@ public final class UserHelper { 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; + public static final String SIMPLE_USER_NAME = Utils.uniqueRandomStringGenerator("NotificationUser", 4); + public static final String SIMPLE_USER_PASSWORD = "aA1qwerty56"; + private static boolean SIMPLE_USER_CREATED = false; private UserHelper() {} @@ -141,23 +142,22 @@ public final class UserHelper { public static RequestSpecification getSimpleUserWithoutBypassPermission(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { - String password = "aA1qwerty56"; - if (!simpleUserCreated) { + String password = SIMPLE_USER_PASSWORD; + if (!SIMPLE_USER_CREATED) { GetOfficesResponse headOffice = OfficeHelper.getHeadOffice(requestSpec, responseSpec); - simpleUsername = Utils.uniqueRandomStringGenerator("NotificationUser", 4); String simpleRoleId = createSimpleRole(requestSpec, responseSpec); - PostUsersRequest createUserRequest = new PostUsersRequest().username(simpleUsername) + PostUsersRequest createUserRequest = new PostUsersRequest().username(SIMPLE_USER_NAME) .firstname(Utils.randomStringGenerator("NotificationFN", 4)).lastname(Utils.randomStringGenerator("NotificationLN", 4)) .email("whatever@mifos.org").password(password).repeatPassword(password).sendPasswordToEmail(false) .roles(List.of(Long.valueOf(simpleRoleId))).officeId(headOffice.getId()); PostUsersResponse userCreationResponse = UserHelper.createUser(requestSpec, responseSpec, createUserRequest); Assertions.assertNotNull(userCreationResponse.getResourceId()); - simpleUserCreated = true; + SIMPLE_USER_CREATED = true; } RequestSpecification responseRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); responseRequestSpec.header("Authorization", - "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(simpleUsername, password)); + "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(SIMPLE_USER_NAME, password)); return responseRequestSpec; } @@ -172,6 +172,10 @@ public final class UserHelper { HashMap permissionMap = new HashMap<>(); permissionMap.put(REPAYMENT_LOAN_PERMISSION, true); permissionMap.put(READ_LOAN_PERMISSION, true); + permissionMap.put("READ_RESCHEDULELOAN", true); + permissionMap.put("CREATE_RESCHEDULELOAN", true); + permissionMap.put("REJECT_RESCHEDULELOAN", true); + permissionMap.put("APPROVE_RESCHEDULELOAN", true); RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap); } }