diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Constraint.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Constraint.java index 7451c06ff3..aa95588255 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Constraint.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Constraint.java @@ -3,4 +3,5 @@ package com.appsmith.server.constants; public class Constraint { public static final int WORKSPACE_LOGO_SIZE_KB = 250; public static final int THUMBNAIL_PHOTO_DIMENSION = 128; + public static final int MAX_LOGO_SIZE_KB = 1024; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java index f2f56f1fd4..4261d78961 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java @@ -130,7 +130,7 @@ public class ApplicationControllerCE extends BaseController> getReleaseItemsInformation() { log.debug("Going to get version release items"); return applicationFetcher.getReleaseItems() - .map(applications -> new ResponseDTO<>(HttpStatus.OK.value(), applications, null)); + .map(applications -> new ResponseDTO<>(HttpStatus.OK.value(), applications, null)); } @PutMapping("/{defaultApplicationId}/changeAccess") @@ -222,11 +222,28 @@ public class ApplicationControllerCE extends BaseController new ResponseDTO<>(HttpStatus.OK.value(), result, null)); } + @PostMapping(value = "/{defaultApplicationId}/logo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Mono> uploadAppNavigationLogo(@PathVariable String defaultApplicationId, + @RequestPart("file") Mono fileMono, + @RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) { + return fileMono + .flatMap(part -> service.saveAppNavigationLogo(branchName, defaultApplicationId, part)) + .map(url -> new ResponseDTO<>(HttpStatus.OK.value(), url, null)); + } + + @DeleteMapping("/{defaultApplicationId}/logo") + public Mono> deleteAppNavigationLogo(@PathVariable String defaultApplicationId, + @RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName){ + return service.deleteAppNavigationLogo(branchName, defaultApplicationId) + .map(ignored -> new ResponseDTO<>(HttpStatus.OK.value(), null, null)); + } + + // !! This API endpoint should not be exposed !! @Override @GetMapping("") public Mono>> getAll(@RequestParam MultiValueMap params, - @RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) { + @RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) { return Mono.just( new ResponseDTO<>(HttpStatus.BAD_REQUEST.value(), null, AppsmithError.UNSUPPORTED_OPERATION.getMessage()) ); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java index cec7674292..be1d35cf34 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java @@ -34,13 +34,14 @@ public class ApplicationServiceImpl extends ApplicationServiceCEImpl implements ResponseUtils responseUtils, PermissionGroupService permissionGroupService, TenantService tenantService, + AssetService assetService, UserRepository userRepository, DatasourcePermission datasourcePermission, ApplicationPermission applicationPermission) { super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService, policyUtils, - configService, commentThreadRepository, sessionUserService, responseUtils, permissionGroupService, tenantService, userRepository, - datasourcePermission, applicationPermission); + configService, commentThreadRepository, sessionUserService, responseUtils, permissionGroupService, tenantService, assetService, + userRepository, datasourcePermission, applicationPermission); } } 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 d995f0005b..5fe1c5dfb1 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 @@ -10,6 +10,7 @@ import com.appsmith.server.dtos.ApplicationAccessDTO; import com.appsmith.server.dtos.GitAuthDTO; import com.appsmith.server.services.CrudService; import com.mongodb.client.result.UpdateResult; +import org.springframework.http.codec.multipart.Part; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -93,4 +94,9 @@ public interface ApplicationServiceCE extends CrudService { Mono getApplicationByDefaultApplicationIdAndDefaultBranch(String defaultApplicationId); Mono findByIdAndExportWithConfiguration(String applicationId, Boolean exportWithConfiguration); + + Mono saveAppNavigationLogo(String branchName, String applicationId, Part filePart); + + public Mono deleteAppNavigationLogo(String branchName, String applicationId); + } 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 14540870d2..b1561e09e8 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 @@ -12,6 +12,7 @@ import com.appsmith.server.domains.Action; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationMode; +import com.appsmith.server.domains.Asset; import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.NewAction; @@ -34,6 +35,7 @@ import com.appsmith.server.repositories.ApplicationRepository; import com.appsmith.server.repositories.CommentThreadRepository; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.AssetService; import com.appsmith.server.services.BaseService; import com.appsmith.server.services.ConfigService; import com.appsmith.server.services.PermissionGroupService; @@ -48,6 +50,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.http.codec.multipart.Part; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; @@ -64,8 +67,8 @@ import java.util.Set; import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS; +import static com.appsmith.server.constants.Constraint.MAX_LOGO_SIZE_KB; import static org.apache.commons.lang3.StringUtils.isBlank; - @Slf4j public class ApplicationServiceCEImpl extends BaseService implements ApplicationServiceCE { @@ -79,6 +82,8 @@ public class ApplicationServiceCEImpl extends BaseService findByIdAndExportWithConfiguration(String applicationId, Boolean exportWithConfiguration) { return repository.findByIdAndExportWithConfiguration(applicationId, exportWithConfiguration); } + + @Override + public Mono saveAppNavigationLogo(String branchName, String applicationId, Part filePart) { + return this.findByBranchNameAndDefaultApplicationId(branchName, applicationId, applicationPermission.getEditPermission()) + .flatMap(branchedApplication -> { + + Application.NavigationSetting rootAppUnpublishedNavigationSetting = ObjectUtils.defaultIfNull( + branchedApplication.getUnpublishedNavigationSetting(), + new Application.NavigationSetting() + ); + + String rootAppLogoAssetId = ObjectUtils.defaultIfNull( + rootAppUnpublishedNavigationSetting.getLogoAssetId(), + "" + ); + + final Mono prevAssetIdMono = Mono.just(rootAppLogoAssetId); + + final Mono uploaderMono = assetService.upload(List.of(filePart), MAX_LOGO_SIZE_KB, true); + + return Mono.zip(prevAssetIdMono, uploaderMono) + .flatMap(tuple -> { + final String oldAssetId = tuple.getT1(); + final Asset uploadedAsset = tuple.getT2(); + Application.NavigationSetting navSetting = ObjectUtils.defaultIfNull( + branchedApplication.getUnpublishedNavigationSetting(), + new Application.NavigationSetting()); + navSetting.setLogoAssetId(uploadedAsset.getId()); + branchedApplication.setUnpublishedNavigationSetting(navSetting); + + final Mono updateMono = this.update(applicationId, branchedApplication, branchName); + + if (!StringUtils.hasLength(oldAssetId)){ + return updateMono; + } else { + return assetService.remove(oldAssetId).then(updateMono); + } + + }); + + }); + + } + + @Override + public Mono deleteAppNavigationLogo(String branchName, String applicationId){ + return this.findByBranchNameAndDefaultApplicationId(branchName, applicationId, applicationPermission.getEditPermission()) + .flatMap(branchedApplication -> { + + Application.NavigationSetting unpublishedNavSetting = ObjectUtils.defaultIfNull(branchedApplication.getUnpublishedNavigationSetting(), new Application.NavigationSetting()); + + String navLogoAssetId = ObjectUtils.defaultIfNull(unpublishedNavSetting.getLogoAssetId(), ""); + + unpublishedNavSetting.setLogoAssetId(null); + branchedApplication.setUnpublishedNavigationSetting(unpublishedNavSetting); + return repository.save(branchedApplication).thenReturn(navLogoAssetId); + }) + .flatMap(assetService::remove); + } } + diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java index 2eac5191b9..7a580dba80 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java @@ -2199,6 +2199,218 @@ public class GitServiceTest { .verifyComplete(); } + private void mockitoSetUp(GitBranchDTO createGitBranchDTO) throws GitAPIException, IOException{ + Mockito.when(gitExecutor.checkoutToBranch(Mockito.any(Path.class), Mockito.anyString())) + .thenReturn(Mono.just(true)); + Mockito.when(gitExecutor.fetchRemote(Mockito.any(Path.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(Mono.just("fetchResult")); + Mockito.when(gitExecutor.listBranches(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(Mono.just(new ArrayList<>())); + Mockito.when(gitExecutor.createAndCheckoutToBranch(Mockito.any(), Mockito.any())) + .thenReturn(Mono.just(createGitBranchDTO.getBranchName())); + Mockito.when(gitFileUtils.saveApplicationToLocalRepo(Mockito.any(Path.class), Mockito.any(ApplicationJson.class), Mockito.anyString())) + .thenReturn(Mono.just(Paths.get(""))); + Mockito.when(gitExecutor.commitApplication(Mockito.any(Path.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyBoolean())) + .thenReturn(Mono.just("System generated commit")); + Mockito.when(gitExecutor.checkoutToBranch(Mockito.any(Path.class), Mockito.anyString())) + .thenReturn(Mono.just(true)); + Mockito.when(gitExecutor.pushApplication(Mockito.any(Path.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(Mono.just("pushed successfully")); + + Mockito.when(gitExecutor.cloneApplication(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(Mono.just(DEFAULT_BRANCH)); + Mockito.when(gitFileUtils.checkIfDirectoryIsEmpty(Mockito.any(Path.class))).thenReturn(Mono.just(true)); + Mockito.when(gitFileUtils.initializeReadme(Mockito.any(Path.class), Mockito.anyString(), Mockito.anyString())) + .thenReturn(Mono.just(Paths.get("textPath"))); + + } + + @Test + @WithUserDetails(value = "api_user") + public void createBranch_BranchHasCustomNavigationSettings_SrcBranchRemainsUnchanged() throws GitAPIException, IOException { + GitBranchDTO createGitBranchDTO = new GitBranchDTO(); + createGitBranchDTO.setBranchName("valid_branch"); + + GitConnectDTO gitConnectDTO = getConnectRequest("git@github.com:test/testRepo.git", testUserProfile); + mockitoSetUp(createGitBranchDTO); + + Application testApplication = new Application(); + GitApplicationMetadata gitApplicationMetadata = new GitApplicationMetadata(); + GitAuth gitAuth = new GitAuth(); + gitAuth.setPublicKey("testkey"); + gitAuth.setPrivateKey("privatekey"); + gitApplicationMetadata.setGitAuth(gitAuth); + testApplication.setGitApplicationMetadata(gitApplicationMetadata); + testApplication.setName("Test App" + UUID.randomUUID().toString()); + testApplication.setWorkspaceId(workspaceId); + + Mono> createBranchMono = applicationPageService.createApplication(testApplication) + .flatMap(application -> gitService.connectApplicationToGit(application.getId(), gitConnectDTO, "origin")) + .flatMap(application -> gitService.createBranch( + application.getId(), createGitBranchDTO, application.getGitApplicationMetadata().getBranchName()) + .then(applicationService.findByBranchNameAndDefaultApplicationId( + createGitBranchDTO.getBranchName(), + application.getId(), + READ_APPLICATIONS + )) + + + ) + .flatMap(branchedApplication -> { + + Application.NavigationSetting appNavigationSetting = new Application.NavigationSetting(); + appNavigationSetting.setOrientation("top"); + branchedApplication.setNavigationSetting(appNavigationSetting); + return Mono.just(branchedApplication); + }) + .flatMap(branchedApplication-> + applicationService.update( + branchedApplication.getGitApplicationMetadata().getDefaultApplicationId(), + branchedApplication, branchedApplication.getGitApplicationMetadata().getBranchName() + ) + ) + .zipWhen(application -> + applicationService.findById(application.getGitApplicationMetadata().getDefaultApplicationId()) + ); + + StepVerifier + .create(createBranchMono) + .assertNext(tuple -> { + Application branchedApp = tuple.getT1(); + Application srcApp = tuple.getT2(); + assertThat(branchedApp.getUnpublishedNavigationSetting().getOrientation()).isEqualTo("top"); + assertThat(srcApp.getUnpublishedNavigationSetting()).isNull(); + }) + .verifyComplete(); + } + + private FilePart createMockFilePart() { + FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); + Flux dataBufferFlux = DataBufferUtils + .read(new ClassPathResource("test_assets/WorkspaceServiceTest/my_workspace_logo.png"), new DefaultDataBufferFactory(), 4096).cache(); + Mockito.when(filepart.content()).thenReturn(dataBufferFlux); + Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.IMAGE_PNG); + return filepart; + } + + @Test + @WithUserDetails(value = "api_user") + public void createBranch_BranchUploadLogo_SrcBranchRemainsUnchanged() throws GitAPIException, IOException { + GitBranchDTO createGitBranchDTO = new GitBranchDTO(); + createGitBranchDTO.setBranchName("valid_branch"); + + GitConnectDTO gitConnectDTO = getConnectRequest("git@github.com:test/testRepo.git", testUserProfile); + mockitoSetUp(createGitBranchDTO); + + Application testApplication = new Application(); + GitApplicationMetadata gitApplicationMetadata = new GitApplicationMetadata(); + GitAuth gitAuth = new GitAuth(); + gitAuth.setPublicKey("testkey"); + gitAuth.setPrivateKey("privatekey"); + gitApplicationMetadata.setGitAuth(gitAuth); + testApplication.setGitApplicationMetadata(gitApplicationMetadata); + testApplication.setName("Test App" + UUID.randomUUID().toString()); + testApplication.setWorkspaceId(workspaceId); + + Mono> createBranchMono = applicationPageService.createApplication(testApplication) + .flatMap(application -> gitService.connectApplicationToGit(application.getId(), gitConnectDTO, "origin")) + .flatMap(application -> gitService.createBranch( + application.getId(), createGitBranchDTO, application.getGitApplicationMetadata().getBranchName()) + .then(applicationService.findByBranchNameAndDefaultApplicationId( + createGitBranchDTO.getBranchName(), + application.getId(), + READ_APPLICATIONS + )) + ) + .flatMap(branchedApplication -> { + + FilePart filepart = createMockFilePart(); + return applicationService.saveAppNavigationLogo(branchedApplication.getGitApplicationMetadata().getBranchName(), branchedApplication.getGitApplicationMetadata().getDefaultApplicationId(), filepart).cache(); + + }) + .zipWhen(application -> + applicationService.findById(application.getGitApplicationMetadata().getDefaultApplicationId()) + ); + + StepVerifier + .create(createBranchMono) + .assertNext(tuple -> { + Application branchedApp = tuple.getT1(); + Application srcApp = tuple.getT2(); + assertThat(branchedApp.getUnpublishedNavigationSetting()).isNotNull(); + assertThat(branchedApp.getUnpublishedNavigationSetting().getLogoAssetId()).isNotNull(); + assertThat(srcApp.getUnpublishedNavigationSetting()).isNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createBranch_BranchDeleteLogo_SrcLogoRemainsUnchanged() throws GitAPIException, IOException { + GitBranchDTO createGitBranchDTO = new GitBranchDTO(); + createGitBranchDTO.setBranchName("valid_branch"); + + GitConnectDTO gitConnectDTO = getConnectRequest("git@github.com:test/testRepo.git", testUserProfile); + mockitoSetUp(createGitBranchDTO); + + Application testApplication = new Application(); + GitApplicationMetadata gitApplicationMetadata = new GitApplicationMetadata(); + GitAuth gitAuth = new GitAuth(); + gitAuth.setPublicKey("testkey"); + gitAuth.setPrivateKey("privatekey"); + gitApplicationMetadata.setGitAuth(gitAuth); + testApplication.setGitApplicationMetadata(gitApplicationMetadata); + testApplication.setName("Test App" + UUID.randomUUID().toString()); + testApplication.setWorkspaceId(workspaceId); + + Mono> createBranchMono = applicationPageService.createApplication(testApplication) + .flatMap(application -> gitService.connectApplicationToGit(application.getId(), gitConnectDTO, "origin")) + .flatMap(application -> + Mono.zip(gitService + .createBranch(application.getId(), createGitBranchDTO, application.getGitApplicationMetadata().getBranchName()) + .then(applicationService.findByBranchNameAndDefaultApplicationId(createGitBranchDTO.getBranchName(), application.getId(), READ_APPLICATIONS)), + Mono.just(application)) + + ) + .flatMap(applicationTuple->{ + Application branchedApplication = applicationTuple.getT1(); + Application application = applicationTuple.getT2(); + String srcBranchName = application.getGitApplicationMetadata().getBranchName(); + String otherBranchName = branchedApplication.getGitApplicationMetadata().getBranchName(); + String defaultApplicationId = branchedApplication.getGitApplicationMetadata().getDefaultApplicationId(); + + FilePart filepart = createMockFilePart(); + return Mono.zip( + applicationService.saveAppNavigationLogo(otherBranchName, defaultApplicationId, filepart).cache(), + applicationService.saveAppNavigationLogo(srcBranchName, defaultApplicationId, filepart).cache() + ); + }) + .flatMap(appTuple->{ + Application branchedApplication = appTuple.getT1(); + Application application = appTuple.getT2(); + + return applicationService.deleteAppNavigationLogo(branchedApplication.getGitApplicationMetadata().getBranchName(), branchedApplication.getGitApplicationMetadata().getDefaultApplicationId()) + .then(applicationService.findByIdAndBranchName(branchedApplication.getGitApplicationMetadata().getDefaultApplicationId(), branchedApplication.getGitApplicationMetadata().getBranchName())); + }) + .zipWhen(application -> + applicationService.findById(application.getGitApplicationMetadata().getDefaultApplicationId()) + ); + + StepVerifier + .create(createBranchMono) + .assertNext(tuple -> { + Application branchedApp = tuple.getT1(); + Application srcApp = tuple.getT2(); + assertThat(branchedApp.getUnpublishedNavigationSetting()).isNotNull(); + assertThat(branchedApp.getUnpublishedNavigationSetting().getLogoAssetId()).isNull(); + assertThat(srcApp.getUnpublishedNavigationSetting()).isNotNull(); + assertThat(srcApp.getUnpublishedNavigationSetting().getLogoAssetId()).isNotNull(); + }) + .verifyComplete(); + } + + + @Test @WithUserDetails(value = "api_user") public void connectApplicationToGit_cancelledMidway_cloneSuccess() throws IOException { diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java index 7d5741123f..4db22e5a6e 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java @@ -15,6 +15,7 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationPage; +import com.appsmith.server.domains.Asset; import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.Layout; @@ -40,6 +41,7 @@ import com.appsmith.server.helpers.PolicyUtils; import com.appsmith.server.helpers.TextUtils; import com.appsmith.server.migrations.ApplicationVersion; import com.appsmith.server.repositories.ApplicationRepository; +import com.appsmith.server.repositories.AssetRepository; import com.appsmith.server.repositories.NewPageRepository; import com.appsmith.server.repositories.PermissionGroupRepository; import com.appsmith.server.repositories.PluginRepository; @@ -73,9 +75,15 @@ import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; 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.ReactiveMongoOperations; import org.springframework.data.mongodb.core.query.Query; import org.springframework.http.HttpMethod; +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.junit.jupiter.SpringExtension; @@ -211,6 +219,9 @@ public class ApplicationServiceCETest { @Autowired SessionUserService sessionUserService; + @Autowired + private AssetRepository assetRepository; + String workspaceId; static Plugin testPlugin = new Plugin(); @@ -3403,4 +3414,100 @@ public class ApplicationServiceCETest { }) .verifyComplete(); } + + private FilePart createMockFilePart() { + FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); + Flux dataBufferFlux = DataBufferUtils + .read(new ClassPathResource("test_assets/WorkspaceServiceTest/my_workspace_logo.png"), new DefaultDataBufferFactory(), 4096).cache(); + Mockito.when(filepart.content()).thenReturn(dataBufferFlux); + Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.IMAGE_PNG); + return filepart; + } + + private String createTestApplication(String applicationName){ + Application testApplication = new Application(); + testApplication.setName(applicationName); + Application application = applicationPageService.createApplication(testApplication, workspaceId).block(); + return application.getId(); + } + + @Test + @WithUserDetails(value = "api_user") + public void testUploadAndDeleteNavigationLogo_validImage() { + FilePart filepart = createMockFilePart(); + String createdApplicationId = createTestApplication("ApplicationServiceTest Upload/Delete Nav Logo"); + + final Mono saveMono = applicationService.saveAppNavigationLogo(null, createdApplicationId, filepart).cache(); + + Mono> loadLogoImageMono = applicationService.findById(createdApplicationId) + .flatMap(fetchedApplication -> { + Mono fetchedApplicationMono = Mono.just(fetchedApplication); + if (StringUtils.isEmpty(fetchedApplication.getUnpublishedNavigationSetting().getLogoAssetId())) { + return fetchedApplicationMono.zipWith(Mono.just(new Asset())); + } else { + return fetchedApplicationMono.zipWith(assetRepository.findById(fetchedApplication.getUnpublishedNavigationSetting().getLogoAssetId())); + } + }); + + + final Mono> saveAndGetMono = saveMono.then(loadLogoImageMono); + final Mono> deleteAndGetMono = saveMono.then(applicationService.deleteAppNavigationLogo(null, createdApplicationId)).then(loadLogoImageMono); + + StepVerifier.create(saveAndGetMono) + .assertNext(tuple -> { + final Application application1 = tuple.getT1(); + assertThat(application1.getUnpublishedNavigationSetting().getLogoAssetId()).isNotNull(); + + final Asset asset = tuple.getT2(); + assertThat(asset).isNotNull(); + }) + .verifyComplete(); + + StepVerifier.create(deleteAndGetMono) + .assertNext(objects -> { + assertThat(objects.getT1().getUnpublishedNavigationSetting().getLogoAssetId()).isNull(); + assertThat(objects.getT2().getId()).isNull(); + }) + // Should be empty since the profile photo has been deleted. + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void testUploadNavigationLogo_invalidImageFormat(){ + FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); + Flux dataBufferFlux = DataBufferUtils + .read(new ClassPathResource("test_assets/WorkspaceServiceTest/my_workspace_logo.png"), new DefaultDataBufferFactory(), 4096) + .cache(); + + Mockito.when(filepart.content()).thenReturn(dataBufferFlux); + Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.IMAGE_GIF); + + String createdApplicationId = createTestApplication("ApplicationServiceTest Upload Invalid Nav Logo"); + + final Mono saveMono = applicationService.saveAppNavigationLogo(null, createdApplicationId, filepart).cache(); + StepVerifier.create(saveMono) + .expectErrorMatches(error -> error instanceof AppsmithException) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void testUploadNavigationLogo_invalidImageSize(){ + FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); + Flux dataBufferFlux = DataBufferUtils + .read(new ClassPathResource("test_assets/WorkspaceServiceTest/my_workspace_logo_large.png"), new DefaultDataBufferFactory(), 4096) + .repeat(100) // So the file size looks like it's much larger than what it actually is. + .cache(); + + Mockito.when(filepart.content()).thenReturn(dataBufferFlux); + Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.IMAGE_PNG); + + String createdApplicationId = createTestApplication("ApplicationServiceTest Upload Invalid Nav Logo Size"); + + final Mono saveMono = applicationService.saveAppNavigationLogo(null, createdApplicationId, filepart).cache(); + StepVerifier.create(saveMono) + .expectErrorMatches(error -> error instanceof AppsmithException) + .verify(); + } } 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 53f215fffe..d36a8aca2c 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 @@ -3588,4 +3588,30 @@ public class ImportExportApplicationServiceTests { assertThat(fields.getBearerTokenAuth().getBearerToken()).isEqualTo("token_" + randomUUID); }).verifyComplete(); } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationTest_WithNavigationSettings() { + + Application application = new Application(); + application.setName("exportNavigationSettingsApplicationTest"); + Application.NavigationSetting navSetting = new Application.NavigationSetting(); + navSetting.setOrientation("top"); + application.setUnpublishedNavigationSetting(navSetting); + Application createdApplication = applicationPageService.createApplication(application, workspaceId).block(); + + Mono resultMono = + importExportApplicationService.exportApplicationById(createdApplication.getId(), ""); + + StepVerifier + .create(resultMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + assertThat(exportedApplication).isNotNull(); + assertThat(exportedApplication.getUnpublishedNavigationSetting()).isNotNull(); + assertThat(exportedApplication.getUnpublishedNavigationSetting().getOrientation()).isEqualTo("top"); + }) + .verifyComplete(); + } + }