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:
Abhijeet Mishra 2023-09-15 16:15:33 +05:30 committed by GitHub
parent 39023b9733
commit 5f913c5604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 391 additions and 165 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
package com.appsmith.server.dtos;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CommunityTemplateUploadDTO {
ApplicationTemplate applicationTemplate;
ApplicationJson appJson;
}

View File

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

View File

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

View File

@ -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.

View File

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

View File

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