feat: APIs to Upload/Delete App navigation logo (#19164) (#19771)

## Description

Implements backend APIs for Uploading and Deleting Navigation Logo

Fixes #19164

## Type of change

- New feature (non-breaking change which adds functionality)



## How Has This Been Tested?

- Manual
- Postman
- JUnit Tests

## Checklist:
### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] PR is being merged under a feature flag


### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
Nilansh Bansal 2023-01-23 13:08:59 +05:30 committed by GitHub
parent 44fa7dfbd5
commit 6531bd6aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 445 additions and 6 deletions

View File

@ -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;
}

View File

@ -130,7 +130,7 @@ public class ApplicationControllerCE extends BaseController<ApplicationService,
public Mono<ResponseDTO<ReleaseItemsDTO>> 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<ApplicationService,
.map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null));
}
@PostMapping(value = "/{defaultApplicationId}/logo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<ResponseDTO<Application>> uploadAppNavigationLogo(@PathVariable String defaultApplicationId,
@RequestPart("file") Mono<Part> 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<ResponseDTO<Void>> 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<ResponseDTO<List<Application>>> getAll(@RequestParam MultiValueMap<String, String> 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())
);

View File

@ -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);
}
}

View File

@ -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<Application, String> {
Mono<Application> getApplicationByDefaultApplicationIdAndDefaultBranch(String defaultApplicationId);
Mono<Application> findByIdAndExportWithConfiguration(String applicationId, Boolean exportWithConfiguration);
Mono<Application> saveAppNavigationLogo(String branchName, String applicationId, Part filePart);
public Mono<Void> deleteAppNavigationLogo(String branchName, String applicationId);
}

View File

@ -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<ApplicationRepository, Application, String> implements ApplicationServiceCE {
@ -79,6 +82,8 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
private final TenantService tenantService;
private final AssetService assetService;
private final UserRepository userRepository;
private final DatasourcePermission datasourcePermission;
private final ApplicationPermission applicationPermission;
@ -97,6 +102,7 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
ResponseUtils responseUtils,
PermissionGroupService permissionGroupService,
TenantService tenantService,
AssetService assetService,
UserRepository userRepository,
DatasourcePermission datasourcePermission,
ApplicationPermission applicationPermission) {
@ -109,6 +115,7 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
this.responseUtils = responseUtils;
this.permissionGroupService = permissionGroupService;
this.tenantService = tenantService;
this.assetService = assetService;
this.userRepository = userRepository;
this.datasourcePermission = datasourcePermission;
this.applicationPermission = applicationPermission;
@ -317,7 +324,9 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
Application.NavigationSetting requestNavSetting = application.getUnpublishedNavigationSetting();
if (requestNavSetting != null) {
Application.NavigationSetting presetNavSetting = ObjectUtils.defaultIfNull(branchedApplication.getUnpublishedNavigationSetting(), new Application.NavigationSetting());
requestNavSetting.setLogoAssetId(ObjectUtils.defaultIfNull(presetNavSetting.getLogoAssetId(), ""));
String presetLogoAssetId = ObjectUtils.defaultIfNull(presetNavSetting.getLogoAssetId(), "");
String requestLogoAssetId = ObjectUtils.defaultIfNull(requestNavSetting.getLogoAssetId(), null);
requestNavSetting.setLogoAssetId(ObjectUtils.defaultIfNull(requestLogoAssetId, presetLogoAssetId));
application.setUnpublishedNavigationSetting(requestNavSetting);
}
return this.update(branchedApplication.getId(), application);
@ -772,4 +781,64 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
public Mono<Application> findByIdAndExportWithConfiguration(String applicationId, Boolean exportWithConfiguration) {
return repository.findByIdAndExportWithConfiguration(applicationId, exportWithConfiguration);
}
@Override
public Mono<Application> 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<String> prevAssetIdMono = Mono.just(rootAppLogoAssetId);
final Mono<Asset> 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<Application> updateMono = this.update(applicationId, branchedApplication, branchName);
if (!StringUtils.hasLength(oldAssetId)){
return updateMono;
} else {
return assetService.remove(oldAssetId).then(updateMono);
}
});
});
}
@Override
public Mono<Void> 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);
}
}

View File

@ -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<Tuple2<Application, Application>> 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<DataBuffer> 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<Tuple2<Application, Application>> 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<Tuple2<Application, Application>> 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 {

View File

@ -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<DataBuffer> 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<Application> saveMono = applicationService.saveAppNavigationLogo(null, createdApplicationId, filepart).cache();
Mono<Tuple2<Application, Asset>> loadLogoImageMono = applicationService.findById(createdApplicationId)
.flatMap(fetchedApplication -> {
Mono<Application> 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<Tuple2<Application, Asset>> saveAndGetMono = saveMono.then(loadLogoImageMono);
final Mono<Tuple2<Application, Asset>> 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<DataBuffer> 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<Application> 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<DataBuffer> 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<Application> saveMono = applicationService.saveAppNavigationLogo(null, createdApplicationId, filepart).cache();
StepVerifier.create(saveMono)
.expectErrorMatches(error -> error instanceof AppsmithException)
.verify();
}
}

View File

@ -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<ApplicationJson> 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();
}
}