From c12d848700cbbd659d9b71eda0f836dcccc0f5f2 Mon Sep 17 00:00:00 2001 From: Jonathan Gamba Date: Thu, 31 Oct 2024 11:21:57 -0600 Subject: [PATCH] #30438 Adding back builder to properly handle content type base types. --- .../AbstractSaveContentTypeRequest.java | 39 ++++++ .../java/com/dotcms/api/ContentTypeAPIIT.java | 100 +++++++++++---- .../contenttype/ContentTypePushHandler.java | 9 +- .../contenttype/ContentTypeCommandIT.java | 60 +++++++++ .../common/ContentTypesTestHelperService.java | 115 +++++++++++++++++- 5 files changed, 291 insertions(+), 32 deletions(-) diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java index 4a3b37b060f2..b8ebafe8cd06 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java @@ -70,4 +70,43 @@ public String idFromValue(Object value) { } + /** + * Custom Builder to handle the typeInf attribute. + */ + public static class Builder extends SaveContentTypeRequest.Builder { + + private Class typeInf = SimpleContentType.class; + + /** + * Sets the typeInf attribute based on the provided ContentType instance. + * + * @param in the ContentType instance + * @return the updated SaveContentTypeRequest.Builder + */ + public SaveContentTypeRequest.Builder of(ContentType in) { + this.typeInf = in.getClass(); + return from(in); + } + + /** + * Builds the SaveContentTypeRequest and sets the typeInf attribute. + * + * @return the built SaveContentTypeRequest + */ + @Override + public SaveContentTypeRequest build() { + this.typeInf(typeInf); + return super.build(); + } + } + + /** + * Helper method to create the custom Builder. + * + * @return a new instance of the custom Builder + */ + public static Builder builder() { + return new Builder(); + } + } diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java index ceec15d90d29..84abd751b21d 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java @@ -21,11 +21,13 @@ import com.dotcms.contenttype.model.field.Relationships; import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.contenttype.model.type.ImmutablePageContentType; import com.dotcms.contenttype.model.type.ImmutableSimpleContentType; import com.dotcms.contenttype.model.type.SimpleContentType; import com.dotcms.contenttype.model.workflow.SystemAction; import com.dotcms.model.ResponseEntityView; import com.dotcms.model.config.ServiceBean; +import com.dotcms.model.contenttype.AbstractSaveContentTypeRequest; import com.dotcms.model.contenttype.FilterContentTypesRequest; import com.dotcms.model.contenttype.SaveContentTypeRequest; import com.dotcms.model.site.GetSiteByNameRequest; @@ -114,7 +116,6 @@ void Test_Content_Type_Model_Serialization() throws JsonProcessingException { .build()).build(); final String ctAsString = objectMapper.writeValueAsString(contentType); - System.out.println(ctAsString); final ContentType ct = objectMapper.readValue(ctAsString, ContentType.class); Assert.assertNotNull(ct); @@ -144,6 +145,50 @@ abstract class AbstractContentTypesResponse extends AbstractResponseEntityView < */ } + /** + * Generate a CT using our classes model then test we can go back and forth using serialization + * and test our fields actually get translated properly using polymorphism to finally test we + * can create a SaveContentTypeRequest from a ContentType and the values are properly set. + * + * @throws JsonProcessingException If an error occurs while processing JSON. + */ + @Test + void Test_Content_Type_Model_SaveContentTypeRequest() throws JsonProcessingException { + + final ObjectMapper objectMapper = new ClientObjectMapper().getContext(null); + + final ImmutablePageContentType contentType = ImmutablePageContentType.builder() + .baseType(BaseContentType.HTMLPAGE) + .description("desc") + .id("1") + .variable("var") + .addFields(ImmutableBinaryField.builder() + .name("name") + .id("1") + .variable("fieldVar") + .build()).build(); + + final String ctAsString = objectMapper.writeValueAsString(contentType); + + final ContentType ct = objectMapper.readValue(ctAsString, ContentType.class); + Assert.assertNotNull(ct); + Assert.assertTrue( + ct.fields().stream().anyMatch(field -> field instanceof BinaryField)); + + Assertions.assertEquals(BaseContentType.HTMLPAGE, ct.baseType()); + Assertions.assertEquals(ContentType.SYSTEM_HOST, ct.host()); + Assertions.assertEquals(ContentType.SYSTEM_FOLDER, ct.folder()); + + // Now we validate that when we create a SaveContentTypeRequest from a ContentType we have + // the proper values + final SaveContentTypeRequest contentTypeRequest = AbstractSaveContentTypeRequest.builder() + .of(ct).build(); + Assertions.assertEquals(BaseContentType.HTMLPAGE, contentTypeRequest.baseType()); + Assertions.assertEquals(ct.getClass(), contentTypeRequest.typeInf()); + Assertions.assertEquals(ContentType.SYSTEM_HOST, contentTypeRequest.host()); + Assertions.assertEquals(ContentType.SYSTEM_FOLDER, contentTypeRequest.folder()); + } + /** * Test that we can hit */ @@ -234,8 +279,8 @@ void Test_Create_Then_Update_Then_Delete_Content_Type() { ).build(); final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(contentType).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder(). + of(contentType).build(); final ResponseEntityView> response = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(response); @@ -250,8 +295,8 @@ void Test_Create_Then_Update_Then_Delete_Content_Type() { // Now lets test update final ImmutableSimpleContentType updatedContentType = ImmutableSimpleContentType.builder().from(newContentType).description("Updated").build(); - final SaveContentTypeRequest request = SaveContentTypeRequest.builder(). - from(updatedContentType).build(); + final SaveContentTypeRequest request = AbstractSaveContentTypeRequest.builder(). + of(updatedContentType).build(); final ResponseEntityView responseEntityView = getUpdateContentTypeResponse( client, request); Assertions.assertEquals("Updated", responseEntityView.entity().description()); @@ -358,8 +403,8 @@ void Test_Create_Then_Update_Action_Mappings() throws JsonProcessingException { // --- // Create the content type with the action mappings final var contentType = contentTypeWithoutMapping.withSystemActionMappings(jsonNodeV1); - final SaveContentTypeRequest request = SaveContentTypeRequest.builder() - .from(contentType).build(); + final SaveContentTypeRequest request = AbstractSaveContentTypeRequest.builder() + .of(contentType).build(); final ResponseEntityView> createContentTypeResponse = client.createContentTypes(List.of(request)); @@ -378,8 +423,8 @@ void Test_Create_Then_Update_Action_Mappings() throws JsonProcessingException { // Modifying the content type without system mappings, nothing should change in mappings var modifiedContentType = contentTypeWithoutMapping.withDescription("Modified!"); - final SaveContentTypeRequest contentTypeRequest = SaveContentTypeRequest.builder() - .from(modifiedContentType).build(); + final SaveContentTypeRequest contentTypeRequest = AbstractSaveContentTypeRequest.builder() + .of(modifiedContentType).build(); // Use of Awaitility to wait for the modified response await().atMost(10, TimeUnit.SECONDS).until(() -> { @@ -402,7 +447,8 @@ void Test_Create_Then_Update_Action_Mappings() throws JsonProcessingException { .withDescription("Modified 2!") .withSystemActionMappings(jsonNodeV2); - final SaveContentTypeRequest contentTypeRequest1 = SaveContentTypeRequest.builder().from(modifiedContentType).build(); + final SaveContentTypeRequest contentTypeRequest1 = AbstractSaveContentTypeRequest.builder() + .of(modifiedContentType).build(); await().atMost(10, TimeUnit.SECONDS).until(() -> { ResponseEntityView updateContentTypeResponse = getUpdateContentTypeResponse( @@ -424,7 +470,8 @@ void Test_Create_Then_Update_Action_Mappings() throws JsonProcessingException { .withDescription("Modified 3!") .withSystemActionMappings(jsonNodeV3); - final SaveContentTypeRequest contentTypeRequest2 = SaveContentTypeRequest.builder().from(modifiedContentType).build(); + final SaveContentTypeRequest contentTypeRequest2 = AbstractSaveContentTypeRequest.builder() + .of(modifiedContentType).build(); await().atMost(10, TimeUnit.SECONDS).until(() -> { ResponseEntityView updateContentTypeResponse = getUpdateContentTypeResponse( @@ -443,7 +490,8 @@ void Test_Create_Then_Update_Action_Mappings() throws JsonProcessingException { .withDescription("Modified 4!") .withSystemActionMappings(jsonNodeV4); - final SaveContentTypeRequest contentTypeRequest3 = SaveContentTypeRequest.builder().from(modifiedContentType).build(); + final SaveContentTypeRequest contentTypeRequest3 = AbstractSaveContentTypeRequest.builder() + .of(modifiedContentType).build(); await().atMost(10, TimeUnit.SECONDS).until(() -> { ResponseEntityView updateContentTypeResponse = getUpdateContentTypeResponse( @@ -623,8 +671,8 @@ void Test_Send_Invalid_Host_And_Folder_Verify_Defaults() { .build() ).build(); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(contentType1).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder(). + of(contentType1).build(); final ResponseEntityView> contentTypeResponse1 = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse1); @@ -670,8 +718,8 @@ void Test_Send_Folder_Path_Only_Valid_Folder_Expect_Matching_Folder_Id() { .build() ).build(); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(contentType1).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() + .of(contentType1).build(); final ResponseEntityView> contentTypeResponse2 = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse2); @@ -718,8 +766,8 @@ void Test_Create_Content_Type_Out_Of_Folder_Path() { .build() ).build(); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(contentType2).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder(). + of(contentType2).build(); final ResponseEntityView> contentTypeResponse2 = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse2); @@ -770,8 +818,8 @@ void Test_ContentType_Without_Layout_Attribute() { final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(contentType).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder(). + of(contentType).build(); final ResponseEntityView> contentTypeResponse = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse); @@ -865,8 +913,8 @@ void Test_ContentType_Layout_Attribute_Is_Ignored() { final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(contentType).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder(). + of(contentType).build(); final ResponseEntityView> contentTypeResponse = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse); @@ -949,8 +997,8 @@ void Simple_Relationship_Support_Test() throws IOException { final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveBlogRequest = SaveContentTypeRequest.builder(). - from(blog).build(); + final SaveContentTypeRequest saveBlogRequest = AbstractSaveContentTypeRequest.builder(). + of(blog).build(); final ResponseEntityView> contentTypeResponse1 = client.createContentTypes(List.of(saveBlogRequest)); ContentType savedContentType1 = null; ContentType savedContentType2 = null; @@ -973,8 +1021,8 @@ void Simple_Relationship_Support_Test() throws IOException { //Assertions.assertTrue(relationships1.isParentField()); Assertions.assertEquals("MyBlogComment" + timeMark, relationships1.velocityVar()); - final SaveContentTypeRequest saveBlogCommentRequest = SaveContentTypeRequest.builder(). - from(blogComment).build(); + final SaveContentTypeRequest saveBlogCommentRequest = AbstractSaveContentTypeRequest.builder(). + of(blogComment).build(); final ResponseEntityView> contentTypeResponse2 = client.createContentTypes( List.of(saveBlogCommentRequest)); final List contentTypes2 = contentTypeResponse2.entity(); diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java index 34cc9defa792..a252f60f95af 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java @@ -5,6 +5,7 @@ import com.dotcms.api.client.push.PushHandler; import com.dotcms.api.client.util.NamingUtils; import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.model.contenttype.AbstractSaveContentTypeRequest; import com.dotcms.model.contenttype.SaveContentTypeRequest; import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.control.ActivateRequestContext; @@ -61,8 +62,8 @@ public ContentType add(File localFile, ContentType localContentType, final ContentTypeAPI contentTypeAPI = clientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(localContentType).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() + .of(localContentType).build(); final var response = contentTypeAPI.createContentTypes(List.of(saveRequest)); return response.entity().stream() @@ -78,8 +79,8 @@ public ContentType edit(File localFile, ContentType localContentType, final ContentTypeAPI contentTypeAPI = clientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(localContentType).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() + .of(localContentType).build(); final var response = contentTypeAPI.updateContentType(localContentType.variable(), saveRequest); diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java index 3fedcf12ff6c..d30063689dad 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java @@ -2345,6 +2345,66 @@ void Test_Command_Content_Find_Authenticated_With_Token() { } } + /** + * Given scenario: Testing the Content Type push command and checking the base type. + * Expected Result: The base type on the server should the same as the one on the descriptor before + * and after the push operation. + * + * @throws IOException if an I/O error occurs during the execution of the test + */ + @Test + void Test_Push_Content_Type_Update_Checking_Base_Type() throws IOException { + + // Create a temporal folder for the workspace + var tempFolder = createTempFolder(); + final Workspace workspace = workspaceManager.getOrCreate(tempFolder); + + final CommandLine commandLine = createCommand(); + final StringWriter writer = new StringWriter(); + try (PrintWriter out = new PrintWriter(writer)) { + + commandLine.setOut(out); + commandLine.setErr(out); + + // ╔══════════════════════╗ + // ║ Preparing the data ║ + // ╚══════════════════════╝ + // Creating a HTML page content type file descriptor + final var newContentTypeResult = contentTypesTestHelper.createPageContentTypeDescriptor( + workspace + ); + + // ╔════════════════════════════════════════════════════════════╗ + // ║ Pushing the descriptor for the just created Content Type ║ + // ╚════════════════════════════════════════════════════════════╝ + var status = commandLine.execute(ContentTypeCommand.NAME, ContentTypePush.NAME, + workspace.contentTypes().toAbsolutePath().toString(), + "--fail-fast", "-e"); + Assertions.assertEquals(CommandLine.ExitCode.OK, status); + + // ╔════════════════════════════════════════════╗ + // ║ Validating the information on the server ║ + // ╚════════════════════════════════════════════╝ + var byVarName = contentTypesTestHelper.findContentType(newContentTypeResult.variable()); + Assertions.assertTrue(byVarName.isPresent()); + Assertions.assertEquals(BaseContentType.HTMLPAGE, byVarName.get().baseType()); + + // --- + // Now validating the auto update updated the content type descriptor + var updatedContentTypeDescriptor = this.mapperService.map( + newContentTypeResult.path().toFile(), + ContentType.class + ); + Assertions.assertNotNull(updatedContentTypeDescriptor.fields()); + Assertions.assertEquals( + BaseContentType.HTMLPAGE, updatedContentTypeDescriptor.baseType() + ); + + } finally { + deleteTempDirectory(tempFolder); + } + } + /** * Function to verify if a list of strings is sorted in ascending order (case-insensitive) * diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java index d4b9de8c86f3..3e2c135ce114 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java @@ -10,10 +10,12 @@ import com.dotcms.contenttype.model.field.ImmutableTextField; import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.contenttype.model.type.ImmutablePageContentType; import com.dotcms.contenttype.model.type.ImmutableSimpleContentType; import com.dotcms.contenttype.model.workflow.ImmutableWorkflow; import com.dotcms.model.ResponseEntityView; import com.dotcms.model.config.Workspace; +import com.dotcms.model.contenttype.AbstractSaveContentTypeRequest; import com.dotcms.model.contenttype.SaveContentTypeRequest; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.enterprise.context.ApplicationScoped; @@ -67,8 +69,8 @@ public String createContentTypeOnServer(final String detailPage, final String ur null, null, detailPage, urlMapPattern ); - final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). - from(contentType).build(); + final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() + .of(contentType).build(); contentTypeAPI.createContentTypes(List.of(saveRequest)); // Make sure the content type is created, and we are giving the server some time to process @@ -79,6 +81,18 @@ public String createContentTypeOnServer(final String detailPage, final String ur return contentType.variable(); } + /** + * Creates a HTML page content type descriptor in the given workspace. + * + * @param workspace The workspace in which the content type descriptor should be created. + * @return The result of the content type descriptor creation. + * @throws IOException If an error occurs while writing the content type descriptor to disk. + */ + public ContentTypeDescriptorCreationResult createPageContentTypeDescriptor(Workspace workspace) + throws IOException { + return createPageContentTypeDescriptor(workspace, null, null); + } + /** * Creates a content type descriptor in the given workspace. * @@ -158,6 +172,33 @@ public ContentTypeDescriptorCreationResult createContentTypeDescriptor( return new ContentTypeDescriptorCreationResult(contentType.variable(), path); } + /** + * Creates a HTML page content type descriptor in the given workspace. + * + * @param workspace The workspace in which the content type descriptor should be created. + * @param identifier The identifier of the content type. + * @param variable The variable of the content type. + * @return The result of the content type descriptor creation. + * @throws IOException If an error occurs while writing the content type descriptor to disk. + */ + public ContentTypeDescriptorCreationResult createPageContentTypeDescriptor( + Workspace workspace, final String identifier, final String variable) + throws IOException { + + final ImmutablePageContentType contentType = buildPageContentType( + identifier, variable + ); + + final ObjectMapper objectMapper = new ClientObjectMapper().getContext(null); + final String asString = objectMapper.writeValueAsString(contentType); + + final Path path = Path.of(workspace.contentTypes().toString(), + String.format("%s.json", contentType.variable())); + Files.writeString(path, asString); + + return new ContentTypeDescriptorCreationResult(contentType.variable(), path); + } + /** * Builds a content type object. * @@ -232,6 +273,76 @@ private ImmutableSimpleContentType buildContentType(final String identifier, return builder.build(); } + /** + * Builds a HTML page content type object. + * + * @param identifier The identifier of the content type. + * @param variable The variable of the content type. + * @return The content type object. + */ + private ImmutablePageContentType buildPageContentType( + final String identifier, final String variable) { + + final long millis = System.currentTimeMillis(); + final String contentTypeVariable = Objects.requireNonNullElseGet(variable, + () -> "var_" + millis); + + var builder = ImmutablePageContentType.builder() + .baseType(BaseContentType.HTMLPAGE) + .description("ct for testing.") + .name("name-" + contentTypeVariable) + .variable(contentTypeVariable) + .modDate(new Date()) + .fixed(true) + .iDate(new Date()) + .host("SYSTEM_HOST") + .folder("SYSTEM_FOLDER") + .addFields( + ImmutableBinaryField.builder() + .name("__bin_var__" + millis) + .fixed(false) + .listed(true) + .searchable(true) + .unique(false) + .indexed(true) + .readOnly(false) + .forceIncludeInApi(false) + .modDate(new Date()) + .required(false) + .variable("lol") + .sortOrder(1) + .dataType(DataTypes.SYSTEM).build(), + ImmutableTextField.builder() + .indexed(true) + .dataType(DataTypes.TEXT) + .fieldType("text") + .readOnly(false) + .required(true) + .searchable(true) + .listed(true) + .sortOrder(2) + .searchable(true) + .name("Name") + .variable("name") + .fixed(false) + .build() + ) + .workflows( + List.of( + ImmutableWorkflow.builder() + .id(SYSTEM_WORKFLOW_ID) + .variableName(SYSTEM_WORKFLOW_VARIABLE_NAME) + .build() + ) + ); + + if (identifier != null) { + builder.id(identifier); + } + + return builder.build(); + } + /** * Searches for a content type by its variable. *