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 2022/06/02 19:39:18 UTC

[fineract] branch develop updated: Filter for instances read, write, batch

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 ebf0e8b53 Filter for instances read, write, batch
ebf0e8b53 is described below

commit ebf0e8b5393c7a632e8baecf474145a3132ce001
Author: Jose Hernandez <jo...@fintecheando.mx>
AuthorDate: Thu Apr 28 12:32:56 2022 -0500

    Filter for instances read, write, batch
---
 .github/workflows/build-mariadb.yml                |   5 +-
 .github/workflows/build-mysql.yml                  |   5 +-
 .github/workflows/build-postgresql.yml             |   5 +-
 build.gradle                                       |   1 +
 .../core/config/FineractProperties.java            |  20 +++
 .../infrastructure/core/config/SecurityConfig.java |  10 +-
 .../core/data/ApiGlobalErrorResponse.java          |  23 ++-
 .../jobs/api/SchedulerJobApiResource.java          |  49 +++--
 .../jobs/service/JobRegisterServiceImpl.java       |   4 +
 .../InvalidInstanceTypeMethodException.java        |  27 +--
 .../filter/FineractInstanceModeApiFilter.java      |  62 +++++++
 instancemode-tests/build.gradle                    | 113 ++++++++++++
 instancemode-tests/dependencies.gradle             |  43 +++++
 .../integrationtests/BatchInstanceModeTest.java    | 199 +++++++++++++++++++++
 .../src/test/resources/cucumber.properties         |  21 +++
 .../src/test/resources/junit-platform.properties   |  20 +++
 .../test/resources/michael.vorburger-crepes.jpg    | Bin 0 -> 2080155 bytes
 .../integrationtests/FixedDepositTest.java         | 124 ++++++-------
 settings.gradle                                    |   1 +
 19 files changed, 624 insertions(+), 108 deletions(-)

diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml
index 21e4d5b5c..ba7408890 100644
--- a/.github/workflows/build-mariadb.yml
+++ b/.github/workflows/build-mariadb.yml
@@ -58,10 +58,13 @@ jobs:
             sudo apt-get install ghostscript graphviz -y
 
       - name: Basic Auth Build & Test
-        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test
+        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test -x :instancemode-tests:test
 
       - name: 2FA Build & Test
         run: ./gradlew --no-daemon -q --console=plain :twofactor-tests:test --fail-fast
 
       - name: OAuth2 Build & Test
         run: ./gradlew --no-daemon -q --console=plain :oauth2-tests:test --fail-fast
+
+      - name: Instance Mode & Test
+        run: ./gradlew --no-daemon -q --console=plain :instancemode-tests:test --fail-fast
diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml
index 609133bfd..44fbcc1e0 100644
--- a/.github/workflows/build-mysql.yml
+++ b/.github/workflows/build-mysql.yml
@@ -58,10 +58,13 @@ jobs:
             sudo apt-get install ghostscript graphviz -y
 
       - name: Basic Auth Build & Test
-        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=mysql
+        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test -x :instancemode-tests:test -PdbType=mysql
 
       - name: 2FA Build & Test
         run: ./gradlew --no-daemon -q --console=plain :twofactor-tests:test --fail-fast -PdbType=mysql
 
       - name: OAuth2 Build & Test
         run: ./gradlew --no-daemon -q --console=plain :oauth2-tests:test --fail-fast -PdbType=mysql
+
+      - name: Instance Mode & Test
+        run: ./gradlew --no-daemon -q --console=plain :instancemode-tests:test --fail-fast -PdbType=mysql
diff --git a/.github/workflows/build-postgresql.yml b/.github/workflows/build-postgresql.yml
index f3f2f44e5..153896d92 100644
--- a/.github/workflows/build-postgresql.yml
+++ b/.github/workflows/build-postgresql.yml
@@ -59,10 +59,13 @@ jobs:
             sudo apt-get install ghostscript graphviz -y
 
       - name: Basic Auth Build & Test
-        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=postgresql
+        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test -x :instancemode-tests:test -PdbType=postgresql
 
       - name: 2FA Build & Test
         run: ./gradlew --no-daemon -q --console=plain :twofactor-tests:test --fail-fast -PdbType=postgresql
 
       - name: OAuth2 Build & Test
         run: ./gradlew --no-daemon -q --console=plain :oauth2-tests:test --fail-fast -PdbType=postgresql
+
+      - name: Instance Mode & Test
+        run: ./gradlew --no-daemon -q --console=plain :instancemode-tests:test --fail-fast -PdbType=postgresql
diff --git a/build.gradle b/build.gradle
index 409e723c2..901cff148 100644
--- a/build.gradle
+++ b/build.gradle
@@ -30,6 +30,7 @@ buildscript {
                 'integration-tests',
                 'twofactor-tests',
                 'oauth2-tests',
+                'instancemode-tests',
                 'fineract-client',
                 'core',
                 'service',
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
index e36e7a107..ecc2beb1a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
@@ -60,5 +60,25 @@ public class FineractProperties {
         public boolean isReadOnlyMode() {
             return readEnabled && !writeEnabled && !batchEnabled;
         }
+
+        public boolean isWriteOnlyMode() {
+            return !readEnabled && writeEnabled && !batchEnabled;
+        }
+
+        public boolean isBatchOnlyMode() {
+            return !readEnabled && !writeEnabled && batchEnabled;
+        }
+
+        public boolean isReadInstance() {
+            return isReadOnlyMode() || readEnabled;
+        }
+
+        public boolean isWriteInstance() {
+            return isWriteOnlyMode() || writeEnabled;
+        }
+
+        public boolean isBatchInstance() {
+            return isBatchOnlyMode() || batchEnabled;
+        }
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
index 13c92e917..b976a36a6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
@@ -19,6 +19,7 @@
 
 package org.apache.fineract.infrastructure.core.config;
 
+import org.apache.fineract.infrastructure.security.filter.FineractInstanceModeApiFilter;
 import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
 import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter;
 import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService;
@@ -53,6 +54,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
     @Autowired
     private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter;
 
+    @Autowired
+    private FineractInstanceModeApiFilter fineractInstanceModeApiFilter;
+
+    @Autowired
+    private FineractProperties fineractProperties;
+
     @Autowired
     private ServerProperties serverProperties;
 
@@ -77,7 +84,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
                 .sessionManagement() //
                 .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //
                 .and() //
-                .addFilterAfter(tenantAwareBasicAuthenticationFilter(), SecurityContextPersistenceFilter.class) //
+                .addFilterAfter(fineractInstanceModeApiFilter, SecurityContextPersistenceFilter.class) //
+                .addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) //
                 .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class); //
 
         if (serverProperties.getSsl().isEnabled()) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java
index 60daaddaa..753fe555b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java
@@ -19,8 +19,10 @@
 package org.apache.fineract.infrastructure.core.data;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.Gson;
 import java.util.ArrayList;
 import java.util.List;
+import javax.ws.rs.core.Response.Status;
 
 /**
  *
@@ -72,7 +74,17 @@ public class ApiGlobalErrorResponse {
         globalErrorResponse.setHttpStatusCode("401");
         globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request.");
         globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier");
-        globalErrorResponse.setDefaultUserMessage("Invalide tenant identifier provided with request.");
+        globalErrorResponse.setDefaultUserMessage("Invalid tenant identifier provided with request.");
+
+        return globalErrorResponse;
+    }
+
+    public static ApiGlobalErrorResponse invalidInstanceTypeMethod(final String method) {
+        final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
+        globalErrorResponse.setHttpStatusCode(Status.METHOD_NOT_ALLOWED.toString());
+        globalErrorResponse.setDeveloperMessage("Invalid instance type called in api request for the method " + method);
+        globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.instance.type");
+        globalErrorResponse.setDefaultUserMessage("Invalid method " + method + " used with request to this instance type.");
 
         return globalErrorResponse;
     }
@@ -186,9 +198,7 @@ public class ApiGlobalErrorResponse {
         return globalErrorResponse;
     }
 
-    protected ApiGlobalErrorResponse() {
-        //
-    }
+    protected ApiGlobalErrorResponse() {}
 
     public ApiGlobalErrorResponse(final List<ApiParameterError> errors) {
         this.errors = errors;
@@ -234,4 +244,9 @@ public class ApiGlobalErrorResponse {
     public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) {
         this.userMessageGlobalisationCode = userMessageGlobalisationCode;
     }
+
+    public String toJson() {
+        final Gson gson = new Gson();
+        return gson.toJson(this);
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
index af2d2c433..972e15fef 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
@@ -39,12 +39,16 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
+import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
 import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
@@ -57,13 +61,13 @@ import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
 import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService;
 import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Path("/jobs")
 @Consumes({ MediaType.APPLICATION_JSON })
 @Produces({ MediaType.APPLICATION_JSON })
 @Component
+@RequiredArgsConstructor
 @Tag(name = "MIFOSX-BATCH JOBS", description = "Batch jobs (also known as cron jobs on Unix-based systems) are a series of back-end jobs executed on a computer at a particular time defined in job's cron expression.\n\n At any point, you can view the list of batch jobs scheduled to run along with other details specific to each job. Manually you can execute the jobs at any point of time.\n\n The scheduler status can be either \"Active\" or \"Standby\". If the scheduler status is Active, it [...]
 public class SchedulerJobApiResource {
 
@@ -74,21 +78,7 @@ public class SchedulerJobApiResource {
     private final ToApiJsonSerializer<JobDetailHistoryData> jobHistoryToApiJsonSerializer;
     private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
     private final PlatformSecurityContext context;
-
-    @Autowired
-    public SchedulerJobApiResource(final SchedulerJobRunnerReadService schedulerJobRunnerReadService,
-            final JobRegisterService jobRegisterService, final ToApiJsonSerializer<JobDetailData> toApiJsonSerializer,
-            final ApiRequestParameterHelper apiRequestParameterHelper,
-            final ToApiJsonSerializer<JobDetailHistoryData> jobHistoryToApiJsonSerializer,
-            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final PlatformSecurityContext context) {
-        this.schedulerJobRunnerReadService = schedulerJobRunnerReadService;
-        this.jobRegisterService = jobRegisterService;
-        this.toApiJsonSerializer = toApiJsonSerializer;
-        this.jobHistoryToApiJsonSerializer = jobHistoryToApiJsonSerializer;
-        this.apiRequestParameterHelper = apiRequestParameterHelper;
-        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
-        this.context = context;
-    }
+    private final FineractProperties fineractProperties;
 
     @GET
     @Operation(summary = "Retrieve Scheduler Jobs", description = "Returns the list of jobs.\n" + "\n" + "Example Requests:\n" + "\n"
@@ -142,17 +132,24 @@ public class SchedulerJobApiResource {
     public Response executeJob(@PathParam(SchedulerJobApiConstants.JOB_ID) @Parameter(description = "jobId") final Long jobId,
             @QueryParam(SchedulerJobApiConstants.COMMAND) @Parameter(description = "command") final String commandParam) {
         // check the logged in user have permissions to execute scheduler jobs
-        final boolean hasNotPermission = this.context.authenticatedUser().hasNotPermissionForAnyOf("ALL_FUNCTIONS", "EXECUTEJOB_SCHEDULER");
-        if (hasNotPermission) {
-            final String authorizationMessage = "User has no authority to execute scheduler jobs";
-            throw new NoAuthorizationException(authorizationMessage);
-        }
-        Response response = Response.status(400).build();
-        if (is(commandParam, SchedulerJobApiConstants.COMMAND_EXECUTE_JOB)) {
-            this.jobRegisterService.executeJob(jobId);
-            response = Response.status(202).build();
+        Response response;
+        if (fineractProperties.getMode().isBatchInstance()) {
+            final boolean hasNotPermission = this.context.authenticatedUser().hasNotPermissionForAnyOf("ALL_FUNCTIONS",
+                    "EXECUTEJOB_SCHEDULER");
+            if (hasNotPermission) {
+                final String authorizationMessage = "User has no authority to execute scheduler jobs";
+                throw new NoAuthorizationException(authorizationMessage);
+            }
+            response = Response.status(400).build();
+            if (is(commandParam, SchedulerJobApiConstants.COMMAND_EXECUTE_JOB)) {
+                this.jobRegisterService.executeJob(jobId);
+                response = Response.status(202).build();
+            } else {
+                throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam);
+            }
         } else {
-            throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam);
+            ApiGlobalErrorResponse errorResponse = ApiGlobalErrorResponse.invalidInstanceTypeMethod("Batch");
+            response = Response.status(Status.METHOD_NOT_ALLOWED).entity(errorResponse).build();
         }
         return response;
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
index cc95ea300..e3b880b0e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
@@ -97,6 +97,10 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
 
     @PostConstruct
     public void loadAllJobs() {
+        // If the instance is not Batch Enabled will not load the Jobs
+        if (!fineractProperties.getMode().isBatchEnabled()) {
+            return;
+        }
         final List<FineractPlatformTenant> allTenants = this.tenantDetailsService.findAllTenants();
         for (final FineractPlatformTenant tenant : allTenants) {
             ThreadLocalContextUtil.setTenant(tenant);
diff --git a/settings.gradle b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidInstanceTypeMethodException.java
similarity index 57%
copy from settings.gradle
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidInstanceTypeMethodException.java
index 4fb8b3b67..a69a0813b 100644
--- a/settings.gradle
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/InvalidInstanceTypeMethodException.java
@@ -16,15 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-rootProject.name='fineract'
-include ':module:dummy:core'
-include ':module:dummy:service'
-include ':module:dummy:starter'
-include ':custom:foo:service'
-include ':fineract-provider'
-include ':fineract-war'
-include ':integration-tests'
-include ':twofactor-tests'
-include ':oauth2-tests'
-include ':fineract-client'
-include ':fineract-doc'
+package org.apache.fineract.infrastructure.security.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link RuntimeException} thrown when an invalid method in the Fineract instance type called in request to platform.
+ *
+ *
+ */
+public class InvalidInstanceTypeMethodException extends AbstractPlatformDomainRuleException {
+
+    public InvalidInstanceTypeMethodException(final String method) {
+        super("error.msg.invalid.method.for.instance.type", "Method Not Allowed " + method + " for the instance type");
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/FineractInstanceModeApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/FineractInstanceModeApiFilter.java
new file mode 100644
index 000000000..c334a66bd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/FineractInstanceModeApiFilter.java
@@ -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.infrastructure.security.filter;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.ext.Provider;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.apache.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@Provider
+@Component
+@RequiredArgsConstructor
+public class FineractInstanceModeApiFilter extends OncePerRequestFilter {
+
+    private final FineractProperties fineractProperties;
+
+    @Override
+    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
+            throws ServletException, IOException {
+
+        if (fineractProperties.getMode().isReadInstance() && isReadMethod(request)) {
+            filterChain.doFilter(request, response);
+        } else if (fineractProperties.getMode().isWriteInstance()) {
+            filterChain.doFilter(request, response);
+        } else if (fineractProperties.getMode().isBatchInstance() && request.getPathInfo().startsWith("/jobs")) {
+            filterChain.doFilter(request, response);
+        } else {
+            response.setStatus(HttpStatus.SC_METHOD_NOT_ALLOWED);
+            ApiGlobalErrorResponse errorResponse = ApiGlobalErrorResponse.invalidInstanceTypeMethod(request.getMethod());
+            response.getWriter().write(errorResponse.toJson());
+        }
+    }
+
+    private boolean isReadMethod(HttpServletRequest request) {
+        return HttpMethod.GET.equals(request.getMethod());
+    }
+}
diff --git a/instancemode-tests/build.gradle b/instancemode-tests/build.gradle
new file mode 100644
index 000000000..3c64c46a2
--- /dev/null
+++ b/instancemode-tests/build.gradle
@@ -0,0 +1,113 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+description = 'Fineract Instance Type Tests'
+
+apply plugin: 'com.bmuschko.cargo'
+
+// Configuration for the Gradle Cargo plugin
+// https://github.com/bmuschko/gradle-cargo-plugin
+configurations {
+    tomcat
+}
+
+apply from: 'dependencies.gradle'
+
+// enable when all tests are migrated
+tasks.cucumber.onlyIf {false}
+
+// Allow external drivers to be used for the tests without packaging it
+// mainly due to license incompatibilities
+configurations {
+    driver
+}
+dependencies {
+    driver 'mysql:mysql-connector-java:8.0.29'
+}
+
+cargo {
+    containerId "tomcat9x"
+
+    // looks like Cargo doesn't detect the WAR file automatically in the multi-module setup
+    deployable {
+        file = file("$rootDir/fineract-war/build/libs/fineract-provider.war")
+        context = 'fineract-provider'
+    }
+
+    local {
+        logLevel = 'high'
+        outputFile = file('build/output.log')
+        installer {
+            installConfiguration = configurations.tomcat
+            downloadDir = file("$buildDir/download")
+            extractDir = file("$buildDir/tomcat")
+        }
+        startStopTimeout = 960000
+        sharedClasspath = configurations.driver
+        containerProperties {
+            def jvmArgs = '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED '
+            if (project.hasProperty('localDebug')) {
+                jvmArgs += ' -agentlib:jdwp=transport=dt_socket,server=y,address=*:9000,suspend=n -Xmx2G -Duser.timezone=Asia/Kolkata '
+            }
+            if (project.hasProperty('dbType')) {
+                if ('postgresql'.equalsIgnoreCase(dbType)) {
+                    jvmArgs += '-Dspring.datasource.hikari.driverClassName=org.postgresql.Driver -Dspring.datasource.hikari.jdbcUrl=jdbc:postgresql://localhost:5432/fineract_tenants -Dspring.datasource.hikari.username=root -Dspring.datasource.hikari.password=postgres -Dfineract.tenant.host=localhost -Dfineract.tenant.port=5432 -Dfineract.tenant.username=root -Dfineract.tenant.password=postgres'
+                } else if ('mysql'.equalsIgnoreCase(dbType)) {
+                    jvmArgs += '-Dspring.datasource.hikari.driverClassName=com.mysql.cj.jdbc.Driver -Dspring.datasource.hikari.jdbcUrl=jdbc:mysql://localhost:3306/fineract_tenants -Dspring.datasource.hikari.username=root -Dspring.datasource.hikari.password=mysql -Dfineract.tenant.host=localhost -Dfineract.tenant.port=3306 -Dfineract.tenant.username=root -Dfineract.tenant.password=mysql'
+                } else {
+                    throw new GradleException('Provided dbType is not supported')
+                }
+            } else {
+                jvmArgs += '-Dspring.datasource.hikari.driverClassName=org.mariadb.jdbc.Driver -Dspring.datasource.hikari.jdbcUrl=jdbc:mariadb://localhost:3306/fineract_tenants -Dspring.datasource.hikari.username=root -Dspring.datasource.hikari.password=mysql -Dfineract.tenant.host=localhost -Dfineract.tenant.port=3306 -Dfineract.tenant.username=root -Dfineract.tenant.password=mysql'
+            }
+            if (project.hasProperty('instanceMode')) {
+                if ('readOnly'.equalsIgnoreCase(instanceMode)) {
+                    jvmArgs += ' -Dfineract.mode.write-enabled=false -Dfineract.mode.batch-enabled=false'
+                } else if ('batchOnly'.equalsIgnoreCase(instanceMode)) {
+                    jvmArgs += ' -Dfineract.mode.write-enabled=false -Dfineract.mode.read-enabled=false'
+                } else {
+                    jvmArgs += ' -Dfineract.mode.read-enabled=false -Dfineract.mode.batch-enabled=false'
+                }
+            } else {
+                jvmArgs += ' -Dfineract.mode.read-enabled=false -Dfineract.mode.batch-enabled=false'
+            }
+
+            property 'cargo.start.jvmargs', jvmArgs
+            property 'cargo.tomcat.connector.keystoreFile', file("$rootDir/fineract-provider/src/main/resources/keystore.jks")
+            property 'cargo.tomcat.connector.keystorePass', 'openmf'
+            property 'cargo.tomcat.httpSecure', true
+            property 'cargo.tomcat.connector.sslProtocol', 'TLS'
+            property 'cargo.tomcat.connector.clientAuth', false
+            property 'cargo.protocol', 'https'
+            property 'cargo.servlet.port', 8443
+        }
+    }
+}
+
+cargoRunLocal.dependsOn ':fineract-war:war'
+cargoStartLocal.dependsOn ':fineract-war:war'
+cargoStartLocal.mustRunAfter 'testClasses'
+
+test {
+    dependsOn (cargoStartLocal)
+    finalizedBy cargoStopLocal
+}
+
+// NOTE: Gradle suggested these dependencies
+compileTestJava.dependsOn(':fineract-provider:generateGitProperties', ':fineract-provider:processResources', ':fineract-provider:resolve')
+spotbugsTest.dependsOn(':fineract-provider:generateGitProperties', ':fineract-provider:processResources', ':fineract-provider:resolve')
diff --git a/instancemode-tests/dependencies.gradle b/instancemode-tests/dependencies.gradle
new file mode 100644
index 000000000..d1852c0e7
--- /dev/null
+++ b/instancemode-tests/dependencies.gradle
@@ -0,0 +1,43 @@
+/**
+ * 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.
+ */
+dependencies {
+    // testCompile dependencies are ONLY used in src/test, not src/main.
+    // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly!
+    //
+    tomcat 'org.apache.tomcat:tomcat:9.0.62@zip'
+    testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"),
+            project(path: ':fineract-provider', configuration: 'runtimeElements'),
+            'io.cucumber:cucumber-spring',
+            'com.intuit.karate:karate-junit5',
+            // 'com.google.code.gson:gson',
+            )
+    testImplementation ('org.mock-server:mockserver-junit-jupiter') {
+        exclude group: 'com.sun.mail', module: 'mailapi'
+        exclude group: 'javax.servlet', module: 'javax.servlet-api'
+        exclude group: 'javax.validation'
+    }
+    testImplementation ('io.rest-assured:rest-assured') {
+        exclude group: 'commons-logging'
+        exclude group: 'org.apache.sling'
+        exclude group: 'com.sun.xml.bind'
+    }
+
+    testCompileOnly 'org.projectlombok:lombok'
+    testAnnotationProcessor 'org.projectlombok:lombok'
+}
diff --git a/instancemode-tests/src/test/java/org/apache/fineract/integrationtests/BatchInstanceModeTest.java b/instancemode-tests/src/test/java/org/apache/fineract/integrationtests/BatchInstanceModeTest.java
new file mode 100644
index 000000000..47f044e46
--- /dev/null
+++ b/instancemode-tests/src/test/java/org/apache/fineract/integrationtests/BatchInstanceModeTest.java
@@ -0,0 +1,199 @@
+/**
+ * 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 static io.restassured.RestAssured.given;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import com.google.gson.Gson;
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.path.json.JsonPath;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpStatus;
+import org.apache.http.conn.HttpHostConnectException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class BatchInstanceModeTest {
+
+    private ResponseSpecification responseSpec200;
+    private ResponseSpecification responseSpec405;
+    private RequestSpecification requestSpec;
+
+    private static final String TENANT_PARAM_NAME = "tenantIdentifier";
+    private static final String DEFAULT_TENANT = "default";
+    private static final String TENANT_IDENTIFIER = TENANT_PARAM_NAME + '=' + DEFAULT_TENANT;
+
+    private static final String HEALTH_URL = "/fineract-provider/actuator/health";
+    private static final String LOGIN_URL = "/fineract-provider/api/v1/authentication?" + TENANT_IDENTIFIER;
+    private static final String JOBS_URL = "/fineract-provider/api/v1/jobs";
+    private static final String OFFICES_URL = "/fineract-provider/api/v1/offices";
+
+    @BeforeEach
+    public void setup() throws InterruptedException {
+        initializeRestAssured();
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+
+        // Login with basic authentication
+        awaitSpringBootActuatorHealthyUp();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.responseSpec200 = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpec405 = new ResponseSpecBuilder().expectStatusCode(405).build();
+    }
+
+    @Test
+    public void acceptWriteRequestWhenIsWriteOnly() {
+        loginIntoServerAndGetBase64EncodedAuthenticationKey(requestSpec, responseSpec200);
+    }
+
+    @Test
+    public void acceptReadRequestWhenIsWriteOnly() {
+        this.requestSpec.header("Authorization",
+                "Basic " + loginIntoServerAndGetBase64EncodedAuthenticationKey(requestSpec, responseSpec200));
+        final int statusCode = getHeadOffice(requestSpec, responseSpec200);
+        assertEquals(HttpStatus.SC_OK, statusCode);
+    }
+
+    @Test
+    public void rejectBatchJobRequestWhenIsWriteOnly() {
+        this.requestSpec.header("Authorization",
+                "Basic " + loginIntoServerAndGetBase64EncodedAuthenticationKey(requestSpec, responseSpec200));
+
+        final String GET_ALL_SCHEDULER_JOBS_URL = JOBS_URL + "?" + TENANT_IDENTIFIER;
+        List<Map<String, Object>> allSchedulerJobsData = getAllSchedulerJobs(requestSpec, responseSpec200, GET_ALL_SCHEDULER_JOBS_URL);
+        assertNotNull(allSchedulerJobsData);
+        final String jobName = "Add Accrual Transactions";
+        final int jobId = this.getSchedulerJobIdByName(allSchedulerJobsData, jobName);
+        final int statusCode = runSchedulerJob(requestSpec, responseSpec405, jobId);
+        assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, statusCode);
+    }
+
+    private static void initializeRestAssured() {
+        RestAssured.baseURI = "https://localhost";
+        RestAssured.port = 8443;
+        RestAssured.keyStore("src/main/resources/keystore.jks", "openmf");
+        RestAssured.useRelaxedHTTPSValidation();
+    }
+
+    private static void awaitSpringBootActuatorHealthyUp() throws InterruptedException {
+        int attempt = 0;
+        final int max_attempts = 10;
+        Response response = null;
+
+        do {
+            try {
+                response = RestAssured.get(HEALTH_URL);
+
+                if (response.statusCode() == 200) {
+                    return;
+                }
+
+                Thread.sleep(3000);
+            } catch (Exception e) {
+                Thread.sleep(3000);
+            }
+        } while (attempt < max_attempts);
+
+        fail(HEALTH_URL + " returned " + response.prettyPrint());
+    }
+
+    private static String loginIntoServerAndGetBase64EncodedAuthenticationKey(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        try {
+            String json = given().spec(requestSpec).body("{\"username\":\"mifos\", \"password\":\"password\"}").expect().spec(responseSpec)
+                    .log().ifError().when().post(LOGIN_URL).asString();
+            assertThat("Failed to login into fineract platform", StringUtils.isBlank(json), is(false));
+            String key = JsonPath.with(json).get("base64EncodedAuthenticationKey");
+            assertThat("Failed to obtain key: " + json, StringUtils.isBlank(key), is(false));
+            return key;
+        } catch (final Exception e) {
+            if (e instanceof HttpHostConnectException) {
+                final HttpHostConnectException hh = (HttpHostConnectException) e;
+                fail("Failed to connect to fineract platform:" + hh.getMessage());
+            }
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    private int getHeadOffice(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
+        return performServerGet(requestSpec, responseSpec, OFFICES_URL + "/1?" + TENANT_IDENTIFIER);
+    }
+
+    private List<Map<String, Object>> getAllSchedulerJobs(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String getURL) {
+        List<Map<String, Object>> response = performServerGet(requestSpec, responseSpec, getURL, "");
+        assertNotNull(response);
+        return response;
+    }
+
+    private int getSchedulerJobIdByName(List<Map<String, Object>> allSchedulerJobsData, String jobName) {
+        for (Integer jobIndex = 0; jobIndex < allSchedulerJobsData.size(); jobIndex++) {
+            if (allSchedulerJobsData.get(jobIndex).get("displayName").equals(jobName)) {
+                return (Integer) allSchedulerJobsData.get(jobIndex).get("jobId");
+            }
+        }
+        throw new IllegalArgumentException(
+                "No such named Job (see org.apache.fineract.infrastructure.jobs.service.JobName enum):" + jobName);
+    }
+
+    private static String runSchedulerJobAsJSON() {
+        final Map<String, String> map = new HashMap<>();
+        String runSchedulerJob = new Gson().toJson(map);
+        return runSchedulerJob;
+    }
+
+    private int runSchedulerJob(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final int jobId) {
+        final String RUN_SCHEDULER_JOB_URL = JOBS_URL + "/" + jobId + "?command=executeJob&" + TENANT_IDENTIFIER;
+        return performServerPost(requestSpec, responseSpec, RUN_SCHEDULER_JOB_URL, runSchedulerJobAsJSON());
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String getURL, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
+        if (jsonAttributeToGetBack == null) {
+            return (T) json;
+        }
+        return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
+    }
+
+    private int performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String getURL) {
+        return given().spec(requestSpec).expect().spec(responseSpec).when().get(getURL).getStatusCode();
+    }
+
+    private int performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String postURL,
+            final String jsonBodyToSend) {
+        return given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).when().post(postURL).getStatusCode();
+    }
+}
diff --git a/instancemode-tests/src/test/resources/cucumber.properties b/instancemode-tests/src/test/resources/cucumber.properties
new file mode 100644
index 000000000..a745f7440
--- /dev/null
+++ b/instancemode-tests/src/test/resources/cucumber.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+cucumber.publish.quiet=true
+cucumber.glue=org.apache.fineract
diff --git a/instancemode-tests/src/test/resources/junit-platform.properties b/instancemode-tests/src/test/resources/junit-platform.properties
new file mode 100644
index 000000000..781baf184
--- /dev/null
+++ b/instancemode-tests/src/test/resources/junit-platform.properties
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$OrderAnnotation
diff --git a/instancemode-tests/src/test/resources/michael.vorburger-crepes.jpg b/instancemode-tests/src/test/resources/michael.vorburger-crepes.jpg
new file mode 100644
index 000000000..c5f4bb6c5
Binary files /dev/null and b/instancemode-tests/src/test/resources/michael.vorburger-crepes.jpg differ
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
index 8e3be1ba4..a0e8c2ffc 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
@@ -1168,7 +1168,7 @@ public class FixedDepositTest {
     }
 
     @Test
-    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_365_Days() {
+    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_360_Days() {
         this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
         this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
 
@@ -1202,6 +1202,10 @@ public class FixedDepositTest {
                 SUBMITTED_ON_DATE, WHOLE_TERM);
         Assertions.assertNotNull(fixedDepositAccountId);
 
+        this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(), fixedDepositProductId.toString(),
+                fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM,
+                INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY);
+
         HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
                 this.responseSpec, fixedDepositAccountId.toString());
         FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
@@ -1234,8 +1238,10 @@ public class FixedDepositTest {
     }
 
     @Test
-    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_360_Days() {
+    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_360() {
         this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
         this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
 
         DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
@@ -1250,12 +1256,14 @@ public class FixedDepositTest {
 
         todaysDate = Calendar.getInstance();
         todaysDate.add(Calendar.MONTH, -1);
-        Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
-        todaysDate.add(Calendar.DATE, -(currentDate - 1));
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
         final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
         final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
-        dateFormat.format(todaysDate.getTime());
+        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
         monthDayFormat.format(todaysDate.getTime());
+        todaysDate.add(Calendar.MONTH, 1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
+        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
 
         Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
         Assertions.assertNotNull(clientId);
@@ -1279,35 +1287,64 @@ public class FixedDepositTest {
         fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
         FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
 
+        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
+        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
+
         HashMap fixedDepositAccountData = FixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
                 fixedDepositAccountId);
         Float principal = (Float) fixedDepositAccountData.get("depositAmount");
-        Float maturityAmount = (Float) fixedDepositAccountData.get("maturityAmount");
         Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
         HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
+        Float preClosurePenalInterestRate = (Float) fixedDepositAccountData.get("preClosurePenalInterest");
         Integer daysInYear = (Integer) daysInYearMap.get("id");
         ArrayList<ArrayList<HashMap>> interestRateChartData = FixedDepositProductHelper
                 .getInterestRateChartSlabsByProductId(this.requestSpec, this.responseSpec, fixedDepositProductId);
+
         Float interestRate = FixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
+        interestRate -= preClosurePenalInterestRate;
         double interestRateInFraction = interestRate / 100;
         double perDay = (double) 1 / daysInYear;
         LOG.info("per day = {}", perDay);
         double interestPerDay = interestRateInFraction * perDay;
 
-        todaysDate.getActualMaximum(Calendar.DATE);
+        todaysDate.add(Calendar.MONTH, -1);
+        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
+        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
+        daysInMonth = daysInMonth - currentDate + 1;
+        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
+        principal += interestPerMonth;
+        todaysDate.add(Calendar.DATE, daysInMonth);
+        LOG.info("{}", monthDayFormat.format(todaysDate.getTime()));
+        interestPerMonth = (float) (interestPerDay * principal * currentDate);
+        LOG.info("IPM = {}", interestPerMonth);
+        principal += interestPerMonth;
+        LOG.info("principal = {}", principal);
 
-        principal = FixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod, interestPerDay,
-                MONTHLY_INTERVAL, MONTHLY_INTERVAL);
+        Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);
+
+        this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId, CLOSED_ON_DATE);
+
+        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
+                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
+        Assertions.assertNotNull(prematureClosureTransactionId);
+
+        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                this.responseSpec, fixedDepositAccountId.toString());
+        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
+
+        fixedDepositAccountData = FixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
+                fixedDepositAccountId);
+
+        Float maturityAmount = (float) fixedDepositAccountData.get("maturityAmount");
+
+        Assertions.assertTrue(Math.abs(principal - maturityAmount) < THRESHOLD, "Verifying Pre-Closure maturity amount");
 
-        LOG.info("{}", principal.toString());
-        Assertions.assertTrue(Math.abs(principal - maturityAmount) < THRESHOLD, "Verifying Maturity amount for Fixed Deposit Account");
     }
 
     @Test
-    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_365() {
+    public void testMaturityAmountForMonthlyCompoundingAndMonthlyPosting_With_365_Days() {
         this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
-        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
-        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
         this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
 
         DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
@@ -1322,14 +1359,12 @@ public class FixedDepositTest {
 
         todaysDate = Calendar.getInstance();
         todaysDate.add(Calendar.MONTH, -1);
-        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
+        Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
+        todaysDate.add(Calendar.DATE, -(currentDate - 1));
         final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
         final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
-        final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
+        dateFormat.format(todaysDate.getTime());
         monthDayFormat.format(todaysDate.getTime());
-        todaysDate.add(Calendar.MONTH, 1);
-        todaysDate.add(Calendar.DAY_OF_MONTH, 1);
-        final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime());
 
         Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
         Assertions.assertNotNull(clientId);
@@ -1349,62 +1384,32 @@ public class FixedDepositTest {
         fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
         FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
 
-        fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
-        FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
-
         HashMap fixedDepositAccountData = FixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
                 fixedDepositAccountId);
         Float principal = (Float) fixedDepositAccountData.get("depositAmount");
+        Float maturityAmount = (Float) fixedDepositAccountData.get("maturityAmount");
         Integer depositPeriod = (Integer) fixedDepositAccountData.get("depositPeriod");
         HashMap daysInYearMap = (HashMap) fixedDepositAccountData.get("interestCalculationDaysInYearType");
-        Float preClosurePenalInterestRate = (Float) fixedDepositAccountData.get("preClosurePenalInterest");
         Integer daysInYear = (Integer) daysInYearMap.get("id");
         ArrayList<ArrayList<HashMap>> interestRateChartData = FixedDepositProductHelper
                 .getInterestRateChartSlabsByProductId(this.requestSpec, this.responseSpec, fixedDepositProductId);
-
         Float interestRate = FixedDepositAccountHelper.getInterestRate(interestRateChartData, depositPeriod);
-        interestRate -= preClosurePenalInterestRate;
         double interestRateInFraction = interestRate / 100;
         double perDay = (double) 1 / daysInYear;
         LOG.info("per day = {}", perDay);
         double interestPerDay = interestRateInFraction * perDay;
 
-        todaysDate.add(Calendar.MONTH, -1);
-        todaysDate.add(Calendar.DAY_OF_MONTH, -1);
-        Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
-        Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE);
-        daysInMonth = daysInMonth - currentDate + 1;
-        Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth);
-        principal += interestPerMonth;
-        todaysDate.add(Calendar.DATE, daysInMonth);
-        LOG.info("{}", monthDayFormat.format(todaysDate.getTime()));
-
-        interestPerMonth = (float) (interestPerDay * principal * currentDate);
-        LOG.info("IPM = {}", interestPerMonth);
-        principal += interestPerMonth;
-        LOG.info("principal = {}", principal);
-
-        this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId, CLOSED_ON_DATE);
-
-        Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
-                fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID);
-        Assertions.assertNotNull(prematureClosureTransactionId);
-
-        fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
-                this.responseSpec, fixedDepositAccountId.toString());
-        FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap);
-
-        fixedDepositAccountData = FixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec,
-                fixedDepositAccountId);
-
-        Float maturityAmount = (float) fixedDepositAccountData.get("maturityAmount");
+        todaysDate.getActualMaximum(Calendar.DATE);
 
-        Assertions.assertTrue(Math.abs(principal - maturityAmount) < THRESHOLD, "Verifying Pre-Closure maturity amount");
+        principal = FixedDepositAccountHelper.getPrincipalAfterCompoundingInterest(todaysDate, principal, depositPeriod, interestPerDay,
+                MONTHLY_INTERVAL, MONTHLY_INTERVAL);
 
+        LOG.info("{}", principal.toString());
+        Assertions.assertTrue(Math.abs(principal - maturityAmount) < THRESHOLD, "Verifying Maturity amount for Fixed Deposit Account");
     }
 
     @Test
-    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_360() {
+    public void testPrematureClosureAmountWithPenalInterestForWholeTerm_With_365() {
         this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
         this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
         this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
@@ -1442,10 +1447,6 @@ public class FixedDepositTest {
                 SUBMITTED_ON_DATE, WHOLE_TERM);
         Assertions.assertNotNull(fixedDepositAccountId);
 
-        this.fixedDepositAccountHelper.updateInterestCalculationConfigForFixedDeposit(clientId.toString(), fixedDepositProductId.toString(),
-                fixedDepositAccountId.toString(), SUBMITTED_ON_DATE, VALID_FROM, VALID_TO, DAYS_360, WHOLE_TERM,
-                INTEREST_CALCULATION_USING_DAILY_BALANCE, MONTHLY, MONTHLY);
-
         HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
                 this.responseSpec, fixedDepositAccountId.toString());
         FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
@@ -1482,13 +1483,12 @@ public class FixedDepositTest {
         principal += interestPerMonth;
         todaysDate.add(Calendar.DATE, daysInMonth);
         LOG.info("{}", monthDayFormat.format(todaysDate.getTime()));
+
         interestPerMonth = (float) (interestPerDay * principal * currentDate);
         LOG.info("IPM = {}", interestPerMonth);
         principal += interestPerMonth;
         LOG.info("principal = {}", principal);
 
-        Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId);
-
         this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId, CLOSED_ON_DATE);
 
         Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit(
diff --git a/settings.gradle b/settings.gradle
index 4fb8b3b67..f187c6b21 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -25,6 +25,7 @@ include ':fineract-provider'
 include ':fineract-war'
 include ':integration-tests'
 include ':twofactor-tests'
+include ':instancemode-tests'
 include ':oauth2-tests'
 include ':fineract-client'
 include ':fineract-doc'