feat: add API to publish community template (#27134)
## Description Add API to publish community template #### PR fixes following issue(s) #26242 #### Type of change - New feature (non-breaking change which adds functionality) ## Testing #### How Has This Been Tested? - [X] Manual - [x] JUnit - [ ] Jest - [ ] Cypress #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
39023b9733
commit
5f913c5604
|
|
@ -17,6 +17,9 @@ public class CloudServicesConfig {
|
|||
@Value("${appsmith.cloud_services.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${appsmith.cloud_services.template_upload_auth_header}")
|
||||
private String templateUploadAuthHeader;
|
||||
|
||||
@Autowired
|
||||
public void setBaseUrl(@Value("${appsmith.cloud_services.base_url:}") String value) {
|
||||
baseUrl = StringUtils.isEmpty(value) ? "https://cs.appsmith.com" : value;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package com.appsmith.server.controllers.ce;
|
|||
|
||||
import com.appsmith.external.views.Views;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.dtos.ApplicationImportDTO;
|
||||
import com.appsmith.server.dtos.ApplicationTemplate;
|
||||
import com.appsmith.server.dtos.CommunityTemplateDTO;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.services.ApplicationTemplateService;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
|
|
@ -92,4 +94,13 @@ public class ApplicationTemplateControllerCE {
|
|||
.mergeTemplateWithApplication(templateId, applicationId, organizationId, branchName, pagesToImport)
|
||||
.map(importedApp -> new ResponseDTO<>(HttpStatus.OK.value(), importedApp, null));
|
||||
}
|
||||
|
||||
@JsonView(Views.Public.class)
|
||||
@PostMapping("publish/{applicationId}/{organizationId}")
|
||||
public Mono<ResponseDTO<Application>> publishAsCommunityTemplate(
|
||||
@RequestBody(required = true) CommunityTemplateDTO resource) {
|
||||
return applicationTemplateService
|
||||
.publishAsCommunityTemplate(resource)
|
||||
.map(template -> new ResponseDTO<>(HttpStatus.OK.value(), template, null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,6 +194,10 @@ public class Application extends BaseDomain {
|
|||
@JsonView(Views.Public.class)
|
||||
Boolean forkWithConfiguration;
|
||||
|
||||
// isCommunityTemplate represents whether this application has been published as a community template
|
||||
@JsonView(Views.Public.class)
|
||||
Boolean isCommunityTemplate;
|
||||
|
||||
@JsonView(Views.Internal.class)
|
||||
@Deprecated
|
||||
String defaultPermissionGroup;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
|
|
@ -34,4 +35,11 @@ public class ApplicationTemplate extends BaseDomain {
|
|||
private Boolean featured;
|
||||
private List<String> tags;
|
||||
private Boolean allowPageImport;
|
||||
// This flag denotes whether a template is an official template
|
||||
// or a community template
|
||||
private Boolean isCommunityTemplate;
|
||||
// This will point to the email of the template's author. This cannot be
|
||||
// null if the template is a community template
|
||||
@Email
|
||||
private String authorEmail;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CommunityTemplateDTO extends BaseDomain {
|
||||
String applicationId;
|
||||
String workspaceId;
|
||||
String branchName;
|
||||
String title;
|
||||
String headline;
|
||||
String description;
|
||||
List<String> useCases;
|
||||
String authorEmail;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CommunityTemplateUploadDTO {
|
||||
ApplicationTemplate applicationTemplate;
|
||||
ApplicationJson appJson;
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.appsmith.server.services.ce;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.dtos.ApplicationImportDTO;
|
||||
import com.appsmith.server.dtos.ApplicationTemplate;
|
||||
import com.appsmith.server.dtos.CommunityTemplateDTO;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
@ -24,4 +26,6 @@ public interface ApplicationTemplateServiceCE {
|
|||
String templateId, String applicationId, String workspaceId, String branchName, List<String> pagesToImport);
|
||||
|
||||
Mono<ApplicationTemplate> getFilters();
|
||||
|
||||
Mono<Application> publishAsCommunityTemplate(CommunityTemplateDTO resource);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ import com.appsmith.server.constants.FieldName;
|
|||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.dtos.ApplicationImportDTO;
|
||||
import com.appsmith.server.dtos.ApplicationJson;
|
||||
import com.appsmith.server.dtos.ApplicationTemplate;
|
||||
import com.appsmith.server.dtos.*;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.ResponseUtils;
|
||||
|
|
@ -20,13 +18,17 @@ import com.appsmith.server.solutions.ApplicationPermission;
|
|||
import com.appsmith.server.solutions.ImportExportApplicationService;
|
||||
import com.appsmith.server.solutions.ReleaseNotesService;
|
||||
import com.appsmith.util.WebClientUtils;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
|
@ -309,4 +311,66 @@ public class ApplicationTemplateServiceCEImpl implements ApplicationTemplateServ
|
|||
return Mono.create(
|
||||
sink -> importedApplicationMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
|
||||
}
|
||||
|
||||
private CommunityTemplateUploadDTO createCommunityTemplateUploadDTO(
|
||||
ApplicationJson appJson, CommunityTemplateDTO templateDetails) {
|
||||
ApplicationTemplate applicationTemplate = new ApplicationTemplate();
|
||||
applicationTemplate.setTitle(templateDetails.getTitle());
|
||||
applicationTemplate.setExcerpt(templateDetails.getHeadline());
|
||||
applicationTemplate.setDescription(templateDetails.getDescription());
|
||||
applicationTemplate.setUseCases(templateDetails.getUseCases());
|
||||
applicationTemplate.setAuthorEmail(templateDetails.getAuthorEmail());
|
||||
|
||||
CommunityTemplateUploadDTO communityTemplate = new CommunityTemplateUploadDTO();
|
||||
communityTemplate.setAppJson(appJson);
|
||||
communityTemplate.setApplicationTemplate(applicationTemplate);
|
||||
return communityTemplate;
|
||||
}
|
||||
|
||||
private Mono<ApplicationTemplate> uploadCommunityTemplateToCS(CommunityTemplateUploadDTO communityTemplate) {
|
||||
String url = cloudServicesConfig.getBaseUrl() + "/api/v1/app-templates/upload-community-template";
|
||||
String authHeader = "Authorization";
|
||||
String payload;
|
||||
try {
|
||||
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
|
||||
payload = ow.writeValueAsString(communityTemplate);
|
||||
} catch (Exception e) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
|
||||
return WebClient.create()
|
||||
.post()
|
||||
.uri(url)
|
||||
.header(authHeader, cloudServicesConfig.getTemplateUploadAuthHeader())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.body(BodyInserters.fromValue(payload))
|
||||
.retrieve()
|
||||
.bodyToMono(ApplicationTemplate.class);
|
||||
}
|
||||
|
||||
private Mono<Application> updateApplicationFlags(String applicationId, String branchId) {
|
||||
return applicationService
|
||||
.findById(applicationId, applicationPermission.getEditPermission())
|
||||
.flatMap(application -> {
|
||||
application.setForkingEnabled(true);
|
||||
application.setIsCommunityTemplate(true);
|
||||
|
||||
return applicationService.update(applicationId, application, branchId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Application> publishAsCommunityTemplate(CommunityTemplateDTO resource) {
|
||||
return importExportApplicationService
|
||||
.exportApplicationById(resource.getApplicationId(), resource.getBranchName())
|
||||
.flatMap(appJson -> uploadCommunityTemplateToCS(createCommunityTemplateUploadDTO(appJson, resource)))
|
||||
.then(updateApplicationFlags(resource.getApplicationId(), resource.getBranchName()))
|
||||
.flatMap(application -> {
|
||||
ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO();
|
||||
applicationAccessDTO.setPublicAccess(true);
|
||||
return applicationService.changeViewAccess(
|
||||
application.getId(), resource.getBranchName(), applicationAccessDTO);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ appsmith.cloud_services.base_url = ${APPSMITH_CLOUD_SERVICES_BASE_URL:}
|
|||
appsmith.cloud_services.signature_base_url = ${APPSMITH_CLOUD_SERVICES_SIGNATURE_BASE_URL:}
|
||||
appsmith.cloud_services.username = ${APPSMITH_CLOUD_SERVICES_USERNAME:}
|
||||
appsmith.cloud_services.password = ${APPSMITH_CLOUD_SERVICES_PASSWORD:}
|
||||
appsmith.cloud_services.template_upload_auth_header = ${APPSMITH_CLOUD_SERVICES_TEMPLATE_UPLOAD_AUTH}
|
||||
github_repo = ${APPSMITH_GITHUB_REPO:}
|
||||
|
||||
# MANDATORY!! No default properties are being provided for encryption password and salt for security.
|
||||
|
|
|
|||
|
|
@ -1,68 +1,47 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.server.configurations.CloudServicesConfig;
|
||||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.dtos.ApplicationTemplate;
|
||||
import com.appsmith.server.dtos.PageNameIdDTO;
|
||||
import com.appsmith.server.helpers.ResponseUtils;
|
||||
import com.appsmith.server.solutions.ApplicationPermission;
|
||||
import com.appsmith.server.solutions.ImportExportApplicationService;
|
||||
import com.appsmith.server.solutions.ReleaseNotesService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.GitApplicationMetadata;
|
||||
import com.appsmith.server.domains.Workspace;
|
||||
import com.appsmith.server.dtos.CommunityTemplateDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mockwebserver3.MockResponse;
|
||||
import mockwebserver3.MockWebServer;
|
||||
import mockwebserver3.RecordedRequest;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.security.test.context.support.WithUserDetails;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* This test is written based on the inspiration from the tutorial: https://www.baeldung.com/spring-mocking-webclient
|
||||
*/
|
||||
@Slf4j
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest
|
||||
@DirtiesContext
|
||||
@TestMethodOrder(MethodOrderer.MethodName.class)
|
||||
public class ApplicationTemplateServiceTest {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static MockWebServer mockCloudServices;
|
||||
|
||||
@Autowired
|
||||
ApplicationTemplateService applicationTemplateService;
|
||||
|
||||
@MockBean
|
||||
ApplicationPermission applicationPermission;
|
||||
@Autowired
|
||||
WorkspaceService workspaceService;
|
||||
|
||||
@MockBean
|
||||
private UserDataService userDataService;
|
||||
@Autowired
|
||||
ApplicationPageService applicationPageService;
|
||||
|
||||
@MockBean
|
||||
private CloudServicesConfig cloudServicesConfig;
|
||||
|
||||
@MockBean
|
||||
private ReleaseNotesService releaseNotesService;
|
||||
|
||||
@MockBean
|
||||
private ImportExportApplicationService importExportApplicationService;
|
||||
|
||||
@MockBean
|
||||
private AnalyticsService analyticsService;
|
||||
|
||||
@MockBean
|
||||
private ApplicationService applicationService;
|
||||
|
||||
@MockBean
|
||||
private ResponseUtils responseUtils;
|
||||
@Autowired
|
||||
CloudServicesConfig cloudServicesConfig;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() throws IOException {
|
||||
|
|
@ -75,129 +54,46 @@ public class ApplicationTemplateServiceTest {
|
|||
mockCloudServices.shutdown();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void initialize() {
|
||||
String baseUrl = String.format("http://localhost:%s", mockCloudServices.getPort());
|
||||
private Application setUpTestApplication() {
|
||||
Workspace workspace = new Workspace();
|
||||
workspace.setName("Import-Export-Test-Workspace");
|
||||
Workspace savedWorkspace = workspaceService.create(workspace).block();
|
||||
|
||||
// mock the cloud services config so that it returns mock server url as cloud service base url
|
||||
Mockito.when(cloudServicesConfig.getBaseUrl()).thenReturn(baseUrl);
|
||||
Application testApplication = new Application();
|
||||
testApplication.setName("Export-Application-Test-Application");
|
||||
testApplication.setWorkspaceId(savedWorkspace.getId());
|
||||
testApplication.setUpdatedAt(Instant.now());
|
||||
testApplication.setLastDeployedAt(Instant.now());
|
||||
testApplication.setModifiedBy("some-user");
|
||||
testApplication.setGitApplicationMetadata(new GitApplicationMetadata());
|
||||
|
||||
applicationTemplateService = new ApplicationTemplateServiceImpl(
|
||||
cloudServicesConfig,
|
||||
releaseNotesService,
|
||||
importExportApplicationService,
|
||||
analyticsService,
|
||||
userDataService,
|
||||
applicationService,
|
||||
responseUtils,
|
||||
applicationPermission);
|
||||
}
|
||||
|
||||
private ApplicationTemplate create(String id, String title) {
|
||||
ApplicationTemplate applicationTemplate = new ApplicationTemplate();
|
||||
applicationTemplate.setId(id);
|
||||
applicationTemplate.setTitle(title);
|
||||
return applicationTemplate;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getActiveTemplates_WhenRecentlyUsedExists_RecentOnesComesFirst() throws JsonProcessingException {
|
||||
ApplicationTemplate templateOne = create("id-one", "First template");
|
||||
ApplicationTemplate templateTwo = create("id-two", "Seonds template");
|
||||
ApplicationTemplate templateThree = create("id-three", "Third template");
|
||||
|
||||
// mock the server to return the above three templates
|
||||
mockCloudServices.enqueue(new MockResponse()
|
||||
.setBody(objectMapper.writeValueAsString(List.of(templateOne, templateTwo, templateThree)))
|
||||
.addHeader("Content-Type", "application/json"));
|
||||
|
||||
// mock the user data to set second template as recently used
|
||||
UserData mockUserData = new UserData();
|
||||
mockUserData.setRecentlyUsedTemplateIds(List.of("id-two"));
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(mockUserData));
|
||||
|
||||
Mono<List<ApplicationTemplate>> templateListMono = applicationTemplateService.getActiveTemplates(null);
|
||||
|
||||
StepVerifier.create(templateListMono)
|
||||
.assertNext(applicationTemplates -> {
|
||||
assertThat(applicationTemplates.size()).isEqualTo(3);
|
||||
assertThat(applicationTemplates.get(0).getId()).isEqualTo("id-two"); // second one should come first
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRecentlyUsedTemplates_WhenNoRecentTemplate_ReturnsEmpty() {
|
||||
// mock the user data to that has no recent template
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(new UserData()));
|
||||
|
||||
StepVerifier.create(applicationTemplateService.getRecentlyUsedTemplates())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRecentlyUsedTemplates_WhenRecentTemplatesExist_ReturnsTemplates()
|
||||
throws InterruptedException, JsonProcessingException {
|
||||
// mock the user data to set recently used template ids
|
||||
UserData mockUserData = new UserData();
|
||||
mockUserData.setRecentlyUsedTemplateIds(List.of("id-one", "id-two"));
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(mockUserData));
|
||||
|
||||
// mock the server to return a template when it's called
|
||||
mockCloudServices.enqueue(new MockResponse()
|
||||
.setBody(objectMapper.writeValueAsString(List.of(create("id-one", "First template"))))
|
||||
.addHeader("Content-Type", "application/json"));
|
||||
|
||||
// make sure we've received the response returned by the mockCloudServices
|
||||
StepVerifier.create(applicationTemplateService.getRecentlyUsedTemplates())
|
||||
.assertNext(
|
||||
applicationTemplates -> assertThat(applicationTemplates).hasSize(1))
|
||||
.verifyComplete();
|
||||
|
||||
// verify that mockCloudServices was called with the query param id i.e. id=id-one&id=id-two
|
||||
RecordedRequest recordedRequest = mockCloudServices.takeRequest();
|
||||
assert recordedRequest.getRequestUrl() != null;
|
||||
List<String> queryParameterValues = recordedRequest.getRequestUrl().queryParameterValues("id");
|
||||
assertThat(queryParameterValues).containsExactly("id-one", "id-two");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get_WhenPageMetaDataExists_PageMetaDataParsedProperly() throws JsonProcessingException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", "1234567890");
|
||||
jsonObject.put("name", "My Page");
|
||||
jsonObject.put("icon", "flight");
|
||||
jsonObject.put("isDefault", true);
|
||||
JSONArray pages = new JSONArray();
|
||||
pages.put(jsonObject);
|
||||
|
||||
JSONObject templateObj = new JSONObject();
|
||||
templateObj.put("title", "My Template");
|
||||
templateObj.put("pages", pages);
|
||||
|
||||
JSONArray templates = new JSONArray();
|
||||
templates.put(templateObj);
|
||||
|
||||
// mock the server to return a template when it's called
|
||||
cloudServicesConfig.setBaseUrl(String.format("http://localhost:%s", mockCloudServices.getPort()));
|
||||
mockCloudServices.enqueue(
|
||||
new MockResponse().setBody(templates.toString()).addHeader("Content-Type", "application/json"));
|
||||
new MockResponse().setBody("{\"status\": 1}").addHeader("Content-Type", "application/json"));
|
||||
|
||||
// mock the user data to set recently used template ids
|
||||
UserData mockUserData = new UserData();
|
||||
mockUserData.setRecentlyUsedTemplateIds(List.of());
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(mockUserData));
|
||||
return applicationPageService
|
||||
.createApplication(testApplication, savedWorkspace.getId())
|
||||
.block();
|
||||
}
|
||||
|
||||
// make sure we've received the response returned by the mockCloudServices
|
||||
StepVerifier.create(applicationTemplateService.getActiveTemplates(null))
|
||||
.assertNext(applicationTemplates -> {
|
||||
assertThat(applicationTemplates.size()).isEqualTo(1);
|
||||
ApplicationTemplate applicationTemplate = applicationTemplates.get(0);
|
||||
assertThat(applicationTemplate.getPages()).hasSize(1);
|
||||
PageNameIdDTO pageNameIdDTO = applicationTemplate.getPages().get(0);
|
||||
assertThat(pageNameIdDTO.getId()).isEqualTo("1234567890");
|
||||
assertThat(pageNameIdDTO.getName()).isEqualTo("My Page");
|
||||
assertThat(pageNameIdDTO.getIcon()).isEqualTo("flight");
|
||||
assertThat(pageNameIdDTO.getIsDefault()).isTrue();
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void test_application_published_as_community_template() {
|
||||
Application testApp = setUpTestApplication();
|
||||
CommunityTemplateDTO communityTemplateDTO = new CommunityTemplateDTO();
|
||||
communityTemplateDTO.setApplicationId(testApp.getId());
|
||||
communityTemplateDTO.setWorkspaceId(testApp.getWorkspaceId());
|
||||
communityTemplateDTO.setTitle("Some title");
|
||||
communityTemplateDTO.setHeadline("Some headline");
|
||||
communityTemplateDTO.setDescription("Some description");
|
||||
communityTemplateDTO.setUseCases(List.of("uc1", "uc2"));
|
||||
communityTemplateDTO.setAuthorEmail("test@user.com");
|
||||
|
||||
StepVerifier.create(applicationTemplateService.publishAsCommunityTemplate(communityTemplateDTO))
|
||||
.assertNext(updatedApplication -> {
|
||||
assertThat(updatedApplication.getIsCommunityTemplate()).isTrue();
|
||||
assertThat(updatedApplication.getForkingEnabled()).isTrue();
|
||||
assertThat(updatedApplication.getIsPublic()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.server.configurations.CloudServicesConfig;
|
||||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.dtos.ApplicationTemplate;
|
||||
import com.appsmith.server.dtos.PageNameIdDTO;
|
||||
import com.appsmith.server.helpers.ResponseUtils;
|
||||
import com.appsmith.server.solutions.ApplicationPermission;
|
||||
import com.appsmith.server.solutions.ImportExportApplicationService;
|
||||
import com.appsmith.server.solutions.ReleaseNotesService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import mockwebserver3.MockResponse;
|
||||
import mockwebserver3.MockWebServer;
|
||||
import mockwebserver3.RecordedRequest;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* This test is written based on the inspiration from the tutorial: https://www.baeldung.com/spring-mocking-webclient
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class ApplicationTemplateServiceUnitTest {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static MockWebServer mockCloudServices;
|
||||
ApplicationTemplateService applicationTemplateService;
|
||||
|
||||
@MockBean
|
||||
ApplicationPermission applicationPermission;
|
||||
|
||||
@MockBean
|
||||
private UserDataService userDataService;
|
||||
|
||||
@MockBean
|
||||
private CloudServicesConfig cloudServicesConfig;
|
||||
|
||||
@MockBean
|
||||
private ReleaseNotesService releaseNotesService;
|
||||
|
||||
@MockBean
|
||||
private ImportExportApplicationService importExportApplicationService;
|
||||
|
||||
@MockBean
|
||||
private AnalyticsService analyticsService;
|
||||
|
||||
@MockBean
|
||||
private ApplicationService applicationService;
|
||||
|
||||
@MockBean
|
||||
private ResponseUtils responseUtils;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() throws IOException {
|
||||
mockCloudServices = new MockWebServer();
|
||||
mockCloudServices.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() throws IOException {
|
||||
mockCloudServices.shutdown();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void initialize() {
|
||||
String baseUrl = String.format("http://localhost:%s", mockCloudServices.getPort());
|
||||
|
||||
// mock the cloud services config so that it returns mock server url as cloud service base url
|
||||
Mockito.when(cloudServicesConfig.getBaseUrl()).thenReturn(baseUrl);
|
||||
|
||||
applicationTemplateService = new ApplicationTemplateServiceImpl(
|
||||
cloudServicesConfig,
|
||||
releaseNotesService,
|
||||
importExportApplicationService,
|
||||
analyticsService,
|
||||
userDataService,
|
||||
applicationService,
|
||||
responseUtils,
|
||||
applicationPermission);
|
||||
}
|
||||
|
||||
private ApplicationTemplate create(String id, String title) {
|
||||
ApplicationTemplate applicationTemplate = new ApplicationTemplate();
|
||||
applicationTemplate.setId(id);
|
||||
applicationTemplate.setTitle(title);
|
||||
return applicationTemplate;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getActiveTemplates_WhenRecentlyUsedExists_RecentOnesComesFirst() throws JsonProcessingException {
|
||||
ApplicationTemplate templateOne = create("id-one", "First template");
|
||||
ApplicationTemplate templateTwo = create("id-two", "Seonds template");
|
||||
ApplicationTemplate templateThree = create("id-three", "Third template");
|
||||
|
||||
// mock the server to return the above three templates
|
||||
mockCloudServices.enqueue(new MockResponse()
|
||||
.setBody(objectMapper.writeValueAsString(List.of(templateOne, templateTwo, templateThree)))
|
||||
.addHeader("Content-Type", "application/json"));
|
||||
|
||||
// mock the user data to set second template as recently used
|
||||
UserData mockUserData = new UserData();
|
||||
mockUserData.setRecentlyUsedTemplateIds(List.of("id-two"));
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(mockUserData));
|
||||
|
||||
Mono<List<ApplicationTemplate>> templateListMono = applicationTemplateService.getActiveTemplates(null);
|
||||
|
||||
StepVerifier.create(templateListMono)
|
||||
.assertNext(applicationTemplates -> {
|
||||
assertThat(applicationTemplates.size()).isEqualTo(3);
|
||||
assertThat(applicationTemplates.get(0).getId()).isEqualTo("id-two"); // second one should come first
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRecentlyUsedTemplates_WhenNoRecentTemplate_ReturnsEmpty() {
|
||||
// mock the user data to that has no recent template
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(new UserData()));
|
||||
|
||||
StepVerifier.create(applicationTemplateService.getRecentlyUsedTemplates())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRecentlyUsedTemplates_WhenRecentTemplatesExist_ReturnsTemplates()
|
||||
throws InterruptedException, JsonProcessingException {
|
||||
// mock the user data to set recently used template ids
|
||||
UserData mockUserData = new UserData();
|
||||
mockUserData.setRecentlyUsedTemplateIds(List.of("id-one", "id-two"));
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(mockUserData));
|
||||
|
||||
// mock the server to return a template when it's called
|
||||
mockCloudServices.enqueue(new MockResponse()
|
||||
.setBody(objectMapper.writeValueAsString(List.of(create("id-one", "First template"))))
|
||||
.addHeader("Content-Type", "application/json"));
|
||||
|
||||
// make sure we've received the response returned by the mockCloudServices
|
||||
StepVerifier.create(applicationTemplateService.getRecentlyUsedTemplates())
|
||||
.assertNext(
|
||||
applicationTemplates -> assertThat(applicationTemplates).hasSize(1))
|
||||
.verifyComplete();
|
||||
|
||||
// verify that mockCloudServices was called with the query param id i.e. id=id-one&id=id-two
|
||||
RecordedRequest recordedRequest = mockCloudServices.takeRequest();
|
||||
assert recordedRequest.getRequestUrl() != null;
|
||||
List<String> queryParameterValues = recordedRequest.getRequestUrl().queryParameterValues("id");
|
||||
assertThat(queryParameterValues).containsExactly("id-one", "id-two");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get_WhenPageMetaDataExists_PageMetaDataParsedProperly() throws JsonProcessingException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", "1234567890");
|
||||
jsonObject.put("name", "My Page");
|
||||
jsonObject.put("icon", "flight");
|
||||
jsonObject.put("isDefault", true);
|
||||
JSONArray pages = new JSONArray();
|
||||
pages.put(jsonObject);
|
||||
|
||||
JSONObject templateObj = new JSONObject();
|
||||
templateObj.put("title", "My Template");
|
||||
templateObj.put("pages", pages);
|
||||
|
||||
JSONArray templates = new JSONArray();
|
||||
templates.put(templateObj);
|
||||
|
||||
// mock the server to return a template when it's called
|
||||
mockCloudServices.enqueue(
|
||||
new MockResponse().setBody(templates.toString()).addHeader("Content-Type", "application/json"));
|
||||
|
||||
// mock the user data to set recently used template ids
|
||||
UserData mockUserData = new UserData();
|
||||
mockUserData.setRecentlyUsedTemplateIds(List.of());
|
||||
Mockito.when(userDataService.getForCurrentUser()).thenReturn(Mono.just(mockUserData));
|
||||
|
||||
// make sure we've received the response returned by the mockCloudServices
|
||||
StepVerifier.create(applicationTemplateService.getActiveTemplates(null))
|
||||
.assertNext(applicationTemplates -> {
|
||||
assertThat(applicationTemplates.size()).isEqualTo(1);
|
||||
ApplicationTemplate applicationTemplate = applicationTemplates.get(0);
|
||||
assertThat(applicationTemplate.getPages()).hasSize(1);
|
||||
PageNameIdDTO pageNameIdDTO = applicationTemplate.getPages().get(0);
|
||||
assertThat(pageNameIdDTO.getId()).isEqualTo("1234567890");
|
||||
assertThat(pageNameIdDTO.getName()).isEqualTo("My Page");
|
||||
assertThat(pageNameIdDTO.getIcon()).isEqualTo("flight");
|
||||
assertThat(pageNameIdDTO.getIsDefault()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user