diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml
index 3099d3a79d..03ca21c6ce 100644
--- a/app/server/appsmith-server/pom.xml
+++ b/app/server/appsmith-server/pom.xml
@@ -218,6 +218,11 @@
junit-jupiter-engine
test
+
+ org.awaitility
+ awaitility
+ test
+
org.junit.platform
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java
index 0b1609797e..050f13f918 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java
@@ -22,6 +22,7 @@ import org.springframework.data.convert.SimpleTypeInformationMapper;
import org.springframework.data.convert.TypeInformationMapper;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
+import org.springframework.data.mongodb.ReactiveMongoTransactionManager;
import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
@@ -32,6 +33,8 @@ import org.springframework.data.mongodb.core.convert.MongoTypeMapper;
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
+import org.springframework.transaction.ReactiveTransactionManager;
+import org.springframework.transaction.reactive.TransactionalOperator;
import java.util.Arrays;
import java.util.Collections;
@@ -125,4 +128,13 @@ public class MongoConfig {
return new EncryptionMongoEventListener(encryptionService);
}
+ @Bean
+ public ReactiveTransactionManager reactiveTransactionManager(ReactiveMongoDatabaseFactory factory) {
+ return new ReactiveMongoTransactionManager(factory);
+ }
+
+ @Bean
+ public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
+ return TransactionalOperator.create(transactionManager);
+ }
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCE.java
index 9c75b9c58b..94d5e079af 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCE.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCE.java
@@ -68,4 +68,6 @@ public interface CustomApplicationRepositoryCE extends AppsmithRepository updateFieldByDefaultIdAndBranchName(String defaultId, String defaultIdPath, Map fieldNameValueMap, String branchName, String branchNamePath, AclPermission permission);
+
+ Mono findByNameAndWorkspaceId(String applicationName, String workspaceId, AclPermission permission);
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java
index 81e93a553c..6c0c091e33 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java
@@ -275,4 +275,11 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
return super.updateFieldByDefaultIdAndBranchName(defaultId, defaultIdPath, fieldValueMap, branchName,
branchNamePath, permission);
}
+
+ @Override
+ public Mono findByNameAndWorkspaceId(String applicationName, String workspaceId, AclPermission permission) {
+ Criteria workspaceIdCriteria = where(fieldName(QApplication.application.workspaceId)).is(workspaceId);
+ Criteria applicationNameCriteria = where(fieldName(QApplication.application.name)).is(applicationName);
+ return queryOne(List.of(workspaceIdCriteria, applicationNameCriteria), permission);
+ }
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GitServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GitServiceImpl.java
index 880e63f6d8..1712d53616 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GitServiceImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GitServiceImpl.java
@@ -14,7 +14,6 @@ import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.ImportExportApplicationService;
import com.appsmith.server.solutions.PagePermission;
-import io.sentry.protocol.App;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java
index d290edb877..8ec15474c9 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java
@@ -53,7 +53,6 @@ import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
-import org.springframework.dao.DuplicateKeyException;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -71,6 +70,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties;
+import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
import static org.apache.commons.lang.ObjectUtils.defaultIfNull;
@@ -1175,22 +1175,25 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
Mono userMono = sessionUserService.getCurrentUser().cache();
Mono applicationWithPoliciesMono = this.setApplicationPolicies(userMono, application.getWorkspaceId(), application);
+ Mono applicationMono = applicationService.findByNameAndWorkspaceId(actualName, application.getWorkspaceId(), MANAGE_APPLICATIONS);
- return applicationWithPoliciesMono
- .zipWith(userMono)
- .flatMap(tuple -> {
- Application application1 = tuple.getT1();
- application1.setModifiedBy(tuple.getT2().getUsername()); // setting modified by to current user
- // We can't use create or createApplication method here as we are expecting update operation if the
- // _id is available with application object
- return applicationService.save(application);
- })
- .onErrorResume(DuplicateKeyException.class, error -> {
- if (error.getMessage() != null) {
- return this.createOrUpdateSuffixedApplication(application, name, 1 + suffix);
- }
- throw error;
- });
+ // We are taking pessimistic approach as this flow is used in import application where we are using transactions
+ // which creates problem if we hit duplicate key exception
+ return applicationMono
+ .flatMap(application1 ->
+ this.createOrUpdateSuffixedApplication(application, name, 1 + suffix)
+ )
+ .switchIfEmpty(Mono.defer(() ->
+ applicationWithPoliciesMono
+ .zipWith(userMono)
+ .flatMap(tuple -> {
+ Application application1 = tuple.getT1();
+ application1.setModifiedBy(tuple.getT2().getUsername()); // setting modified by to current user
+ // We can't use create or createApplication method here as we are expecting update operation if the
+ // _id is available with application object
+ return applicationService.save(application);
+ })
+ ));
}
/**
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java
index 5fe1c5dfb1..3972c0a8e6 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java
@@ -78,7 +78,6 @@ public interface ApplicationServiceCE extends CrudService {
String defaultApplicationId,
String fieldName,
AclPermission aclPermission);
-
Mono findBranchedApplicationId(String branchName, String defaultApplicationId, AclPermission permission);
Flux findAllApplicationsByDefaultApplicationId(String defaultApplicationId, AclPermission permission);
@@ -99,4 +98,5 @@ public interface ApplicationServiceCE extends CrudService {
public Mono deleteAppNavigationLogo(String branchName, String applicationId);
+ Mono findByNameAndWorkspaceId(String applicationName, String workspaceId, AclPermission permission);
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java
index b1561e09e8..1beb380817 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java
@@ -813,7 +813,7 @@ public class ApplicationServiceCEImpl extends BaseService updateMono = this.update(applicationId, branchedApplication, branchName);
- if (!StringUtils.hasLength(oldAssetId)){
+ if (!StringUtils.hasLength(oldAssetId)) {
return updateMono;
} else {
return assetService.remove(oldAssetId).then(updateMono);
@@ -822,7 +822,12 @@ public class ApplicationServiceCEImpl extends BaseService findByNameAndWorkspaceId(String applicationName, String workspaceId, AclPermission permission) {
+ return repository.findByNameAndWorkspaceId(applicationName, workspaceId, permission);
}
@Override
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java
index 611f794fdf..d2a85c74aa 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java
@@ -22,6 +22,7 @@ import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.reactive.TransactionalOperator;
@Slf4j
@Component
@@ -51,12 +52,13 @@ public class ImportExportApplicationServiceImpl extends ImportExportApplicationS
ApplicationPermission applicationPermission,
PagePermission pagePermission,
ActionPermission actionPermission,
- Gson gson) {
+ Gson gson,
+ TransactionalOperator transactionalOperator) {
super(datasourceService, sessionUserService, newActionRepository, datasourceRepository, pluginRepository,
workspaceService, applicationService, newPageService, applicationPageService, newPageRepository,
newActionService, sequenceService, examplesWorkspaceCloner, actionCollectionRepository,
actionCollectionService, themeService, analyticsService, customJSLibService, datasourcePermission,
- workspacePermission, applicationPermission, pagePermission, actionPermission, gson);
+ workspacePermission, applicationPermission, pagePermission, actionPermission, gson, transactionalOperator);
}
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java
index 48b968e8d6..0ea598ca09 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java
@@ -22,6 +22,8 @@ import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.reactive.TransactionalOperator;
@Slf4j
@Component
@@ -51,12 +53,13 @@ public class ImportExportApplicationServiceImplV2 extends ImportExportApplicatio
ApplicationPermission applicationPermission,
PagePermission pagePermission,
ActionPermission actionPermission,
- Gson gson) {
+ Gson gson,
+ TransactionalOperator transactionalOperator) {
super(datasourceService, sessionUserService, newActionRepository, datasourceRepository, pluginRepository,
workspaceService, applicationService, newPageService, applicationPageService, newPageRepository,
newActionService, sequenceService, examplesWorkspaceCloner, actionCollectionRepository,
actionCollectionService, themeService, analyticsService, customJSLibService, datasourcePermission,
- workspacePermission, applicationPermission, pagePermission, actionPermission, gson);
+ workspacePermission, applicationPermission, pagePermission, actionPermission, gson, transactionalOperator);
}
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java
index 5f2be404b0..5d51b71d3f 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java
@@ -23,6 +23,7 @@ import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.CustomJSLib;
import com.appsmith.server.domains.GitApplicationMetadata;
+import com.appsmith.server.domains.GitApplicationMetadata;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
@@ -78,6 +79,7 @@ import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.Part;
+import org.springframework.transaction.reactive.TransactionalOperator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -133,6 +135,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
private final PagePermission pagePermission;
private final ActionPermission actionPermission;
private final Gson gson;
+ private final TransactionalOperator transactionalOperator;
/**
* This function will give the application resource to rebuild the application in import application flow
@@ -1202,12 +1205,9 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
})
.onErrorResume(throwable -> {
log.error("Error while importing the application ", throwable.getMessage());
- if (importedApplication.getId() != null && applicationId == null) {
- return applicationPageService.deleteApplication(importedApplication.getId())
- .then(Mono.error(new AppsmithException(AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, throwable.getMessage())));
- }
- return Mono.error(new AppsmithException(AppsmithError.UNKNOWN_PLUGIN_REFERENCE));
- });
+ return Mono.error(new AppsmithException(AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, ""));
+ })
+ .as(transactionalOperator::transactional);
// Import Application is currently a slow API because it needs to import and create application, pages, actions
// and action collection. This process may take time and the client may cancel the request. This leads to the flow
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java
index 68193a6691..02895291a0 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java
@@ -78,6 +78,8 @@ import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.Part;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.reactive.TransactionalOperator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -132,6 +134,7 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli
private final PagePermission pagePermission;
private final ActionPermission actionPermission;
private final Gson gson;
+ private final TransactionalOperator transactionalOperator;
private static final Set ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON);
private static final String INVALID_JSON_FILE = "invalid json file";
@@ -1231,12 +1234,9 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli
})
.onErrorResume(throwable -> {
log.error("Error while importing the application ", throwable.getMessage());
- if (importedApplication.getId() != null && applicationId == null) {
- return applicationPageService.deleteApplication(importedApplication.getId())
- .then(Mono.error(new AppsmithException(AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, throwable.getMessage())));
- }
return Mono.error(new AppsmithException(AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, ""));
- });
+ })
+ .as(transactionalOperator::transactional);
// Import Application is currently a slow API because it needs to import and create application, pages, actions
// and action collection. This process may take time and the client may cancel the request. This leads to the flow
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/TransactionalConfig.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/TransactionalConfig.java
new file mode 100644
index 0000000000..f7505a3366
--- /dev/null
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/TransactionalConfig.java
@@ -0,0 +1,17 @@
+package com.appsmith.server.configurations;
+
+import de.flapdoodle.embed.mongo.commands.MongodArguments;
+import de.flapdoodle.embed.mongo.config.Storage;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class TransactionalConfig {
+
+ @Bean
+ MongodArguments mongodArguments() {
+ return MongodArguments.builder()
+ .replication(Storage.of("appsmith-replica-set", 10))
+ .build();
+ }
+}
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java
index e17371e4a3..fc4f7c5026 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java
@@ -2901,14 +2901,13 @@ public class ImportExportApplicationServiceTests {
assert appJson != null;
final String randomId = UUID.randomUUID().toString();
appJson.getDatasourceList().get(0).setPluginId(randomId);
- final Mono resultMono = workspaceService
- .create(newWorkspace)
- .flatMap(workspace -> importExportApplicationService.importApplicationInWorkspace(workspace.getId(), appJson));
+ Workspace createdWorkspace = workspaceService.create(newWorkspace).block();
+ final Mono resultMono = importExportApplicationService.importApplicationInWorkspace(createdWorkspace.getId(), appJson);
StepVerifier
.create(resultMono)
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
- throwable.getMessage().equals(AppsmithError.UNKNOWN_PLUGIN_REFERENCE.getMessage(randomId)))
+ throwable.getMessage().equals(AppsmithError.GENERIC_JSON_IMPORT_ERROR.getMessage(createdWorkspace.getId(), "")))
.verify();
}
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/transactions/ImportApplicationTransactionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/transactions/ImportApplicationTransactionServiceTest.java
new file mode 100644
index 0000000000..47a4fce365
--- /dev/null
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/transactions/ImportApplicationTransactionServiceTest.java
@@ -0,0 +1,162 @@
+package com.appsmith.server.transactions;
+
+import com.appsmith.server.domains.ActionCollection;
+import com.appsmith.server.domains.Application;
+import com.appsmith.server.domains.NewAction;
+import com.appsmith.server.domains.NewPage;
+import com.appsmith.server.domains.Workspace;
+import com.appsmith.server.dtos.ApplicationJson;
+import com.appsmith.server.exceptions.AppsmithError;
+import com.appsmith.server.exceptions.AppsmithException;
+import com.appsmith.server.helpers.MockPluginExecutor;
+import com.appsmith.server.helpers.PluginExecutorHelper;
+import com.appsmith.server.migrations.JsonSchemaMigration;
+import com.appsmith.server.repositories.ActionCollectionRepository;
+import com.appsmith.server.repositories.NewActionRepository;
+import com.appsmith.server.services.ActionCollectionService;
+import com.appsmith.server.services.NewActionService;
+import com.appsmith.server.services.WorkspaceService;
+import com.appsmith.server.solutions.ImportExportApplicationService;
+import com.google.gson.Gson;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.http.MediaType;
+import org.springframework.http.codec.multipart.FilePart;
+import org.springframework.security.test.context.support.WithUserDetails;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+// All the test case are for failure or exception. Test cases for valid json file is already present in ImportExportApplicationServiceTest class
+
+@AutoConfigureDataMongo
+@SpringBootTest(
+ properties = "de.flapdoodle.mongodb.embedded.version=5.0.5"
+)
+@EnableAutoConfiguration()
+@TestPropertySource(properties = "property=C")
+@DirtiesContext
+public class ImportApplicationTransactionServiceTest {
+
+ @Autowired
+ @Qualifier("importExportServiceCEImplV2")
+ ImportExportApplicationService importExportApplicationService;
+
+ @Autowired
+ WorkspaceService workspaceService;
+
+ @Autowired
+ MongoTemplate mongoTemplate;
+
+ @MockBean
+ NewActionService newActionService;
+
+ @MockBean
+ NewActionRepository newActionRepository;
+
+ @MockBean
+ ActionCollectionService actionCollectionService;
+
+ @MockBean
+ ActionCollectionRepository actionCollectionRepository;
+
+ @MockBean
+ PluginExecutorHelper pluginExecutorHelper;
+
+ private ApplicationJson applicationJson = new ApplicationJson();
+
+ Long applicationCount = 0L, pageCount = 0L, actionCount = 0L, actionCollectionCount = 0L;
+
+ @BeforeEach
+ public void setup() {
+ Mockito
+ .when(pluginExecutorHelper.getPluginExecutor(Mockito.any()))
+ .thenReturn(Mono.just(new MockPluginExecutor()));
+
+ applicationJson = createAppJson("test_assets/ImportExportServiceTest/valid-application.json").block();
+ applicationCount = mongoTemplate.count(new Query(), Application.class);
+ pageCount = mongoTemplate.count(new Query(), NewPage.class);
+ actionCount = mongoTemplate.count(new Query(), NewAction.class);
+ actionCollectionCount = mongoTemplate.count(new Query(), ActionCollection.class);
+ }
+
+
+ private FilePart createFilePart(String filePath) {
+ FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS);
+ Flux dataBufferFlux = DataBufferUtils
+ .read(
+ new ClassPathResource(filePath),
+ new DefaultDataBufferFactory(),
+ 4096)
+ .cache();
+
+ Mockito.when(filepart.content()).thenReturn(dataBufferFlux);
+ Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.APPLICATION_JSON);
+
+ return filepart;
+
+ }
+
+ private Mono createAppJson(String filePath) {
+ FilePart filePart = createFilePart(filePath);
+
+ Mono stringifiedFile = DataBufferUtils.join(filePart.content())
+ .map(dataBuffer -> {
+ byte[] data = new byte[dataBuffer.readableByteCount()];
+ dataBuffer.read(data);
+ DataBufferUtils.release(dataBuffer);
+ return new String(data);
+ });
+
+ return stringifiedFile
+ .map(data -> {
+ Gson gson = new Gson();
+ return gson.fromJson(data, ApplicationJson.class);
+ })
+ .map(JsonSchemaMigration::migrateApplicationToLatestSchema);
+ }
+
+ @Test
+ @WithUserDetails(value = "api_user")
+ public void importApplication_exceptionDuringActionSave_savedPagesAndApplicationReverted() {
+
+ Workspace newWorkspace = new Workspace();
+ newWorkspace.setName("Template Workspace");
+
+ Mockito.when(newActionService.save(Mockito.any()))
+ .thenThrow(new AppsmithException(AppsmithError.GENERIC_BAD_REQUEST));
+
+ Workspace createdWorkspace = workspaceService.create(newWorkspace).block();
+
+ Mono resultMono = importExportApplicationService.importApplicationInWorkspace(createdWorkspace.getId(), applicationJson);
+
+ // Check if expected exception is thrown
+ StepVerifier
+ .create(resultMono)
+ .expectErrorMatches(error -> error instanceof AppsmithException && error.getMessage().equals(AppsmithError.GENERIC_JSON_IMPORT_ERROR.getMessage(createdWorkspace.getId(), "")))
+ .verify();
+
+ // After the import application failed in the middle of execution after the application and pages are saved to DB
+ // check if the saved pages reverted after the exception
+ assertThat(mongoTemplate.count(new Query(), Application.class)).isEqualTo(applicationCount);
+ assertThat(mongoTemplate.count(new Query(), NewPage.class)).isEqualTo(pageCount);
+ assertThat(mongoTemplate.count(new Query(), NewAction.class)).isEqualTo(actionCount);
+ }
+}
\ No newline at end of file
diff --git a/app/server/appsmith-server/src/test/resources/application-test.properties b/app/server/appsmith-server/src/test/resources/application-test.properties
index 6aea70a3e6..0777b1f8d6 100644
--- a/app/server/appsmith-server/src/test/resources/application-test.properties
+++ b/app/server/appsmith-server/src/test/resources/application-test.properties
@@ -1,2 +1,2 @@
# embedded mongo DB version which is used during junit tests
-de.flapdoodle.mongodb.embedded.version=5.0.14
\ No newline at end of file
+de.flapdoodle.mongodb.embedded.version=5.0.5
\ No newline at end of file
diff --git a/app/server/envs/dev.env.example b/app/server/envs/dev.env.example
index 938cedbb92..0619bcb8fc 100644
--- a/app/server/envs/dev.env.example
+++ b/app/server/envs/dev.env.example
@@ -1,6 +1,6 @@
#!/bin/sh
-APPSMITH_MONGODB_URI="mongodb://localhost:27017/appsmith"
+APPSMITH_MONGODB_URI="mongodb://localhost:27017/appsmith?replicaSet=appsmith-replica-set"
APPSMITH_REDIS_URL="redis://127.0.0.1:6379"