From f14fa05addb7c3026bbdb90b3a736ee126eeed00 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Tue, 19 Nov 2024 11:07:22 -0300 Subject: [PATCH 01/32] #30669 add ContentImportForm --- .../v1/contentImport/ContentImportForm.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java new file mode 100644 index 000000000000..219f68ee63cc --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java @@ -0,0 +1,63 @@ +package com.dotcms.rest.api.v1.contentImport; + +import com.dotcms.repackage.javax.validation.constraints.NotNull; +import com.dotcms.rest.api.Validated; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * Form object that represents the JSON parameters for content import operations. + */ +public class ContentImportForm extends Validated { + + @NotNull(message = "Content Type is required") + private final String contentType; + + private final String language; + + @NotNull(message = "Workflow Action ID is required") + private final String workflowActionId; + + private final List fields; + + @JsonCreator + public ContentImportForm( + @JsonProperty("contentType") final String contentType, + @JsonProperty("language") final String language, + @JsonProperty("workflowActionId") final String workflowActionId, + @JsonProperty("fields") final List fields) { + super(); + this.contentType = contentType; + this.language = language; + this.workflowActionId = workflowActionId; + this.fields = fields; + } + + public String getContentType() { + return contentType; + } + + public String getLanguage() { + return language; + } + + public String getWorkflowActionId() { + return workflowActionId; + } + + public List getFields() { + return fields; + } + + @Override + public String toString() { + return "ContentImportForm{" + + "contentType='" + contentType + '\'' + + ", language='" + language + '\'' + + ", workflowActionId='" + workflowActionId + '\'' + + ", fields=" + fields + + '}'; + } +} \ No newline at end of file From 3f04f62ddeb1c6b4a4371a5059ea9b5b0eaacbb3 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Tue, 19 Nov 2024 11:07:56 -0300 Subject: [PATCH 02/32] #30669 add contentImport params --- .../v1/contentImport/ContentImportParams.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java new file mode 100644 index 000000000000..7f9f9738c62b --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java @@ -0,0 +1,76 @@ +package com.dotcms.rest.api.v1.contentImport; + +import com.dotcms.rest.api.Validated; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.dotmarketing.exception.DotDataException; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; + +import java.io.InputStream; + +/** + * Bean class that encapsulates the multipart form parameters for content import operations. + */ +public class ContentImportParams extends Validated { + + @FormDataParam("file") + private InputStream fileInputStream; + + @FormDataParam("file") + private FormDataContentDisposition contentDisposition; + + @FormDataParam("form") + private com.dotcms.rest.api.v1.contentImport.ContentImportForm form; + + @FormDataParam("form") + private String jsonForm; + + public InputStream getFileInputStream() { + return fileInputStream; + } + + public void setFileInputStream(InputStream fileInputStream) { + this.fileInputStream = fileInputStream; + } + + public FormDataContentDisposition getContentDisposition() { + return contentDisposition; + } + + public void setContentDisposition(FormDataContentDisposition contentDisposition) { + this.contentDisposition = contentDisposition; + } + + public String getJsonForm() { + return jsonForm; + } + + public void setForm(com.dotcms.rest.api.v1.contentImport.ContentImportForm form) { + this.form = form; + } + + /** + * Gets the parsed form object, lazily parsing the JSON if needed + * @return The ContentImportForm object + */ + public com.dotcms.rest.api.v1.contentImport.ContentImportForm getForm() throws DotDataException, JsonProcessingException { + if (null == form && (null != jsonForm && !jsonForm.isEmpty())) { + form = new ObjectMapper().readValue(jsonForm, com.dotcms.rest.api.v1.contentImport.ContentImportForm.class); + } + + if (form == null) { + throw new DotDataException("Import form parameters are required"); + } + return form; + } + + @Override + public String toString() { + return "ContentImportParams{" + + "form=" + form + + ", hasFile=" + (fileInputStream != null) + + ", fileName=" + (contentDisposition != null ? contentDisposition.getFileName() : "null") + + '}'; + } +} \ No newline at end of file From 3631fd433905e99191cec0f301b241057eb89d2e Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Tue, 19 Nov 2024 11:08:20 -0300 Subject: [PATCH 03/32] #30669 add contentimport helper --- .../v1/contentImport/ContentImportHelper.java | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java new file mode 100644 index 000000000000..6447441688eb --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java @@ -0,0 +1,213 @@ +package com.dotcms.rest.api.v1.contentImport; + +import com.dotcms.jobs.business.api.JobProcessorScanner; +import com.dotcms.jobs.business.api.JobQueueManagerAPI; +import com.dotcms.jobs.business.processor.JobProcessor; +import com.dotcms.jobs.business.processor.Queue; +import com.dotcms.rest.api.v1.temp.DotTempFile; +import com.dotmarketing.business.APILocator; +import com.dotmarketing.business.web.WebAPILocator; +import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.exception.DotSecurityException; +import com.dotmarketing.util.Logger; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.liferay.portal.model.User; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@ApplicationScoped +public class ContentImportHelper { + + private static final String CMD_PREVIEW = "preview"; + private static final String CMD_PUBLISH = "publish"; + + JobQueueManagerAPI jobQueueManagerAPI; + JobProcessorScanner scanner; + + @Inject + public ContentImportHelper( + JobQueueManagerAPI jobQueueManagerAPI, + JobProcessorScanner scanner) { + this.jobQueueManagerAPI = jobQueueManagerAPI; + this.scanner = scanner; + } + + public ContentImportHelper() { + } + + @PostConstruct + public void onInit() { + + if(!jobQueueManagerAPI.isStarted()){ + jobQueueManagerAPI.start(); + Logger.info(this.getClass(), "JobQueueManagerAPI started"); + } + final List> processors = scanner.discoverJobProcessors(); + processors.forEach(processor -> { + try { + if(!testInstantiation(processor)){ + return; + } + //registering the processor with the jobQueueManagerAPI + // lower case it to avoid case + if(processor.isAnnotationPresent(Queue.class)){ + final Queue queue = processor.getAnnotation(Queue.class); + jobQueueManagerAPI.registerProcessor(queue.value(), processor); + } else { + jobQueueManagerAPI.registerProcessor(processor.getName(), processor); + } + }catch (Exception e){ + Logger.error(this.getClass(), "Unable to register JobProcessor ", e); + } + }); + } + + /** + * Test if a processor can be instantiated + * @param processor The processor to tested + * @return true if the processor can be instantiated, false otherwise + */ + private boolean testInstantiation(Class processor) { + try { + final Constructor declaredConstructor = processor.getDeclaredConstructor(); + declaredConstructor.newInstance(); + return true; + } catch (Exception e) { + Logger.error(this.getClass(), String.format(" JobProcessor [%s] can not be instantiated and will be ignored.",processor.getName()), e); + } + return false; + } + + @PreDestroy + public void onDestroy() { + if(jobQueueManagerAPI.isStarted()){ + try { + jobQueueManagerAPI.close(); + Logger.info(this.getClass(), "JobQueueManagerAPI successfully closed"); + } catch (Exception e) { + Logger.error(this.getClass(), e.getMessage(), e); + } + } + } + + /** + * Creates a content import job with the provided parameters + * + * @param preview Whether this is a preview job + * @param queueName The name of the queue to submit the job to + * @param params The import parameters + * @param user The user initiating the import + * @param request The HTTP request + * @return The ID of the created job + */ + public String createJob( + final boolean preview, + final String queueName, + final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final User user, + final HttpServletRequest request) throws DotDataException, JsonProcessingException { + + params.getForm().checkValid(); + + final Map jobParameters = createJobParameters(preview, params, user, request); + processFileUpload(params, jobParameters, request); + + return jobQueueManagerAPI.createJob(queueName, jobParameters); + } + + /** + * Creates the job parameters map from the provided inputs + */ + private Map createJobParameters( + final boolean preview, + final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final User user, + final HttpServletRequest request) throws JsonProcessingException, DotDataException { + + final Map jobParameters = new HashMap<>(); + + // Add required parameters + jobParameters.put("cmd", preview ? CMD_PREVIEW : CMD_PUBLISH); + jobParameters.put("userId", user.getUserId()); + jobParameters.put("contentType", params.getForm().getContentType()); + jobParameters.put("workflowActionId", params.getForm().getWorkflowActionId()); + + // Add optional parameters + addOptionalParameters(params, jobParameters); + + // Add site information + addSiteInformation(request, jobParameters); + + return jobParameters; + } + + /** + * Adds optional parameters to the job parameters map if they are present + */ + private void addOptionalParameters( + final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final Map jobParameters) throws JsonProcessingException, DotDataException { + + final com.dotcms.rest.api.v1.contentImport.ContentImportForm form = params.getForm(); + + if (form.getLanguage() != null && !form.getLanguage().isEmpty()) { + jobParameters.put("language", form.getLanguage()); + } + if (form.getFields() != null && !form.getFields().isEmpty()) { + jobParameters.put("fields", form.getFields()); + } + } + + /** + * Adds current site information to the job parameters + */ + private void addSiteInformation( + final HttpServletRequest request, + final Map jobParameters){ + + final var currentHost = WebAPILocator.getHostWebAPI().getCurrentHostNoThrow(request); + jobParameters.put("siteName", currentHost.getHostname()); + jobParameters.put("siteIdentifier", currentHost.getIdentifier()); + } + + /** + * Processes the file upload and adds the necessary parameters to the job + */ + private void processFileUpload( + final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final Map jobParameters, + final HttpServletRequest request) throws DotDataException { + + validateFileUpload(params); + + try { + final DotTempFile tempFile = APILocator.getTempFileAPI().createTempFile( + params.getContentDisposition().getFileName(), + request, + params.getFileInputStream() + ); + jobParameters.put("tempFileId", tempFile.id); + jobParameters.put("requestFingerPrint", APILocator.getTempFileAPI().getRequestFingerprint(request)); + } catch (DotSecurityException e) { + Logger.error(this, "Error handling file upload", e); + throw new DotDataException("Error processing file upload: " + e.getMessage()); + } + } + + /** + * Validates that the file upload parameters are present + */ + private void validateFileUpload(final com.dotcms.rest.api.v1.contentImport.ContentImportParams params) throws DotDataException { + if (params.getFileInputStream() == null || params.getContentDisposition() == null) { + throw new DotDataException("CSV file is required"); + } + } +} \ No newline at end of file From a33f47c053f6bbd7f1895c8ba6f6555ca2eaa9e8 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Tue, 19 Nov 2024 11:08:40 -0300 Subject: [PATCH 04/32] #30669 add contentImport resource --- .../contentImport/ContentImportResource.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java new file mode 100644 index 000000000000..2515f76cb112 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java @@ -0,0 +1,63 @@ +package com.dotcms.rest.api.v1.contentImport; + +import com.dotcms.rest.ResponseEntityView; +import com.dotcms.rest.WebResource; +import com.dotmarketing.exception.DotDataException; +import com.fasterxml.jackson.core.JsonProcessingException; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; + + +@Path("/v1/content") +public class ContentImportResource { + + private final WebResource webResource; + private final com.dotcms.rest.api.v1.contentImport.ContentImportHelper importHelper; + private final String IMPORT_QUEUE_NAME = "importContentlets"; + + @Inject + public ContentImportResource(final com.dotcms.rest.api.v1.contentImport.ContentImportHelper importHelper) { + this(new WebResource(), importHelper); + } + + public ContentImportResource(WebResource webResource, com.dotcms.rest.api.v1.contentImport.ContentImportHelper importHelper) { + this.webResource = webResource; + this.importHelper = importHelper; + } + + /** + * Creates and enqueues a new content import job + * + * @param request HTTP request + * @param params The import parameters including: + * - file: The CSV file to import + * - params: JSON object containing: + * - contentType: The content type variable or ID (required) + * - language: The language code (e.g., "en-US") or ID + * - workflowActionId: The workflow action ID to apply (required) + * - fields: List of fields to use as key for updates + * @return Response containing the job ID + */ + @POST + @Path("/import") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public ResponseEntityView importContent( + @Context final HttpServletRequest request, + @BeanParam final com.dotcms.rest.api.v1.contentImport.ContentImportParams params) + throws DotDataException, JsonProcessingException { + final var initDataObject = new WebResource.InitBuilder(webResource) + .requiredBackendUser(true) + .requiredFrontendUser(false) + .requestAndResponse(request, null) + .rejectWhenNoUser(true) + .init(); + + final String jobId = importHelper.createJob(false, IMPORT_QUEUE_NAME, params, initDataObject.getUser(), request); + return new ResponseEntityView<>(jobId); + } +} \ No newline at end of file From 40311d9feb314ff292922a643361699994aaf1f1 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Tue, 19 Nov 2024 11:46:32 -0300 Subject: [PATCH 05/32] #30669 add params validation --- .../v1/contentImport/ContentImportHelper.java | 18 +++---------- .../v1/contentImport/ContentImportParams.java | 25 +++++++++++++------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java index 6447441688eb..52411427eddb 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java @@ -111,10 +111,11 @@ public void onDestroy() { public String createJob( final boolean preview, final String queueName, - final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final ContentImportParams params, final User user, final HttpServletRequest request) throws DotDataException, JsonProcessingException { + params.checkValid(); params.getForm().checkValid(); final Map jobParameters = createJobParameters(preview, params, user, request); @@ -154,7 +155,7 @@ private Map createJobParameters( */ private void addOptionalParameters( final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, - final Map jobParameters) throws JsonProcessingException, DotDataException { + final Map jobParameters) throws JsonProcessingException { final com.dotcms.rest.api.v1.contentImport.ContentImportForm form = params.getForm(); @@ -182,12 +183,10 @@ private void addSiteInformation( * Processes the file upload and adds the necessary parameters to the job */ private void processFileUpload( - final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final ContentImportParams params, final Map jobParameters, final HttpServletRequest request) throws DotDataException { - validateFileUpload(params); - try { final DotTempFile tempFile = APILocator.getTempFileAPI().createTempFile( params.getContentDisposition().getFileName(), @@ -201,13 +200,4 @@ private void processFileUpload( throw new DotDataException("Error processing file upload: " + e.getMessage()); } } - - /** - * Validates that the file upload parameters are present - */ - private void validateFileUpload(final com.dotcms.rest.api.v1.contentImport.ContentImportParams params) throws DotDataException { - if (params.getFileInputStream() == null || params.getContentDisposition() == null) { - throw new DotDataException("CSV file is required"); - } - } } \ No newline at end of file diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java index 7f9f9738c62b..0b33eb8c8dae 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java @@ -1,9 +1,11 @@ package com.dotcms.rest.api.v1.contentImport; +import com.dotcms.repackage.javax.validation.ValidationException; +import com.dotcms.repackage.javax.validation.constraints.NotNull; import com.dotcms.rest.api.Validated; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.dotmarketing.exception.DotDataException; +import net.minidev.json.annotate.JsonIgnore; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; @@ -14,15 +16,18 @@ */ public class ContentImportParams extends Validated { + @NotNull(message = "The file is required.") @FormDataParam("file") private InputStream fileInputStream; + @JsonIgnore @FormDataParam("file") private FormDataContentDisposition contentDisposition; @FormDataParam("form") - private com.dotcms.rest.api.v1.contentImport.ContentImportForm form; + private ContentImportForm form; + @NotNull(message = "The form data is required.") @FormDataParam("form") private String jsonForm; @@ -46,7 +51,7 @@ public String getJsonForm() { return jsonForm; } - public void setForm(com.dotcms.rest.api.v1.contentImport.ContentImportForm form) { + public void setForm(ContentImportForm form) { this.form = form; } @@ -54,14 +59,10 @@ public void setForm(com.dotcms.rest.api.v1.contentImport.ContentImportForm form) * Gets the parsed form object, lazily parsing the JSON if needed * @return The ContentImportForm object */ - public com.dotcms.rest.api.v1.contentImport.ContentImportForm getForm() throws DotDataException, JsonProcessingException { + public ContentImportForm getForm() throws JsonProcessingException { if (null == form && (null != jsonForm && !jsonForm.isEmpty())) { form = new ObjectMapper().readValue(jsonForm, com.dotcms.rest.api.v1.contentImport.ContentImportForm.class); } - - if (form == null) { - throw new DotDataException("Import form parameters are required"); - } return form; } @@ -73,4 +74,12 @@ public String toString() { ", fileName=" + (contentDisposition != null ? contentDisposition.getFileName() : "null") + '}'; } + + @Override + public void checkValid() { + super.checkValid(); + if (contentDisposition == null || contentDisposition.getFileName() == null) { + throw new ValidationException("The file must have a valid file name."); + } + } } \ No newline at end of file From c6e620f9aecc9e82e2dfcfbc334e0e719cb1aa15 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Tue, 19 Nov 2024 12:25:20 -0300 Subject: [PATCH 06/32] #30669 fix extract jobQueueManagerAPI init and destroy --- .../rest/api/v1/JobQueueManagerHelper.java | 82 +++++++++++++++++++ .../v1/contentImport/ContentImportHelper.java | 60 ++------------ .../rest/api/v1/job/JobQueueHelper.java | 65 ++------------- 3 files changed, 94 insertions(+), 113 deletions(-) create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java new file mode 100644 index 000000000000..ee791720941c --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java @@ -0,0 +1,82 @@ +package com.dotcms.rest.api.v1; + +import com.dotcms.jobs.business.api.JobProcessorScanner; +import com.dotcms.jobs.business.api.JobQueueManagerAPI; +import com.dotcms.jobs.business.processor.JobProcessor; +import com.dotcms.jobs.business.processor.Queue; +import com.dotmarketing.util.Logger; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.lang.reflect.Constructor; +import java.util.List; + +@ApplicationScoped +public class JobQueueManagerHelper { + + private JobProcessorScanner scanner; + + @Inject + public JobQueueManagerHelper(JobProcessorScanner scanner) { + this.scanner = scanner; + } + + public JobQueueManagerHelper() { + } + + public void registerProcessors(JobQueueManagerAPI jobQueueManagerAPI) { + if (!jobQueueManagerAPI.isStarted()) { + jobQueueManagerAPI.start(); + Logger.info(this.getClass(), "JobQueueManagerAPI started"); + } + + List> processors = scanner.discoverJobProcessors(); + processors.forEach(processor -> { + try { + if (!testInstantiation(processor)) { + return; + } + registerProcessor(jobQueueManagerAPI, processor); + } catch (Exception e) { + Logger.error(this.getClass(), "Unable to register JobProcessor ", e); + } + }); + } + + /** + * Test if a processor can be instantiated + * @param processor The processor to tested + * @return true if the processor can be instantiated, false otherwise + */ + private boolean testInstantiation(Class processor) { + try { + Constructor declaredConstructor = processor.getDeclaredConstructor(); + declaredConstructor.newInstance(); + return true; + } catch (Exception e) { + Logger.error(this.getClass(), String.format(" JobProcessor [%s] cannot be instantiated and will be ignored.", processor.getName()), e); + } + return false; + } + + private void registerProcessor(JobQueueManagerAPI jobQueueManagerAPI, Class processor) { + if (processor.isAnnotationPresent(Queue.class)) { + Queue queue = processor.getAnnotation(Queue.class); + jobQueueManagerAPI.registerProcessor(queue.value(), processor); + } else { + jobQueueManagerAPI.registerProcessor(processor.getName(), processor); + } + } + + public void shutdown(JobQueueManagerAPI jobQueueManagerAPI) { + if (jobQueueManagerAPI.isStarted()) { + try { + jobQueueManagerAPI.close(); + Logger.info(this.getClass(), "JobQueueManagerAPI successfully closed"); + } catch (Exception e) { + Logger.error(this.getClass(), e.getMessage(), e); + } + } + } +} + diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java index 52411427eddb..a7d1afb50e15 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java @@ -4,6 +4,7 @@ import com.dotcms.jobs.business.api.JobQueueManagerAPI; import com.dotcms.jobs.business.processor.JobProcessor; import com.dotcms.jobs.business.processor.Queue; +import com.dotcms.rest.api.v1.JobQueueManagerHelper; import com.dotcms.rest.api.v1.temp.DotTempFile; import com.dotmarketing.business.APILocator; import com.dotmarketing.business.web.WebAPILocator; @@ -29,15 +30,13 @@ public class ContentImportHelper { private static final String CMD_PREVIEW = "preview"; private static final String CMD_PUBLISH = "publish"; - JobQueueManagerAPI jobQueueManagerAPI; - JobProcessorScanner scanner; + private JobQueueManagerAPI jobQueueManagerAPI; + private JobQueueManagerHelper jobQueueManagerHelper; @Inject - public ContentImportHelper( - JobQueueManagerAPI jobQueueManagerAPI, - JobProcessorScanner scanner) { + public ContentImportHelper(JobQueueManagerAPI jobQueueManagerAPI, JobQueueManagerHelper jobQueueManagerHelper) { this.jobQueueManagerAPI = jobQueueManagerAPI; - this.scanner = scanner; + this.jobQueueManagerHelper = jobQueueManagerHelper; } public ContentImportHelper() { @@ -45,57 +44,12 @@ public ContentImportHelper() { @PostConstruct public void onInit() { - - if(!jobQueueManagerAPI.isStarted()){ - jobQueueManagerAPI.start(); - Logger.info(this.getClass(), "JobQueueManagerAPI started"); - } - final List> processors = scanner.discoverJobProcessors(); - processors.forEach(processor -> { - try { - if(!testInstantiation(processor)){ - return; - } - //registering the processor with the jobQueueManagerAPI - // lower case it to avoid case - if(processor.isAnnotationPresent(Queue.class)){ - final Queue queue = processor.getAnnotation(Queue.class); - jobQueueManagerAPI.registerProcessor(queue.value(), processor); - } else { - jobQueueManagerAPI.registerProcessor(processor.getName(), processor); - } - }catch (Exception e){ - Logger.error(this.getClass(), "Unable to register JobProcessor ", e); - } - }); - } - - /** - * Test if a processor can be instantiated - * @param processor The processor to tested - * @return true if the processor can be instantiated, false otherwise - */ - private boolean testInstantiation(Class processor) { - try { - final Constructor declaredConstructor = processor.getDeclaredConstructor(); - declaredConstructor.newInstance(); - return true; - } catch (Exception e) { - Logger.error(this.getClass(), String.format(" JobProcessor [%s] can not be instantiated and will be ignored.",processor.getName()), e); - } - return false; + jobQueueManagerHelper.registerProcessors(jobQueueManagerAPI); } @PreDestroy public void onDestroy() { - if(jobQueueManagerAPI.isStarted()){ - try { - jobQueueManagerAPI.close(); - Logger.info(this.getClass(), "JobQueueManagerAPI successfully closed"); - } catch (Exception e) { - Logger.error(this.getClass(), e.getMessage(), e); - } - } + jobQueueManagerHelper.shutdown(jobQueueManagerAPI); } /** diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java index 534986fb7b37..50e2c0c158d7 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java @@ -10,6 +10,7 @@ import com.dotcms.jobs.business.job.JobState; import com.dotcms.jobs.business.processor.JobProcessor; import com.dotcms.jobs.business.processor.Queue; +import com.dotcms.rest.api.v1.JobQueueManagerHelper; import com.dotcms.rest.api.v1.temp.DotTempFile; import com.dotcms.rest.api.v1.temp.TempFileAPI; import com.dotmarketing.business.APILocator; @@ -45,73 +46,17 @@ @ApplicationScoped public class JobQueueHelper { - JobQueueManagerAPI jobQueueManagerAPI; - - JobProcessorScanner scanner; + private JobQueueManagerAPI jobQueueManagerAPI; + private JobQueueManagerHelper jobQueueManagerHelper; public JobQueueHelper() { //default constructor Mandatory for CDI } - @PostConstruct - public void onInit() { - - if(!jobQueueManagerAPI.isStarted()){ - jobQueueManagerAPI.start(); - Logger.info(this.getClass(), "JobQueueManagerAPI started"); - } - final List> processors = scanner.discoverJobProcessors(); - processors.forEach(processor -> { - try { - if(!testInstantiation(processor)){ - return; - } - //registering the processor with the jobQueueManagerAPI - // lower case it to avoid case - if(processor.isAnnotationPresent(Queue.class)){ - final Queue queue = processor.getAnnotation(Queue.class); - jobQueueManagerAPI.registerProcessor(queue.value(), processor); - } else { - jobQueueManagerAPI.registerProcessor(processor.getName(), processor); - } - }catch (Exception e){ - Logger.error(this.getClass(), "Unable to register JobProcessor ", e); - } - }); - } - - /** - * Test if a processor can be instantiated - * @param processor The processor to tested - * @return true if the processor can be instantiated, false otherwise - */ - private boolean testInstantiation(Class processor) { - try { - final Constructor declaredConstructor = processor.getDeclaredConstructor(); - declaredConstructor.newInstance(); - return true; - } catch (Exception e) { - Logger.error(this.getClass(), String.format(" JobProcessor [%s] can not be instantiated and will be ignored.",processor.getName()), e); - } - return false; - } - - @PreDestroy - public void onDestroy() { - if(jobQueueManagerAPI.isStarted()){ - try { - jobQueueManagerAPI.close(); - Logger.info(this.getClass(), "JobQueueManagerAPI successfully closed"); - } catch (Exception e) { - Logger.error(this.getClass(), e.getMessage(), e); - } - } - } - @Inject - public JobQueueHelper(JobQueueManagerAPI jobQueueManagerAPI, JobProcessorScanner scanner) { + public JobQueueHelper(JobQueueManagerAPI jobQueueManagerAPI, JobQueueManagerHelper jobQueueManagerHelper) { this.jobQueueManagerAPI = jobQueueManagerAPI; - this.scanner = scanner; + this.jobQueueManagerHelper = jobQueueManagerHelper; } /** From ea4fa2e60e0d01c6c1aee000fbb1b91fb4dd4ae9 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Tue, 19 Nov 2024 12:30:11 -0300 Subject: [PATCH 07/32] Update JobQueueHelper.java --- .../com/dotcms/rest/api/v1/job/JobQueueHelper.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java index 50e2c0c158d7..87270c912e86 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java @@ -69,6 +69,16 @@ void registerProcessor(final String queueName, final Class Date: Thu, 21 Nov 2024 10:49:05 -0300 Subject: [PATCH 08/32] #30669 add ContentUploadResourceIntegrationTest --- .../ContentUploadResourceIntegrationTest.java | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java new file mode 100644 index 000000000000..e3c952d80049 --- /dev/null +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java @@ -0,0 +1,238 @@ +package com.dotcms.rest.api.v1.content.upload; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.dotcms.JUnit4WeldRunner; +import com.dotcms.Junit5WeldBaseTest; +import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.datagen.TestDataUtils; +import com.dotcms.datagen.TestUserUtils; +import com.dotcms.jobs.business.api.JobQueueManagerAPI; +import com.dotcms.jobs.business.job.Job; +import com.dotcms.jobs.business.processor.impl.ImportContentletsProcessor; +import com.dotcms.jobs.business.util.JobUtil; +import com.dotcms.rest.ResponseEntityView; +import com.dotcms.rest.api.v1.JobQueueManagerHelper; +import com.dotcms.rest.api.v1.contentImport.ContentImportForm; +import com.dotcms.rest.api.v1.contentImport.ContentImportHelper; +import com.dotcms.rest.api.v1.contentImport.ContentImportParams; +import com.dotcms.rest.api.v1.contentImport.ContentImportResource; +import com.dotcms.rest.exception.ValidationException; +import com.dotcms.util.IntegrationTestInitService; +import com.dotmarketing.beans.Host; +import com.dotmarketing.business.APILocator; +import com.dotmarketing.exception.DotDataException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.liferay.portal.model.User; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.jboss.weld.junit5.EnableWeld; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; + +import javax.enterprise.context.ApplicationScoped; +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +/** + * Integration test suite for content import functionality. + * Tests the ContentImportResource API endpoints for various scenarios. + */ +@ApplicationScoped +@EnableWeld +@RunWith(JUnit4WeldRunner.class) +public class ContentUploadResourceIntegrationTest extends Junit5WeldBaseTest { + + private static User adminUser; + private static HttpServletRequest request; + private static Host defaultSite; + private static ObjectMapper mapper; + private static ContentImportResource importResource; + + //TODO move to a common place + private final static String IMPORT_QUEUE_NAME = "importContentlets"; + private final static String CMD_PUBLISH = "publish"; + private final static String CMD_PREVIEW = "preview"; + + @BeforeAll + static void setUp() throws Exception { + IntegrationTestInitService.getInstance().init(); + + adminUser = TestUserUtils.getAdminUser(); + defaultSite = APILocator.getHostAPI().findDefaultHost(adminUser, false); + request = JobUtil.generateMockRequest(adminUser, defaultSite.getHostname()); + mapper = new ObjectMapper(); + + JobQueueManagerHelper jobQueueManagerHelper = mock(JobQueueManagerHelper.class); + JobQueueManagerAPI jobQueueManagerAPI = APILocator.getJobQueueManagerAPI(); + jobQueueManagerAPI.registerProcessor(IMPORT_QUEUE_NAME, ImportContentletsProcessor.class); + + ContentImportHelper helper = new ContentImportHelper(jobQueueManagerAPI, jobQueueManagerHelper); + importResource = new ContentImportResource(helper); + } + + /** + * Given: A valid CSV file and all required import parameters + * When: Importing content with valid content type, language, workflow action, and fields + * Then: The import job should be created successfully with all parameters properly set + */ + @Test + public void test_import_content_with_valid_params() throws IOException, DotDataException { + ContentType contentType = TestDataUtils.getRichTextLikeContentType(); + File csvFile = createTestCsvFile(); + + ContentImportForm form = createContentImportForm(contentType.name(), "1", "workflow-action-id", List.of("title")); + ContentImportParams params = createContentImportParams(csvFile, form); + + ResponseEntityView response = importResource.importContent(request, params); + validateSuccessfulResponse(response, contentType.name(), "1", List.of("title"), "workflow-action-id", CMD_PUBLISH); + } + + /** + * Given: A valid CSV file with only required parameters + * When: Importing content without optional parameters (language and fields) + * Then: The import job should be created successfully with only required parameters set + */ + @Test + public void test_import_content_without_optional_params() throws IOException, DotDataException { + ContentType contentType = TestDataUtils.getRichTextLikeContentType(); + File csvFile = createTestCsvFile(); + + ContentImportForm form = createContentImportForm(contentType.name(), null, "workflow-action-id-2", null); + ContentImportParams params = createContentImportParams(csvFile, form); + + ResponseEntityView response = importResource.importContent(request, params); + validateSuccessfulResponse(response, contentType.name(), null, null, "workflow-action-id-2", CMD_PUBLISH); + } + + /** + * Given: A valid CSV file but missing content type in form + * When: Attempting to import content without specifying content type + * Then: A ValidationException should be thrown + */ + @Test + public void test_import_content_without_content_type_in_form() throws IOException { + File csvFile = createTestCsvFile(); + ContentImportForm form = createContentImportForm(null, null, "workflow-action-id", null); + ContentImportParams params = createContentImportParams(csvFile, form); + + assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + } + + /** + * Given: A valid CSV file but missing workflow action in form + * When: Attempting to import content without specifying workflow action + * Then: A ValidationException should be thrown + */ + @Test + public void test_import_content_without_workflow_action_in_form() throws IOException { + ContentType contentType = TestDataUtils.getRichTextLikeContentType(); + File csvFile = createTestCsvFile(); + + ContentImportForm form = createContentImportForm(contentType.name(), null, null, null); + ContentImportParams params = createContentImportParams(csvFile, form); + + assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + } + + /** + * Given: Valid form data but no CSV file + * When: Attempting to import content without providing a file + * Then: A ValidationException should be thrown + */ + @Test + public void test_import_content_missing_file() throws Exception { + ContentType contentType = TestDataUtils.getRichTextLikeContentType(); + ContentImportForm form = createContentImportForm(contentType.name(), "1", "workflow-action-id", null); + + ContentImportParams params = new ContentImportParams(); + params.setJsonForm(mapper.writeValueAsString(form)); + + assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + } + + /** + * Given: A valid CSV file but no form data + * When: Attempting to import content without providing form data + * Then: A ValidationException should be thrown + */ + @Test + public void test_import_content_missing_form() throws Exception { + File csvFile = createTestCsvFile(); + + ContentImportParams params = new ContentImportParams(); + params.setFileInputStream(new FileInputStream(csvFile)); + params.setContentDisposition(createContentDisposition(csvFile.getName())); + + assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + } + + /** + * Helper method to validate successful import response. + * Given: A response from a successful content import + * When: Validating the job parameters + * Then: All expected parameters should match the provided values + */ + private static void validateSuccessfulResponse(ResponseEntityView response, String expectedContentType, String expectedLanguage, List expectedFields, String expectedWorkflowActionId, String expectedCommand) throws DotDataException { + // Validate response object and job ID existence + assertNotNull(response, "Response should not be null"); + assertNotNull(response.getEntity(), "Job ID should not be null"); + assertFalse(response.getEntity().isEmpty(), "Job ID should be a non-empty string"); + + // Retrieve and validate job exists in the queue + Job job = APILocator.getJobQueueManagerAPI().getJob(response.getEntity()); + assertNotNull(job, "Job should exist in queue"); + + // Validate core import parameters + assertEquals(expectedContentType, job.parameters().get("contentType"), "Job should contain correct content type"); + assertEquals(expectedLanguage, job.parameters().get("language"), "Job should contain correct language"); + assertEquals(expectedWorkflowActionId, job.parameters().get("workflowActionId"), "Job should contain correct workflow action"); + + // Validate job configuration and metadata + assertEquals(IMPORT_QUEUE_NAME, job.queueName(), "Job should be in the correct queue"); + assertEquals(expectedCommand, job.parameters().get("cmd").toString(), "Job command should be 'publish'"); + assertEquals(defaultSite.getIdentifier(), job.parameters().get("siteIdentifier"), "Job should contain correct site identifier"); + assertEquals(adminUser.getUserId(), job.parameters().get("userId"), "Job should contain correct user ID"); + + // Validate optional fields parameter + if (expectedFields != null) { + assertTrue(job.parameters().containsKey("fields"), "Job should contain fields"); + assertEquals(expectedFields, job.parameters().get("fields"), "Job should contain correct fields"); + } else { + assertFalse(job.parameters().containsKey("fields"), "Job should not contain fields"); + } + } + + //TODO move to a common place + private static File createTestCsvFile() throws IOException { + String csv = "title,body\nTest Title 1,Test Body 1\nTest Title 2,Test Body 2\n"; + File csvFile = File.createTempFile("test", ".csv"); + Files.write(csvFile.toPath(), csv.getBytes()); + return csvFile; + } + + private static FormDataContentDisposition createContentDisposition(String filename) { + return FormDataContentDisposition + .name("file") + .fileName(filename) + .size(100L) + .build(); + } + + private static ContentImportParams createContentImportParams(File file, ContentImportForm form) throws IOException { + ContentImportParams params = new ContentImportParams(); + params.setFileInputStream(new FileInputStream(file)); + params.setContentDisposition(createContentDisposition(file.getName())); + params.setJsonForm(mapper.writeValueAsString(form)); + return params; + } + + private static ContentImportForm createContentImportForm(String contentType, String language, String workflowActionId, List fields) { + return new ContentImportForm(contentType, language, workflowActionId, fields); + } +} From 9c82d6669ad2aa50be4345a703c87ce728ae20ce Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 10:51:08 -0300 Subject: [PATCH 09/32] #30669 remove boolean param --- .../v1/contentImport/ContentImportHelper.java | 20 +++++++++---------- .../v1/contentImport/ContentImportParams.java | 6 +++++- .../contentImport/ContentImportResource.java | 15 +++++++++----- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java index a7d1afb50e15..aeb46c602b4b 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java @@ -27,19 +27,17 @@ @ApplicationScoped public class ContentImportHelper { - private static final String CMD_PREVIEW = "preview"; - private static final String CMD_PUBLISH = "publish"; - private JobQueueManagerAPI jobQueueManagerAPI; private JobQueueManagerHelper jobQueueManagerHelper; @Inject - public ContentImportHelper(JobQueueManagerAPI jobQueueManagerAPI, JobQueueManagerHelper jobQueueManagerHelper) { + public ContentImportHelper(final JobQueueManagerAPI jobQueueManagerAPI, final JobQueueManagerHelper jobQueueManagerHelper) { this.jobQueueManagerAPI = jobQueueManagerAPI; this.jobQueueManagerHelper = jobQueueManagerHelper; } public ContentImportHelper() { + //default constructor Mandatory for CDI } @PostConstruct @@ -55,7 +53,7 @@ public void onDestroy() { /** * Creates a content import job with the provided parameters * - * @param preview Whether this is a preview job + * @param command Whether this is a preview job * @param queueName The name of the queue to submit the job to * @param params The import parameters * @param user The user initiating the import @@ -63,7 +61,7 @@ public void onDestroy() { * @return The ID of the created job */ public String createJob( - final boolean preview, + final String command, final String queueName, final ContentImportParams params, final User user, @@ -72,7 +70,7 @@ public String createJob( params.checkValid(); params.getForm().checkValid(); - final Map jobParameters = createJobParameters(preview, params, user, request); + final Map jobParameters = createJobParameters(command, params, user, request); processFileUpload(params, jobParameters, request); return jobQueueManagerAPI.createJob(queueName, jobParameters); @@ -82,15 +80,15 @@ public String createJob( * Creates the job parameters map from the provided inputs */ private Map createJobParameters( - final boolean preview, + final String command, final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, final User user, - final HttpServletRequest request) throws JsonProcessingException, DotDataException { + final HttpServletRequest request) throws JsonProcessingException { final Map jobParameters = new HashMap<>(); // Add required parameters - jobParameters.put("cmd", preview ? CMD_PREVIEW : CMD_PUBLISH); + jobParameters.put("cmd", command); jobParameters.put("userId", user.getUserId()); jobParameters.put("contentType", params.getForm().getContentType()); jobParameters.put("workflowActionId", params.getForm().getWorkflowActionId()); @@ -111,7 +109,7 @@ private void addOptionalParameters( final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, final Map jobParameters) throws JsonProcessingException { - final com.dotcms.rest.api.v1.contentImport.ContentImportForm form = params.getForm(); + final ContentImportForm form = params.getForm(); if (form.getLanguage() != null && !form.getLanguage().isEmpty()) { jobParameters.put("language", form.getLanguage()); diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java index 0b33eb8c8dae..27a69340e9b0 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java @@ -47,6 +47,10 @@ public void setContentDisposition(FormDataContentDisposition contentDisposition) this.contentDisposition = contentDisposition; } + public void setJsonForm(String jsonForm) { + this.jsonForm = jsonForm; + } + public String getJsonForm() { return jsonForm; } @@ -61,7 +65,7 @@ public void setForm(ContentImportForm form) { */ public ContentImportForm getForm() throws JsonProcessingException { if (null == form && (null != jsonForm && !jsonForm.isEmpty())) { - form = new ObjectMapper().readValue(jsonForm, com.dotcms.rest.api.v1.contentImport.ContentImportForm.class); + form = new ObjectMapper().readValue(jsonForm, ContentImportForm.class); } return form; } diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java index 2515f76cb112..48c2a14bdb2c 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java @@ -4,6 +4,7 @@ import com.dotcms.rest.WebResource; import com.dotmarketing.exception.DotDataException; import com.fasterxml.jackson.core.JsonProcessingException; +import graphql.VisibleForTesting; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -16,15 +17,19 @@ public class ContentImportResource { private final WebResource webResource; - private final com.dotcms.rest.api.v1.contentImport.ContentImportHelper importHelper; + private final ContentImportHelper importHelper; private final String IMPORT_QUEUE_NAME = "importContentlets"; + //TODO move to a common place + private static final String CMD_PREVIEW = "preview"; + private static final String CMD_PUBLISH = "publish"; + @Inject - public ContentImportResource(final com.dotcms.rest.api.v1.contentImport.ContentImportHelper importHelper) { + public ContentImportResource(final ContentImportHelper importHelper) { this(new WebResource(), importHelper); } - public ContentImportResource(WebResource webResource, com.dotcms.rest.api.v1.contentImport.ContentImportHelper importHelper) { + public ContentImportResource(final WebResource webResource, final ContentImportHelper importHelper) { this.webResource = webResource; this.importHelper = importHelper; } @@ -48,7 +53,7 @@ public ContentImportResource(WebResource webResource, com.dotcms.rest.api.v1.con @Produces(MediaType.APPLICATION_JSON) public ResponseEntityView importContent( @Context final HttpServletRequest request, - @BeanParam final com.dotcms.rest.api.v1.contentImport.ContentImportParams params) + @BeanParam final ContentImportParams params) throws DotDataException, JsonProcessingException { final var initDataObject = new WebResource.InitBuilder(webResource) .requiredBackendUser(true) @@ -57,7 +62,7 @@ public ResponseEntityView importContent( .rejectWhenNoUser(true) .init(); - final String jobId = importHelper.createJob(false, IMPORT_QUEUE_NAME, params, initDataObject.getUser(), request); + final String jobId = importHelper.createJob(CMD_PUBLISH, IMPORT_QUEUE_NAME, params, initDataObject.getUser(), request); return new ResponseEntityView<>(jobId); } } \ No newline at end of file From b7c9c9d8bd88617db3e6316e7db22ccaee691748 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 13:09:04 -0300 Subject: [PATCH 10/32] #30669 add response to initBuilder --- .../contentImport/ContentImportResource.java | 4 +++- .../ContentUploadResourceIntegrationTest.java | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java index 48c2a14bdb2c..b1fe81a0f573 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java @@ -8,6 +8,7 @@ import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -53,12 +54,13 @@ public ContentImportResource(final WebResource webResource, final ContentImportH @Produces(MediaType.APPLICATION_JSON) public ResponseEntityView importContent( @Context final HttpServletRequest request, + @Context final HttpServletResponse response, @BeanParam final ContentImportParams params) throws DotDataException, JsonProcessingException { final var initDataObject = new WebResource.InitBuilder(webResource) .requiredBackendUser(true) .requiredFrontendUser(false) - .requestAndResponse(request, null) + .requestAndResponse(request, response) .rejectWhenNoUser(true) .init(); diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java index e3c952d80049..4ca219b2f576 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java @@ -12,6 +12,7 @@ import com.dotcms.jobs.business.job.Job; import com.dotcms.jobs.business.processor.impl.ImportContentletsProcessor; import com.dotcms.jobs.business.util.JobUtil; +import com.dotcms.mock.response.MockHttpResponse; import com.dotcms.rest.ResponseEntityView; import com.dotcms.rest.api.v1.JobQueueManagerHelper; import com.dotcms.rest.api.v1.contentImport.ContentImportForm; @@ -33,6 +34,7 @@ import javax.enterprise.context.ApplicationScoped; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -50,6 +52,7 @@ public class ContentUploadResourceIntegrationTest extends Junit5WeldBaseTest { private static User adminUser; private static HttpServletRequest request; + private static HttpServletResponse response; private static Host defaultSite; private static ObjectMapper mapper; private static ContentImportResource importResource; @@ -66,6 +69,7 @@ static void setUp() throws Exception { adminUser = TestUserUtils.getAdminUser(); defaultSite = APILocator.getHostAPI().findDefaultHost(adminUser, false); request = JobUtil.generateMockRequest(adminUser, defaultSite.getHostname()); + response = new MockHttpResponse(); mapper = new ObjectMapper(); JobQueueManagerHelper jobQueueManagerHelper = mock(JobQueueManagerHelper.class); @@ -89,8 +93,8 @@ public void test_import_content_with_valid_params() throws IOException, DotDataE ContentImportForm form = createContentImportForm(contentType.name(), "1", "workflow-action-id", List.of("title")); ContentImportParams params = createContentImportParams(csvFile, form); - ResponseEntityView response = importResource.importContent(request, params); - validateSuccessfulResponse(response, contentType.name(), "1", List.of("title"), "workflow-action-id", CMD_PUBLISH); + ResponseEntityView importContentResponse = importResource.importContent(request, response, params); + validateSuccessfulResponse(importContentResponse, contentType.name(), "1", List.of("title"), "workflow-action-id", CMD_PUBLISH); } /** @@ -106,8 +110,8 @@ public void test_import_content_without_optional_params() throws IOException, Do ContentImportForm form = createContentImportForm(contentType.name(), null, "workflow-action-id-2", null); ContentImportParams params = createContentImportParams(csvFile, form); - ResponseEntityView response = importResource.importContent(request, params); - validateSuccessfulResponse(response, contentType.name(), null, null, "workflow-action-id-2", CMD_PUBLISH); + ResponseEntityView importContentResponse = importResource.importContent(request, response, params); + validateSuccessfulResponse(importContentResponse, contentType.name(), null, null, "workflow-action-id-2", CMD_PUBLISH); } /** @@ -121,7 +125,7 @@ public void test_import_content_without_content_type_in_form() throws IOExceptio ContentImportForm form = createContentImportForm(null, null, "workflow-action-id", null); ContentImportParams params = createContentImportParams(csvFile, form); - assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params)); } /** @@ -137,7 +141,7 @@ public void test_import_content_without_workflow_action_in_form() throws IOExcep ContentImportForm form = createContentImportForm(contentType.name(), null, null, null); ContentImportParams params = createContentImportParams(csvFile, form); - assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params)); } /** @@ -153,7 +157,7 @@ public void test_import_content_missing_file() throws Exception { ContentImportParams params = new ContentImportParams(); params.setJsonForm(mapper.writeValueAsString(form)); - assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params)); } /** @@ -169,7 +173,7 @@ public void test_import_content_missing_form() throws Exception { params.setFileInputStream(new FileInputStream(csvFile)); params.setContentDisposition(createContentDisposition(csvFile.getName())); - assertThrows(ValidationException.class, () -> importResource.importContent(request, params)); + assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params)); } /** From 126f0ea03baff94a2c845004098b8a6420264e31 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 19:21:37 -0300 Subject: [PATCH 11/32] #30669 update JobQueueManagerHelper --- .../rest/api/v1/JobQueueManagerHelper.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java index ee791720941c..bf52c785de57 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java @@ -4,6 +4,7 @@ import com.dotcms.jobs.business.api.JobQueueManagerAPI; import com.dotcms.jobs.business.processor.JobProcessor; import com.dotcms.jobs.business.processor.Queue; +import com.dotcms.util.AnnotationUtils; import com.dotmarketing.util.Logger; import javax.enterprise.context.ApplicationScoped; @@ -17,14 +18,15 @@ public class JobQueueManagerHelper { private JobProcessorScanner scanner; @Inject - public JobQueueManagerHelper(JobProcessorScanner scanner) { + public JobQueueManagerHelper(final JobProcessorScanner scanner) { this.scanner = scanner; } public JobQueueManagerHelper() { + // Default constructor required by CDI } - public void registerProcessors(JobQueueManagerAPI jobQueueManagerAPI) { + public void registerProcessors(final JobQueueManagerAPI jobQueueManagerAPI) { if (!jobQueueManagerAPI.isStarted()) { jobQueueManagerAPI.start(); Logger.info(this.getClass(), "JobQueueManagerAPI started"); @@ -48,7 +50,7 @@ public void registerProcessors(JobQueueManagerAPI jobQueueManagerAPI) { * @param processor The processor to tested * @return true if the processor can be instantiated, false otherwise */ - private boolean testInstantiation(Class processor) { + private boolean testInstantiation(final Class processor) { try { Constructor declaredConstructor = processor.getDeclaredConstructor(); declaredConstructor.newInstance(); @@ -59,16 +61,16 @@ private boolean testInstantiation(Class processor) { return false; } - private void registerProcessor(JobQueueManagerAPI jobQueueManagerAPI, Class processor) { - if (processor.isAnnotationPresent(Queue.class)) { - Queue queue = processor.getAnnotation(Queue.class); + private void registerProcessor(final JobQueueManagerAPI jobQueueManagerAPI, final Class processor) { + Queue queue = AnnotationUtils.getBeanAnnotation(processor, Queue.class); + if (null != queue) { jobQueueManagerAPI.registerProcessor(queue.value(), processor); } else { jobQueueManagerAPI.registerProcessor(processor.getName(), processor); } } - public void shutdown(JobQueueManagerAPI jobQueueManagerAPI) { + public void shutdown(final JobQueueManagerAPI jobQueueManagerAPI) { if (jobQueueManagerAPI.isStarted()) { try { jobQueueManagerAPI.close(); From 5db3bd8673bcbdc8be4840faad954bb5449fcebc Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 19:22:39 -0300 Subject: [PATCH 12/32] #30669 add catch for JobValidationException --- .../v1/contentImport/ContentImportResource.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java index b1fe81a0f573..3d1d3c068657 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java @@ -1,7 +1,9 @@ package com.dotcms.rest.api.v1.contentImport; +import com.dotcms.jobs.business.error.JobValidationException; import com.dotcms.rest.ResponseEntityView; import com.dotcms.rest.WebResource; +import com.dotcms.rest.exception.mapper.ExceptionMapperUtil; import com.dotmarketing.exception.DotDataException; import com.fasterxml.jackson.core.JsonProcessingException; import graphql.VisibleForTesting; @@ -12,6 +14,7 @@ import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; @Path("/v1/content") @@ -49,10 +52,10 @@ public ContentImportResource(final WebResource webResource, final ContentImportH * @return Response containing the job ID */ @POST - @Path("/import") + @Path("/_import") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) - public ResponseEntityView importContent( + public Response importContent( @Context final HttpServletRequest request, @Context final HttpServletResponse response, @BeanParam final ContentImportParams params) @@ -64,7 +67,11 @@ public ResponseEntityView importContent( .rejectWhenNoUser(true) .init(); - final String jobId = importHelper.createJob(CMD_PUBLISH, IMPORT_QUEUE_NAME, params, initDataObject.getUser(), request); - return new ResponseEntityView<>(jobId); + try{ + final String jobId = importHelper.createJob(CMD_PUBLISH, IMPORT_QUEUE_NAME, params, initDataObject.getUser(), request); + return Response.ok(new ResponseEntityView<>(jobId)).build(); + }catch (JobValidationException e) { + return ExceptionMapperUtil.createResponse(null, e.getMessage()); + } } } \ No newline at end of file From dc3ad8cf1a686f4b26c2565225e059f0bf26ed47 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 19:23:14 -0300 Subject: [PATCH 13/32] #30669 move checkValid call --- .../dotcms/rest/api/v1/contentImport/ContentImportForm.java | 5 +++-- .../rest/api/v1/contentImport/ContentImportHelper.java | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java index 219f68ee63cc..57f397b203cb 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java @@ -12,12 +12,12 @@ */ public class ContentImportForm extends Validated { - @NotNull(message = "Content Type is required") + @NotNull(message = "A Content Type id or variable is required") private final String contentType; private final String language; - @NotNull(message = "Workflow Action ID is required") + @NotNull(message = "A Workflow Action id is required") private final String workflowActionId; private final List fields; @@ -33,6 +33,7 @@ public ContentImportForm( this.language = language; this.workflowActionId = workflowActionId; this.fields = fields; + this.checkValid(); } public String getContentType() { diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java index aeb46c602b4b..79ef8dd4723a 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java @@ -68,7 +68,6 @@ public String createJob( final HttpServletRequest request) throws DotDataException, JsonProcessingException { params.checkValid(); - params.getForm().checkValid(); final Map jobParameters = createJobParameters(command, params, user, request); processFileUpload(params, jobParameters, request); From 186a28bbb521f3790dd3d7c49a24f280a3f6a184 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 19:23:40 -0300 Subject: [PATCH 14/32] #30669 update integreation tests --- .../ContentUploadResourceIntegrationTest.java | 231 ++++++++++++++---- 1 file changed, 187 insertions(+), 44 deletions(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java index 4ca219b2f576..932f0b777f7a 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java @@ -24,6 +24,7 @@ import com.dotmarketing.beans.Host; import com.dotmarketing.business.APILocator; import com.dotmarketing.exception.DotDataException; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.liferay.portal.model.User; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; @@ -35,6 +36,7 @@ import javax.enterprise.context.ApplicationScoped; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Response; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -57,10 +59,8 @@ public class ContentUploadResourceIntegrationTest extends Junit5WeldBaseTest { private static ObjectMapper mapper; private static ContentImportResource importResource; - //TODO move to a common place private final static String IMPORT_QUEUE_NAME = "importContentlets"; private final static String CMD_PUBLISH = "publish"; - private final static String CMD_PREVIEW = "preview"; @BeforeAll static void setUp() throws Exception { @@ -81,9 +81,12 @@ static void setUp() throws Exception { } /** - * Given: A valid CSV file and all required import parameters - * When: Importing content with valid content type, language, workflow action, and fields - * Then: The import job should be created successfully with all parameters properly set + * Scenario: Import content with all parameters being passed (csv file, content type, language, workflow action, and fields). + *

+ * Expected: A new import job should be created successfully with all parameters properly set. + * + * @throws IOException if there's an error with file operations + * @throws DotDataException if there's an error with dotCMS data operations */ @Test public void test_import_content_with_valid_params() throws IOException, DotDataException { @@ -93,64 +96,129 @@ public void test_import_content_with_valid_params() throws IOException, DotDataE ContentImportForm form = createContentImportForm(contentType.name(), "1", "workflow-action-id", List.of("title")); ContentImportParams params = createContentImportParams(csvFile, form); - ResponseEntityView importContentResponse = importResource.importContent(request, response, params); + Response importContentResponse = importResource.importContent(request, response, params); validateSuccessfulResponse(importContentResponse, contentType.name(), "1", List.of("title"), "workflow-action-id", CMD_PUBLISH); } /** - * Given: A valid CSV file with only required parameters - * When: Importing content without optional parameters (language and fields) - * Then: The import job should be created successfully with only required parameters set + * Scenario: Import content with all parameters using the language ISO code + *

+ * Expected: A new import job should be created successfully with all parameters properly set. + * + * @throws IOException if there's an error with file operations + * @throws DotDataException if there's an error with dotCMS data operations */ @Test - public void test_import_content_without_optional_params() throws IOException, DotDataException { + public void test_import_content_with_language_iso_code() throws IOException, DotDataException { ContentType contentType = TestDataUtils.getRichTextLikeContentType(); File csvFile = createTestCsvFile(); - ContentImportForm form = createContentImportForm(contentType.name(), null, "workflow-action-id-2", null); + ContentImportForm form = createContentImportForm(contentType.name(), "en-us", "workflow-action-id", List.of("title")); ContentImportParams params = createContentImportParams(csvFile, form); - ResponseEntityView importContentResponse = importResource.importContent(request, response, params); - validateSuccessfulResponse(importContentResponse, contentType.name(), null, null, "workflow-action-id-2", CMD_PUBLISH); + Response importContentResponse = importResource.importContent(request, response, params); + validateSuccessfulResponse(importContentResponse, contentType.name(), "en-us", List.of("title"), "workflow-action-id", CMD_PUBLISH); } /** - * Given: A valid CSV file but missing content type in form - * When: Attempting to import content without specifying content type - * Then: A ValidationException should be thrown + * Scenario: Attempt to import content without specifying language and fields parameters. + *

+ * Expected: The import request should fail with BAD_REQUEST (400) status code. + * A key identifying the different Language versions of the same content must be defined + * when importing multilingual files + * + * @throws IOException if there's an error with file operations + * @throws DotDataException if there's an error with dotCMS data operations */ @Test - public void test_import_content_without_content_type_in_form() throws IOException { + public void test_import_content_without_language_and_field_params() throws IOException, DotDataException { + ContentType contentType = TestDataUtils.getRichTextLikeContentType(); File csvFile = createTestCsvFile(); - ContentImportForm form = createContentImportForm(null, null, "workflow-action-id", null); + + ContentImportForm form = createContentImportForm(contentType.name(), null, "workflow-action-id-2", null); ContentImportParams params = createContentImportParams(csvFile, form); - assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params)); + // Assert that the response status is BAD_REQUEST (400) + assertBadRequestResponse(importResource.importContent(request, response, params)); + } /** - * Given: A valid CSV file but missing workflow action in form - * When: Attempting to import content without specifying workflow action - * Then: A ValidationException should be thrown + * Scenario: Attempt to import content specifying a non-existing language. + *

+ * Expected: The import request should fail with BAD_REQUEST (400) status code. + * + * @throws IOException if there's an error with file operations + * @throws DotDataException if there's an error with dotCMS data operations */ @Test - public void test_import_content_without_workflow_action_in_form() throws IOException { + public void test_import_content_with_invalid_language() throws IOException, DotDataException { ContentType contentType = TestDataUtils.getRichTextLikeContentType(); File csvFile = createTestCsvFile(); - ContentImportForm form = createContentImportForm(contentType.name(), null, null, null); + ContentImportForm form = createContentImportForm(contentType.name(), "123", "workflow-action-id-2", null); ContentImportParams params = createContentImportParams(csvFile, form); - assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params)); + assertBadRequestResponse(importResource.importContent(request, response, params)); + } + + /** + * Scenario: Attempt to import content specifying a non-existing content-type. + *

+ * Expected: The import request should fail with BAD_REQUEST (400) status code since the content type is invalid. + * + * @throws IOException if there's an error with file operations + * @throws DotDataException if there's an error with dotCMS data operations + */ + @Test + public void test_import_content_with_invalid_content_type() throws IOException, DotDataException { + File csvFile = createTestCsvFile(); + + ContentImportForm form = createContentImportForm("doesNotExist", "12345", "workflow-action-id-2", null); + ContentImportParams params = createContentImportParams(csvFile, form); + + assertBadRequestResponse(importResource.importContent(request, response, params)); + } + + /** + * Scenario: Attempt to create an import form without specifying the required content type parameter. + *

+ * Expected: A ValidationException should be thrown since content type is a required parameter + * for content import operations. + * A Content Type id or variable is required. + * + * @throws ValidationException when attempting to create a form without content type + */ + @Test + public void test_import_content_without_content_type_in_form() { + assertThrows(ValidationException.class, () -> createContentImportForm(null, null, "workflow-action-id", null)); } /** - * Given: Valid form data but no CSV file - * When: Attempting to import content without providing a file - * Then: A ValidationException should be thrown + * Scenario: Attempt to create an import form without specifying the required workflow action parameter. + *

+ * Expected: A ValidationException should be thrown since workflow action is a required parameter + * for content import operations. + * + * @throws ValidationException when attempting to create a form without workflow action */ @Test - public void test_import_content_missing_file() throws Exception { + public void test_import_content_without_workflow_action_in_form() { + ContentType contentType = TestDataUtils.getRichTextLikeContentType(); + assertThrows(ValidationException.class, () -> createContentImportForm(contentType.name(), null, null, null)); + } + + /** + * Scenario: Attempt to import content with valid form data but without providing the required CSV file. + *

+ * Expected: A ValidationException should be thrown since the file is a required parameter + * for content import operations. + * + * @throws JsonProcessingException if there's an error during JSON serialization + * @throws ValidationException when attempting to import content without setting the file + */ + @Test + public void test_import_content_missing_file() throws JsonProcessingException { ContentType contentType = TestDataUtils.getRichTextLikeContentType(); ContentImportForm form = createContentImportForm(contentType.name(), "1", "workflow-action-id", null); @@ -161,12 +229,16 @@ public void test_import_content_missing_file() throws Exception { } /** - * Given: A valid CSV file but no form data - * When: Attempting to import content without providing form data - * Then: A ValidationException should be thrown + * Scenario: Attempt to import content with a valid CSV file but without providing the required form data. + *

+ * Expected: A ValidationException should be thrown since form data is a required parameter + * for content import operations. + * + * @throws IOException if there's an error during file operations + * @throws ValidationException when attempting to import content without setting form data */ @Test - public void test_import_content_missing_form() throws Exception { + public void test_import_content_missing_form() throws IOException { File csvFile = createTestCsvFile(); ContentImportParams params = new ContentImportParams(); @@ -177,19 +249,44 @@ public void test_import_content_missing_form() throws Exception { } /** - * Helper method to validate successful import response. - * Given: A response from a successful content import - * When: Validating the job parameters - * Then: All expected parameters should match the provided values + * Validates the response and job parameters from a content import operation. + *

+ * Performs the following validations: + * - Response status is OK (200) + * - Response entity is properly formatted + * - Job exists in the queue + * - All job parameters match expected values + * - Optional fields are properly set when provided + * + * @param response The Response object from the import operation + * @param expectedContentType The content type that should be set in the job + * @param expectedLanguage The language ID that should be set in the job + * @param expectedFields List of fields that should be included in the job, or null if no fields expected + * @param expectedWorkflowActionId The workflow action ID that should be set in the job + * @param expectedCommand The command that should be set in the job (usually 'publish') + * @throws DotDataException if there's an error retrieving the job from the queue + * @throws AssertionError if any validation fails */ - private static void validateSuccessfulResponse(ResponseEntityView response, String expectedContentType, String expectedLanguage, List expectedFields, String expectedWorkflowActionId, String expectedCommand) throws DotDataException { + private static void validateSuccessfulResponse(Response response, String expectedContentType, String expectedLanguage, List expectedFields, String expectedWorkflowActionId, String expectedCommand) throws DotDataException { + // Validate Response object + assertNotNull(response, "Import response should not be null"); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), "Response status should be OK"); + + // Check and cast the entity safely + Object entity = response.getEntity(); + assertNotNull(entity, "Response entity should not be null"); + assertInstanceOf(ResponseEntityView.class, entity, "Entity should be of type ResponseEntityView"); + + @SuppressWarnings("unchecked") + ResponseEntityView responseEntityView = (ResponseEntityView) entity; + // Validate response object and job ID existence - assertNotNull(response, "Response should not be null"); - assertNotNull(response.getEntity(), "Job ID should not be null"); - assertFalse(response.getEntity().isEmpty(), "Job ID should be a non-empty string"); + assertNotNull(responseEntityView, "ResponseEntityView should not be null"); + assertNotNull(responseEntityView.getEntity(), "Job ID should not be null"); + assertFalse(responseEntityView.getEntity().isEmpty(), "Job ID should be a non-empty string"); // Retrieve and validate job exists in the queue - Job job = APILocator.getJobQueueManagerAPI().getJob(response.getEntity()); + Job job = APILocator.getJobQueueManagerAPI().getJob(responseEntityView.getEntity()); assertNotNull(job, "Job should exist in queue"); // Validate core import parameters @@ -212,7 +309,13 @@ private static void validateSuccessfulResponse(ResponseEntityView respon } } - //TODO move to a common place + /** + * Creates a temporary CSV file for testing purposes. + * The file contains two rows of test data with 'title' and 'body' columns. + * + * @return A temporary File object containing test CSV data + * @throws IOException if there's an error creating or writing to the temporary file + */ private static File createTestCsvFile() throws IOException { String csv = "title,body\nTest Title 1,Test Body 1\nTest Title 2,Test Body 2\n"; File csvFile = File.createTempFile("test", ".csv"); @@ -220,6 +323,13 @@ private static File createTestCsvFile() throws IOException { return csvFile; } + /** + * Creates a FormDataContentDisposition object for file upload testing. + * Sets up the basic metadata required for a file upload including name and size. + * + * @param filename The name of the file to be included in the content disposition + * @return A FormDataContentDisposition object configured for testing + */ private static FormDataContentDisposition createContentDisposition(String filename) { return FormDataContentDisposition .name("file") @@ -228,6 +338,15 @@ private static FormDataContentDisposition createContentDisposition(String filena .build(); } + /** + * Creates a ContentImportParams object with all required parameters for content import. + * Includes file input stream, content disposition, and JSON form data. + * + * @param file The CSV file to be imported + * @param form The form containing import configuration parameters + * @return A fully configured ContentImportParams object + * @throws IOException if there's an error reading the file or serializing the form to JSON + */ private static ContentImportParams createContentImportParams(File file, ContentImportForm form) throws IOException { ContentImportParams params = new ContentImportParams(); params.setFileInputStream(new FileInputStream(file)); @@ -236,7 +355,31 @@ private static ContentImportParams createContentImportParams(File file, ContentI return params; } - private static ContentImportForm createContentImportForm(String contentType, String language, String workflowActionId, List fields) { + /** + * Creates a ContentImportForm with the specified parameters for content import configuration. + * + * @param contentType The type of content to be imported + * @param language The language ID for the imported content + * @param workflowActionId The ID of the workflow action to be applied + * @param fields List of fields to be included in the import + * @return A ContentImportForm configured with the specified parameters + * @throws ValidationException if required parameters (contentType or workflowActionId) are missing + */ + private static ContentImportForm createContentImportForm(String contentType, String language, + String workflowActionId, List fields) { return new ContentImportForm(contentType, language, workflowActionId, fields); } + + /** + * Asserts that the given response has a status of BAD_REQUEST (400). + * + *

This method checks that the HTTP response status code is 400 (BAD_REQUEST). + * It is commonly used in test cases where the expected response is an error due to invalid input or request.

+ * + * @param importContentResponse the HTTP response to check + * @throws AssertionError if the response status is not BAD_REQUEST + */ + private static void assertBadRequestResponse(Response importContentResponse) { + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), importContentResponse.getStatus(), "Expected BAD_REQUEST status"); + } } From 7a076c19e13c613c21709c0216d7fa54a306d52e Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 19:30:46 -0300 Subject: [PATCH 15/32] #30669 add javadoc to JobQueueManagerHelper --- .../rest/api/v1/JobQueueManagerHelper.java | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java index bf52c785de57..3abfb75d30c9 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java @@ -11,27 +11,53 @@ import javax.inject.Inject; import java.lang.reflect.Constructor; import java.util.List; +import java.util.Objects; +/** + * Helper class for managing job queue processors in the JobQueueManagerAPI. + * This class is responsible for discovering job processors, registering them with + * the JobQueueManagerAPI, and shutting down the JobQueueManagerAPI when needed. + *

+ * It utilizes the {@link JobProcessorScanner} to discover available job processors + * and the {@link JobQueueManagerAPI} to register them for processing jobs in the queue. + *

+ * The class is annotated with {@link ApplicationScoped} to indicate that it is + * a singleton managed by the CDI container. + */ @ApplicationScoped public class JobQueueManagerHelper { private JobProcessorScanner scanner; + /** + * Constructor that injects the {@link JobProcessorScanner} instance. + * + * @param scanner The JobProcessorScanner to discover job processors + */ @Inject public JobQueueManagerHelper(final JobProcessorScanner scanner) { this.scanner = scanner; } + /** + * Default constructor required by CDI. + */ public JobQueueManagerHelper() { - // Default constructor required by CDI } + /** + * Registers all discovered job processors with the provided JobQueueManagerAPI. + * If the JobQueueManagerAPI is not started, it starts the API before registering the processors. + * + * @param jobQueueManagerAPI The JobQueueManagerAPI instance to register processors with + */ public void registerProcessors(final JobQueueManagerAPI jobQueueManagerAPI) { if (!jobQueueManagerAPI.isStarted()) { jobQueueManagerAPI.start(); Logger.info(this.getClass(), "JobQueueManagerAPI started"); } + // Discover job processors and attempt to register them List> processors = scanner.discoverJobProcessors(); processors.forEach(processor -> { try { @@ -46,8 +72,10 @@ public void registerProcessors(final JobQueueManagerAPI jobQueueManagerAPI) { } /** - * Test if a processor can be instantiated - * @param processor The processor to tested + * Tests whether a given job processor can be instantiated by attempting to + * create an instance of the processor using its default constructor. + * + * @param processor The processor class to test for instantiation * @return true if the processor can be instantiated, false otherwise */ private boolean testInstantiation(final Class processor) { @@ -61,15 +89,30 @@ private boolean testInstantiation(final Class processor) return false; } + /** + * Registers a job processor with the JobQueueManagerAPI using the queue name specified + * in the {@link Queue} annotation, if present. If no annotation is found, the processor's + * class name is used as the queue name. + * + * @param jobQueueManagerAPI the JobQueueManagerAPI instance to register the processor with + * @param processor the processor class to register + */ private void registerProcessor(final JobQueueManagerAPI jobQueueManagerAPI, final Class processor) { Queue queue = AnnotationUtils.getBeanAnnotation(processor, Queue.class); - if (null != queue) { + if (Objects.nonNull(queue)) { jobQueueManagerAPI.registerProcessor(queue.value(), processor); } else { jobQueueManagerAPI.registerProcessor(processor.getName(), processor); } } + /** + * Shuts down the provided JobQueueManagerAPI if it is currently started. + * If the JobQueueManagerAPI is started, it attempts to close it gracefully. + * In case of an error during the shutdown process, the error is logged. + * + * @param jobQueueManagerAPI the JobQueueManagerAPI instance to shut down + */ public void shutdown(final JobQueueManagerAPI jobQueueManagerAPI) { if (jobQueueManagerAPI.isStarted()) { try { @@ -80,5 +123,4 @@ public void shutdown(final JobQueueManagerAPI jobQueueManagerAPI) { } } } -} - +} \ No newline at end of file From 6f4deeb07acef131033d3ffa3f14ee6ad0205675 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 20:27:17 -0300 Subject: [PATCH 16/32] #30669 refactor JobQueueManagerHelper --- .../rest/api/v1/JobQueueManagerHelper.java | 34 +++++++------------ .../v1/contentImport/ContentImportHelper.java | 9 ++--- .../rest/api/v1/job/JobQueueHelper.java | 8 ++--- 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java index 3abfb75d30c9..b8511ce3c0fe 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java @@ -17,54 +17,49 @@ * Helper class for managing job queue processors in the JobQueueManagerAPI. * This class is responsible for discovering job processors, registering them with * the JobQueueManagerAPI, and shutting down the JobQueueManagerAPI when needed. - *

- * It utilizes the {@link JobProcessorScanner} to discover available job processors - * and the {@link JobQueueManagerAPI} to register them for processing jobs in the queue. - *

- * The class is annotated with {@link ApplicationScoped} to indicate that it is - * a singleton managed by the CDI container. */ @ApplicationScoped public class JobQueueManagerHelper { + private JobQueueManagerAPI jobQueueManagerAPI; private JobProcessorScanner scanner; /** - * Constructor that injects the {@link JobProcessorScanner} instance. + * Constructor that injects the {@link JobProcessorScanner} and {@link JobQueueManagerAPI}. * * @param scanner The JobProcessorScanner to discover job processors + * @param jobQueueManagerAPI The JobQueueManagerAPI instance to register processors with */ @Inject - public JobQueueManagerHelper(final JobProcessorScanner scanner) { + public JobQueueManagerHelper(final JobProcessorScanner scanner, final JobQueueManagerAPI jobQueueManagerAPI) { this.scanner = scanner; + this.jobQueueManagerAPI = jobQueueManagerAPI; } /** * Default constructor required by CDI. */ public JobQueueManagerHelper() { + // Default constructor required by CDI } /** - * Registers all discovered job processors with the provided JobQueueManagerAPI. + * Registers all discovered job processors with the JobQueueManagerAPI. * If the JobQueueManagerAPI is not started, it starts the API before registering the processors. - * - * @param jobQueueManagerAPI The JobQueueManagerAPI instance to register processors with */ - public void registerProcessors(final JobQueueManagerAPI jobQueueManagerAPI) { + public void registerProcessors() { if (!jobQueueManagerAPI.isStarted()) { jobQueueManagerAPI.start(); Logger.info(this.getClass(), "JobQueueManagerAPI started"); } - // Discover job processors and attempt to register them List> processors = scanner.discoverJobProcessors(); processors.forEach(processor -> { try { if (!testInstantiation(processor)) { return; } - registerProcessor(jobQueueManagerAPI, processor); + registerProcessor(processor); } catch (Exception e) { Logger.error(this.getClass(), "Unable to register JobProcessor ", e); } @@ -94,10 +89,9 @@ private boolean testInstantiation(final Class processor) * in the {@link Queue} annotation, if present. If no annotation is found, the processor's * class name is used as the queue name. * - * @param jobQueueManagerAPI the JobQueueManagerAPI instance to register the processor with * @param processor the processor class to register */ - private void registerProcessor(final JobQueueManagerAPI jobQueueManagerAPI, final Class processor) { + private void registerProcessor(final Class processor) { Queue queue = AnnotationUtils.getBeanAnnotation(processor, Queue.class); if (Objects.nonNull(queue)) { jobQueueManagerAPI.registerProcessor(queue.value(), processor); @@ -107,13 +101,11 @@ private void registerProcessor(final JobQueueManagerAPI jobQueueManagerAPI, fina } /** - * Shuts down the provided JobQueueManagerAPI if it is currently started. + * Shuts down the JobQueueManagerAPI if it is currently started. * If the JobQueueManagerAPI is started, it attempts to close it gracefully. * In case of an error during the shutdown process, the error is logged. - * - * @param jobQueueManagerAPI the JobQueueManagerAPI instance to shut down */ - public void shutdown(final JobQueueManagerAPI jobQueueManagerAPI) { + public void shutdown() { if (jobQueueManagerAPI.isStarted()) { try { jobQueueManagerAPI.close(); @@ -123,4 +115,4 @@ public void shutdown(final JobQueueManagerAPI jobQueueManagerAPI) { } } } -} \ No newline at end of file +} diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java index 79ef8dd4723a..13d32fe07c52 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java @@ -1,9 +1,6 @@ package com.dotcms.rest.api.v1.contentImport; -import com.dotcms.jobs.business.api.JobProcessorScanner; import com.dotcms.jobs.business.api.JobQueueManagerAPI; -import com.dotcms.jobs.business.processor.JobProcessor; -import com.dotcms.jobs.business.processor.Queue; import com.dotcms.rest.api.v1.JobQueueManagerHelper; import com.dotcms.rest.api.v1.temp.DotTempFile; import com.dotmarketing.business.APILocator; @@ -19,9 +16,7 @@ import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Constructor; import java.util.HashMap; -import java.util.List; import java.util.Map; @ApplicationScoped @@ -42,12 +37,12 @@ public ContentImportHelper() { @PostConstruct public void onInit() { - jobQueueManagerHelper.registerProcessors(jobQueueManagerAPI); + jobQueueManagerHelper.registerProcessors(); } @PreDestroy public void onDestroy() { - jobQueueManagerHelper.shutdown(jobQueueManagerAPI); + jobQueueManagerHelper.shutdown(); } /** diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java index 87270c912e86..d57d5d3edfaf 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/job/JobQueueHelper.java @@ -2,14 +2,12 @@ import static com.dotcms.jobs.business.util.JobUtil.roundedProgress; -import com.dotcms.jobs.business.api.JobProcessorScanner; import com.dotcms.jobs.business.api.JobQueueManagerAPI; import com.dotcms.jobs.business.error.JobProcessorNotFoundException; import com.dotcms.jobs.business.job.Job; import com.dotcms.jobs.business.job.JobPaginatedResult; import com.dotcms.jobs.business.job.JobState; import com.dotcms.jobs.business.processor.JobProcessor; -import com.dotcms.jobs.business.processor.Queue; import com.dotcms.rest.api.v1.JobQueueManagerHelper; import com.dotcms.rest.api.v1.temp.DotTempFile; import com.dotcms.rest.api.v1.temp.TempFileAPI; @@ -23,10 +21,8 @@ import com.liferay.portal.model.User; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Constructor; import java.time.format.DateTimeFormatter; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -71,12 +67,12 @@ void registerProcessor(final String queueName, final Class Date: Thu, 21 Nov 2024 20:32:50 -0300 Subject: [PATCH 17/32] #30669 add doc for ContentImportResource --- .../contentImport/ContentImportResource.java | 90 +++++++++++++++---- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java index 3d1d3c068657..707ba47ed522 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java @@ -5,8 +5,12 @@ import com.dotcms.rest.WebResource; import com.dotcms.rest.exception.mapper.ExceptionMapperUtil; import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.util.Logger; import com.fasterxml.jackson.core.JsonProcessingException; -import graphql.VisibleForTesting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -16,7 +20,12 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import static com.dotcms.util.DotPreconditions.checkNotNull; +/** + * REST resource for handling content import operations, including creating and enqueuing content import jobs. + * This class provides endpoints for importing content from CSV files and processing them based on the provided parameters. + */ @Path("/v1/content") public class ContentImportResource { @@ -25,41 +34,87 @@ public class ContentImportResource { private final String IMPORT_QUEUE_NAME = "importContentlets"; //TODO move to a common place + + // Constants for commands private static final String CMD_PREVIEW = "preview"; private static final String CMD_PUBLISH = "publish"; + /** + * Constructor for ContentImportResource. + * + * @param importHelper The helper class used to manage content import jobs + */ @Inject public ContentImportResource(final ContentImportHelper importHelper) { this(new WebResource(), importHelper); } + /** + * Constructor for ContentImportResource with WebResource and ContentImportHelper injected. + * + * @param webResource The web resource for handling HTTP requests and responses + * @param importHelper The helper class used to manage content import jobs + */ public ContentImportResource(final WebResource webResource, final ContentImportHelper importHelper) { this.webResource = webResource; this.importHelper = importHelper; } /** - * Creates and enqueues a new content import job - * - * @param request HTTP request - * @param params The import parameters including: - * - file: The CSV file to import - * - params: JSON object containing: - * - contentType: The content type variable or ID (required) - * - language: The language code (e.g., "en-US") or ID - * - workflowActionId: The workflow action ID to apply (required) - * - fields: List of fields to use as key for updates - * @return Response containing the job ID + * Creates and enqueues a new content import job, processing a CSV file with specified parameters. + * + * @param request The HTTP servlet request containing user and context information + * @param response The HTTP servlet response that will contain the response to the client + * @param params The import parameters, including: + * - file: The CSV file to import + * - contentType: The content type variable or ID (required) + * - language: The language code (e.g., "en-US") or ID + * - workflowActionId: The workflow action ID to apply (required) + * - fields: List of fields to use as keys for updates + * + * @return A Response containing the job ID if the import job was successfully created, or an error response if validation fails + * @throws DotDataException If there is an issue with DotData during the import process + * @throws JsonProcessingException If there is an issue processing the JSON response */ @POST @Path("/_import") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) + @Operation( + operationId = "importContent", + summary = "Imports content from a CSV file", + description = "Creates and enqueues a new content import job based on the provided parameters. The job processes a CSV file and updates content based on the specified content type, language, and workflow action.", + tags = {"Content Import"}, + responses = { + @ApiResponse( + responseCode = "200", + description = "Content import job created successfully", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{\n" + + " \"entity\": \"3930f815-7aa4-4649-94c2-3f37fd21136d\",\n" + + " \"errors\": [],\n" + + " \"i18nMessagesMap\": {},\n" + + " \"messages\": [],\n" + + " \"pagination\": null,\n" + + " \"permissions\": []\n" + + "}") + ) + ), + @ApiResponse(responseCode = "400", description = "Bad request due to validation errors"), + @ApiResponse(responseCode = "401", description = "Invalid user authentication"), + @ApiResponse(responseCode = "403", description = "Forbidden due to insufficient permissions"), + @ApiResponse(responseCode = "404", description = "Content type or language not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + } + ) public Response importContent( @Context final HttpServletRequest request, @Context final HttpServletResponse response, @BeanParam final ContentImportParams params) throws DotDataException, JsonProcessingException { + + checkNotNull(params, "Form data is required"); + // Initialize the WebResource and set required user information final var initDataObject = new WebResource.InitBuilder(webResource) .requiredBackendUser(true) .requiredFrontendUser(false) @@ -67,11 +122,16 @@ public Response importContent( .rejectWhenNoUser(true) .init(); - try{ + Logger.debug(this, ()->String.format("Importing content: %s", params)); + + + try { + // Create the import job final String jobId = importHelper.createJob(CMD_PUBLISH, IMPORT_QUEUE_NAME, params, initDataObject.getUser(), request); return Response.ok(new ResponseEntityView<>(jobId)).build(); - }catch (JobValidationException e) { + } catch (JobValidationException e) { + // Handle validation exception and return appropriate error message return ExceptionMapperUtil.createResponse(null, e.getMessage()); } } -} \ No newline at end of file +} \ No newline at end of file From c59048a0b40619eaf81cf3644d5c7d4b206c68c4 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 23:25:48 -0300 Subject: [PATCH 18/32] #30669 update doc --- .../main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java index b8511ce3c0fe..db7e60ff94eb 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java @@ -15,6 +15,7 @@ /** * Helper class for managing job queue processors in the JobQueueManagerAPI. + *

* This class is responsible for discovering job processors, registering them with * the JobQueueManagerAPI, and shutting down the JobQueueManagerAPI when needed. */ From b4da6c63df9c342e50768bdbbb47d6380cb95046 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 23:26:22 -0300 Subject: [PATCH 19/32] #30669 fix toString --- .../dotcms/rest/api/v1/contentImport/ContentImportParams.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java index 27a69340e9b0..961aa78806f4 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java @@ -73,7 +73,7 @@ public ContentImportForm getForm() throws JsonProcessingException { @Override public String toString() { return "ContentImportParams{" + - "form=" + form + + "form=" + getJsonForm() + ", hasFile=" + (fileInputStream != null) + ", fileName=" + (contentDisposition != null ? contentDisposition.getFileName() : "null") + '}'; From be133fe5b12e5923e40b6b88646525cb5069df65 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 23:27:07 -0300 Subject: [PATCH 20/32] #30669 add ContentImportHelper doc --- .../v1/contentImport/ContentImportHelper.java | 81 ++++++++++++++----- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java index 13d32fe07c52..25ed0bc53680 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java @@ -19,51 +19,75 @@ import java.util.HashMap; import java.util.Map; +/** + * Helper class for managing content import operations in the dotCMS application. + *

+ * This class provides methods to create and manage jobs for importing content + * from external sources, such as CSV files, into the system. It handles the + * validation of import parameters, processes file uploads, and constructs + * the necessary job parameters to enqueue content import tasks in the job queue. + */ @ApplicationScoped public class ContentImportHelper { private JobQueueManagerAPI jobQueueManagerAPI; private JobQueueManagerHelper jobQueueManagerHelper; + /** + * Constructor for dependency injection. + * + * @param jobQueueManagerAPI The API for managing job queues. + * @param jobQueueManagerHelper Helper for job queue management. + */ @Inject public ContentImportHelper(final JobQueueManagerAPI jobQueueManagerAPI, final JobQueueManagerHelper jobQueueManagerHelper) { this.jobQueueManagerAPI = jobQueueManagerAPI; this.jobQueueManagerHelper = jobQueueManagerHelper; } + /** + * Default constructor required for CDI. + */ public ContentImportHelper() { - //default constructor Mandatory for CDI + // Default constructor mandatory for CDI } + /** + * Initializes the helper by registering job processors during application startup. + */ @PostConstruct public void onInit() { jobQueueManagerHelper.registerProcessors(); } + /** + * Cleans up resources and shuts down the helper during application shutdown. + */ @PreDestroy public void onDestroy() { jobQueueManagerHelper.shutdown(); } /** - * Creates a content import job with the provided parameters + * Creates a content import job with the provided parameters and submits it to the job queue. * - * @param command Whether this is a preview job - * @param queueName The name of the queue to submit the job to - * @param params The import parameters - * @param user The user initiating the import - * @param request The HTTP request - * @return The ID of the created job + * @param command The command indicating the type of operation (e.g., "preview" or "import"). + * @param queueName The name of the queue to which the job should be submitted. + * @param params The content import parameters containing the details of the import operation. + * @param user The user initiating the import. + * @param request The HTTP request associated with the import operation. + * @return The ID of the created job. + * @throws DotDataException If there is an error creating the job. + * @throws JsonProcessingException If there is an error processing JSON data. */ public String createJob( final String command, - final String queueName, + final String queueName, final ContentImportParams params, final User user, final HttpServletRequest request) throws DotDataException, JsonProcessingException { params.checkValid(); - final Map jobParameters = createJobParameters(command, params, user, request); processFileUpload(params, jobParameters, request); @@ -71,7 +95,14 @@ public String createJob( } /** - * Creates the job parameters map from the provided inputs + * Constructs a map of job parameters based on the provided inputs. + * + * @param command The command indicating the type of operation. + * @param params The content import parameters. + * @param user The user initiating the import. + * @param request The HTTP request associated with the operation. + * @return A map containing the job parameters. + * @throws JsonProcessingException If there is an error processing JSON data. */ private Map createJobParameters( final String command, @@ -80,7 +111,7 @@ private Map createJobParameters( final HttpServletRequest request) throws JsonProcessingException { final Map jobParameters = new HashMap<>(); - + // Add required parameters jobParameters.put("cmd", command); jobParameters.put("userId", user.getUserId()); @@ -89,7 +120,7 @@ private Map createJobParameters( // Add optional parameters addOptionalParameters(params, jobParameters); - + // Add site information addSiteInformation(request, jobParameters); @@ -97,12 +128,16 @@ private Map createJobParameters( } /** - * Adds optional parameters to the job parameters map if they are present + * Adds optional parameters to the job parameter map if they are present in the form. + * + * @param params The content import parameters. + * @param jobParameters The map of job parameters to which optional parameters are added. + * @throws JsonProcessingException If there is an error processing JSON data. */ private void addOptionalParameters( final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, final Map jobParameters) throws JsonProcessingException { - + final ContentImportForm form = params.getForm(); if (form.getLanguage() != null && !form.getLanguage().isEmpty()) { @@ -114,19 +149,27 @@ private void addOptionalParameters( } /** - * Adds current site information to the job parameters + * Adds the current site information to the job parameters. + * + * @param request The HTTP request associated with the operation. + * @param jobParameters The map of job parameters to which site information is added. */ private void addSiteInformation( - final HttpServletRequest request, + final HttpServletRequest request, final Map jobParameters){ - + final var currentHost = WebAPILocator.getHostWebAPI().getCurrentHostNoThrow(request); jobParameters.put("siteName", currentHost.getHostname()); jobParameters.put("siteIdentifier", currentHost.getIdentifier()); } /** - * Processes the file upload and adds the necessary parameters to the job + * Processes the file upload and adds the file-related parameters to the job. + * + * @param params The content import parameters. + * @param jobParameters The map of job parameters. + * @param request The HTTP request containing the uploaded file. + * @throws DotDataException If there is an error processing the file upload. */ private void processFileUpload( final ContentImportParams params, From 68942f0bed85405a0e2b8d250bda8c3c73c2aec8 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Thu, 21 Nov 2024 23:58:52 -0300 Subject: [PATCH 21/32] #30669 add log --- .../dotcms/rest/api/v1/JobQueueManagerHelper.java | 1 + .../api/v1/contentImport/ContentImportResource.java | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java index db7e60ff94eb..77e9c6acb09f 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java @@ -60,6 +60,7 @@ public void registerProcessors() { if (!testInstantiation(processor)) { return; } + Logger.info(this.getClass(), "Registering JobProcessor: " + processor.getName()); registerProcessor(processor); } catch (Exception e) { Logger.error(this.getClass(), "Unable to register JobProcessor ", e); diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java index 707ba47ed522..b824016839bd 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java @@ -20,7 +20,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import static com.dotcms.util.DotPreconditions.checkNotNull; /** * REST resource for handling content import operations, including creating and enqueuing content import jobs. @@ -31,12 +30,9 @@ public class ContentImportResource { private final WebResource webResource; private final ContentImportHelper importHelper; - private final String IMPORT_QUEUE_NAME = "importContentlets"; - - //TODO move to a common place - + private static final String IMPORT_QUEUE_NAME = "importContentlets"; + // Constants for commands - private static final String CMD_PREVIEW = "preview"; private static final String CMD_PUBLISH = "publish"; /** @@ -113,7 +109,6 @@ public Response importContent( @BeanParam final ContentImportParams params) throws DotDataException, JsonProcessingException { - checkNotNull(params, "Form data is required"); // Initialize the WebResource and set required user information final var initDataObject = new WebResource.InitBuilder(webResource) .requiredBackendUser(true) @@ -122,8 +117,7 @@ public Response importContent( .rejectWhenNoUser(true) .init(); - Logger.debug(this, ()->String.format("Importing content: %s", params)); - + Logger.debug(this, ()->String.format(" user %s is importing content: %s", initDataObject.getUser().getUserId(), params)); try { // Create the import job From 5df1df38a2419f00e8add375360848ac649ec3f7 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 00:07:30 -0300 Subject: [PATCH 22/32] #30669 use constants --- .../rest/api/v1/contentImport/ContentImportResource.java | 4 ++-- .../content/upload/ContentUploadResourceIntegrationTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java index b824016839bd..0c70b5760d58 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java @@ -33,8 +33,8 @@ public class ContentImportResource { private static final String IMPORT_QUEUE_NAME = "importContentlets"; // Constants for commands - private static final String CMD_PUBLISH = "publish"; - + private static final String CMD_PUBLISH = com.dotmarketing.util.Constants.PUBLISH; + /** * Constructor for ContentImportResource. * diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java index 932f0b777f7a..22cabfc4656f 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java @@ -60,7 +60,7 @@ public class ContentUploadResourceIntegrationTest extends Junit5WeldBaseTest { private static ContentImportResource importResource; private final static String IMPORT_QUEUE_NAME = "importContentlets"; - private final static String CMD_PUBLISH = "publish"; + private static final String CMD_PUBLISH = com.dotmarketing.util.Constants.PUBLISH; @BeforeAll static void setUp() throws Exception { From fb100668e14066adec063e84632104f5d51349e9 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 00:33:33 -0300 Subject: [PATCH 23/32] #30669 rename package --- .../_import}/ContentImportForm.java | 2 +- .../_import}/ContentImportHelper.java | 6 +++--- .../_import}/ContentImportParams.java | 2 +- .../_import}/ContentImportResource.java | 2 +- .../ContentImportResourceIntegrationTest.java} | 8 ++------ 5 files changed, 8 insertions(+), 12 deletions(-) rename dotCMS/src/main/java/com/dotcms/rest/api/v1/{contentImport => content/_import}/ContentImportForm.java (97%) rename dotCMS/src/main/java/com/dotcms/rest/api/v1/{contentImport => content/_import}/ContentImportHelper.java (97%) rename dotCMS/src/main/java/com/dotcms/rest/api/v1/{contentImport => content/_import}/ContentImportParams.java (98%) rename dotCMS/src/main/java/com/dotcms/rest/api/v1/{contentImport => content/_import}/ContentImportResource.java (99%) rename dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/{upload/ContentUploadResourceIntegrationTest.java => _import/ContentImportResourceIntegrationTest.java} (98%) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportForm.java similarity index 97% rename from dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java rename to dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportForm.java index 57f397b203cb..abb1cd2f446f 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportForm.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportForm.java @@ -1,4 +1,4 @@ -package com.dotcms.rest.api.v1.contentImport; +package com.dotcms.rest.api.v1.content._import; import com.dotcms.repackage.javax.validation.constraints.NotNull; import com.dotcms.rest.api.Validated; diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java similarity index 97% rename from dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java rename to dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java index 25ed0bc53680..57cd443f6d8a 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java @@ -1,4 +1,4 @@ -package com.dotcms.rest.api.v1.contentImport; +package com.dotcms.rest.api.v1.content._import; import com.dotcms.jobs.business.api.JobQueueManagerAPI; import com.dotcms.rest.api.v1.JobQueueManagerHelper; @@ -106,7 +106,7 @@ public String createJob( */ private Map createJobParameters( final String command, - final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final ContentImportParams params, final User user, final HttpServletRequest request) throws JsonProcessingException { @@ -135,7 +135,7 @@ private Map createJobParameters( * @throws JsonProcessingException If there is an error processing JSON data. */ private void addOptionalParameters( - final com.dotcms.rest.api.v1.contentImport.ContentImportParams params, + final ContentImportParams params, final Map jobParameters) throws JsonProcessingException { final ContentImportForm form = params.getForm(); diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportParams.java similarity index 98% rename from dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java rename to dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportParams.java index 961aa78806f4..61e993280d11 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportParams.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportParams.java @@ -1,4 +1,4 @@ -package com.dotcms.rest.api.v1.contentImport; +package com.dotcms.rest.api.v1.content._import; import com.dotcms.repackage.javax.validation.ValidationException; import com.dotcms.repackage.javax.validation.constraints.NotNull; diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportResource.java similarity index 99% rename from dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java rename to dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportResource.java index 0c70b5760d58..07319756379d 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contentImport/ContentImportResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportResource.java @@ -1,4 +1,4 @@ -package com.dotcms.rest.api.v1.contentImport; +package com.dotcms.rest.api.v1.content._import; import com.dotcms.jobs.business.error.JobValidationException; import com.dotcms.rest.ResponseEntityView; diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java similarity index 98% rename from dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java rename to dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java index 22cabfc4656f..df0440cd0a41 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/upload/ContentUploadResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java @@ -1,4 +1,4 @@ -package com.dotcms.rest.api.v1.content.upload; +package com.dotcms.rest.api.v1.content._import; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -15,10 +15,6 @@ import com.dotcms.mock.response.MockHttpResponse; import com.dotcms.rest.ResponseEntityView; import com.dotcms.rest.api.v1.JobQueueManagerHelper; -import com.dotcms.rest.api.v1.contentImport.ContentImportForm; -import com.dotcms.rest.api.v1.contentImport.ContentImportHelper; -import com.dotcms.rest.api.v1.contentImport.ContentImportParams; -import com.dotcms.rest.api.v1.contentImport.ContentImportResource; import com.dotcms.rest.exception.ValidationException; import com.dotcms.util.IntegrationTestInitService; import com.dotmarketing.beans.Host; @@ -50,7 +46,7 @@ @ApplicationScoped @EnableWeld @RunWith(JUnit4WeldRunner.class) -public class ContentUploadResourceIntegrationTest extends Junit5WeldBaseTest { +public class ContentImportResourceIntegrationTest extends Junit5WeldBaseTest { private static User adminUser; private static HttpServletRequest request; From c6d72ba54b9b4bac44d3481af4b6093eaf605e12 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 09:56:52 -0300 Subject: [PATCH 24/32] #30669 add postman tests --- ...tentImportResource.postman_collection.json | 805 ++++++++++++++++++ .../test-import-content-job-final.csv | 2 + 2 files changed, 807 insertions(+) create mode 100644 dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json create mode 100644 dotcms-postman/src/main/resources/postman/resources/ContentImportResource/test-import-content-job-final.csv diff --git a/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json b/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json new file mode 100644 index 000000000000..31651eac6d81 --- /dev/null +++ b/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json @@ -0,0 +1,805 @@ +{ + "info": { + "_postman_id": "a089a839-9ea3-4498-88a1-f94e76e04b3c", + "name": "ContentImportResource", + "description": "Postman collection for testing the ContentImportResource API endpoints.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "36604690" + }, + "item": [ + { + "name": "pre-execution-scripts", + "item": [ + { + "name": "Create ContentType Copy", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\":\"com.dotcms.contenttype.model.type.ImmutableSimpleContentType\",\n \"defaultType\":false,\n \"name\":\"TestImportJob\",\n \"description\":\"THE DESCRIPTION\",\n \"host\":\"SYSTEM_HOST\",\n \"owner\":\"dotcms.org.1\",\n \"fixed\":false,\n \"system\":false,\n \"folder\":\"SYSTEM_FOLDER\",\n \"workflow\": [\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"],\n \"fields\":[\n {\n \"clazz\":\"com.dotcms.contenttype.model.field.ImmutableHostFolderField\",\n \"dataType\":\"SYSTEM\",\n \"fieldType\":\"Host-Folder\",\n \"fieldTypeLabel\":\"Site or Folder\",\n \"fixed\":false,\n \"forceIncludeInApi\":false,\n \"indexed\":true,\n \"listed\":false,\n \"name\":\"Host\",\n \"readOnly\":false,\n \"required\":true,\n \"searchable\":false,\n \"sortOrder\":2,\n \"unique\":false,\n \"variable\":\"contentHost\"\n },\n {\n \"clazz\":\"com.dotcms.contenttype.model.field.ImmutableTextField\",\n \"dataType\":\"TEXT\",\n \"fieldType\":\"Text\",\n \"fieldTypeLabel\":\"Text\",\n \"fixed\":false,\n \"forceIncludeInApi\":false,\n \"indexed\":true,\n \"listed\":true,\n \"name\":\"Title\",\n \"readOnly\":false,\n \"required\":true,\n \"searchable\":true,\n \"sortOrder\":3,\n \"unique\":false,\n \"variable\":\"title\"\n },\n {\n \"clazz\":\"com.dotcms.contenttype.model.field.ImmutableTextAreaField\",\n \"dataType\":\"LONG_TEXT\",\n \"fieldType\":\"Textarea\",\n \"fieldTypeLabel\":\"Textarea\",\n \"fixed\":false,\n \"forceIncludeInApi\":false,\n \"indexed\":false,\n \"listed\":false,\n \"name\":\"Description\",\n \"readOnly\":false,\n \"required\":false,\n \"searchable\":false,\n \"sortOrder\":5,\n \"unique\":false,\n \"variable\":\"description\"\n },\n {\n \"clazz\":\"com.dotcms.contenttype.model.field.ImmutableTagField\",\n \"dataType\":\"SYSTEM\",\n \"fieldType\":\"Tag\",\n \"fieldTypeLabel\":\"Tag\",\n \"fixed\":false,\n \"forceIncludeInApi\":false,\n \"indexed\":true,\n \"listed\":false,\n \"name\":\"Tags\",\n \"readOnly\":false,\n \"required\":false,\n \"searchable\":false,\n \"sortOrder\":6,\n \"unique\":false,\n \"variable\":\"tags\"\n },\n {\n \"clazz\":\"com.dotcms.contenttype.model.field.ImmutableImageField\",\n \"dataType\":\"TEXT\",\n \"fieldType\":\"Image\",\n \"fieldTypeLabel\":\"Image\",\n \"fieldVariables\":[\n \n ],\n \"fixed\":false,\n \"forceIncludeInApi\":false,\n \"indexed\":false,\n \"listed\":false,\n \"name\":\"Image\",\n \"readOnly\":false,\n \"required\":false,\n \"searchable\":false,\n \"sortOrder\":8,\n \"unique\":false,\n \"variable\":\"image\"\n },\n {\n \"clazz\":\"com.dotcms.contenttype.model.field.ImmutableTextField\",\n \"dataType\":\"TEXT\",\n \"fieldType\":\"Text\",\n \"fieldTypeLabel\":\"Text\",\n \"fieldVariables\":[\n \n ],\n \"fixed\":false,\n \"forceIncludeInApi\":false,\n \"indexed\":false,\n \"listed\":false,\n \"name\":\"Alt Tag\",\n \"readOnly\":false,\n \"required\":false,\n \"searchable\":false,\n \"sortOrder\":9,\n \"unique\":false,\n \"variable\":\"altTag\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype" + ] + }, + "description": "Given a content type payload containing field variables.\nWhen sending a POST.\nExpect that code is 200.\nExpect content type is created with the provided fields.\nExpect that new properties of content types are set (icon and sortOrder)." + }, + "response": [] + } + ] + }, + { + "name": "Create Valid Import Job Expect Success", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response is successful", + "pm.test(\"Response status is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "pm.test(\"Response body should contain a valid Job ID\", function () {", + " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", + "});", + "", + "const jobId = responseBody.entity;", + "pm.collectionVariables.set(\"jobId\", jobId);", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + }, + { + "key": "form", + "value": "{\"contentType\":\"{{contentType}}\",\"language\":\"{{language}}\",\"workflowActionId\":\"{{workflowActionId}}\", \"fields\": {{fields}}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Check Successful Job Status", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response is successful", + "pm.test(\"Response status is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "pm.test(\"Response body should contain a valid Job ID\", function () {", + " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", + "});", + "", + "const job = responseBody.entity;", + "", + "pm.test(\"Job entity should be defined\", function () {", + " pm.expect(job).to.be.an('object');", + "});", + "", + "", + "pm.test(\"Job should contain correct parameters\", function () {", + " const jobParameters = job.parameters;", + "", + " pm.expect(jobParameters).to.be.an('object');", + " pm.expect(jobParameters).to.have.property('cmd', 'publish');", + " pm.expect(jobParameters).to.have.property('userId', 'dotcms.org.1');", + "", + " pm.expect(jobParameters).to.have.property('contentType', pm.collectionVariables.get(\"contentType\"));", + " pm.expect(jobParameters).to.have.property('language', pm.collectionVariables.get(\"language\"));", + " pm.expect(jobParameters).to.have.property('workflowActionId', pm.collectionVariables.get(\"workflowActionId\"));", + " pm.expect(jobParameters).to.have.property('fields').that.deep.equals(JSON.parse(pm.collectionVariables.get(\"fields\")));", + "", + "});", + "", + "pm.test(\"Job queue name should be correct\", function () {", + " pm.expect(job).to.have.property('queueName', pm.collectionVariables.get('queueName'));", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/jobs/{{jobId}}/status", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "jobs", + "{{jobId}}", + "status" + ] + } + }, + "response": [] + }, + { + "name": "Create Import Job Without Fields Expect Success", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response is successful", + "pm.test(\"Response status is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "pm.test(\"Response body should contain a valid Job ID\", function () {", + " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + }, + { + "key": "form", + "value": "{\"contentType\":\"{{contentType}}\",\"language\":\"{{language}}\",\"workflowActionId\":\"{{workflowActionId}}\"}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Create Import Job With ISO Languag Expect Success Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response is successful", + "pm.test(\"Response status is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "pm.test(\"Response body should contain a valid Job ID\", function () {", + " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + }, + { + "key": "form", + "value": "{\"contentType\":\"{{contentType}}\",\"language\":\"en-us\",\"workflowActionId\":\"{{workflowActionId}}\", \"fields\": {{fields}}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Create Import Job Without Language Expect Success", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response is successful", + "pm.test(\"Response status is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "pm.test(\"Response body should contain a valid Job ID\", function () {", + " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + }, + { + "key": "form", + "value": "{\"contentType\":\"{{contentType}}\",\"workflowActionId\":\"{{workflowActionId}}\", \"fields\": {{fields}}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Create Import Job Without Language and Field Expect Failure", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response status is 400", + "pm.test(\"Response status is 400\", function () {", + " pm.response.to.have.status(400);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "", + "// Validate that the response body contains the 'message' property and it is not empty", + "pm.test(\"Response should have an error message\", function () {", + " pm.expect(responseBody).to.have.property('message').that.is.not.empty;", + " pm.expect(responseBody.message).to.equal('A key identifying the different Language versions of the same content must be defined when importing multilingual files.');", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + }, + { + "key": "form", + "value": "{\"contentType\":\"{{contentType}}\",\"workflowActionId\":\"{{workflowActionId}}\"}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Create Import Job With Invalid language Expect Failure", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response status is 400", + "pm.test(\"Response status is 400\", function () {", + " pm.response.to.have.status(400);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "", + "// Validate that the response body contains the 'message' property and it is not empty", + "pm.test(\"Response should have an error message\", function () {", + " pm.expect(responseBody).to.have.property('message').that.is.not.empty;", + " pm.expect(responseBody.message).to.equal('Language [54321] not found.');", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + }, + { + "key": "form", + "value": "{\"contentType\":\"{{contentType}}\",\"language\":\"54321\",\"workflowActionId\":\"{{workflowActionId}}\", \"fields\": {{fields}}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Create Import Job With Invalid ContentType Expect Failure", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate the response status is 400", + "pm.test(\"Response status is 400\", function () {", + " pm.response.to.have.status(400);", + "});", + "", + "// Parse the response body", + "const responseBody = pm.response.json();", + "", + "// Validate that the response body contains the 'message' property and it is not empty", + "pm.test(\"Response should have an error message\", function () {", + " pm.expect(responseBody).to.have.property('message').that.is.not.empty;", + " pm.expect(responseBody.message).to.equal('Content Type [doesNotExist] not found.');", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + }, + { + "key": "form", + "value": "{\"contentType\":\"doesNotExist\",\"language\":\"{{language}}\",\"workflowActionId\":\"{{workflowActionId}}\", \"fields\": {{fields}}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Create Job Without File Expect Failure", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Validate missing file throws the correct error\", function () {", + " pm.response.to.have.status(400);", + " const errors = pm.response.json();", + "", + " const fileError = errors.find(error => error.fieldName === \"fileInputStream\");", + " pm.expect(fileError).to.be.an(\"object\");", + " pm.expect(fileError).to.have.property(\"message\", \"The file is required.\");", + " pm.expect(fileError).to.have.property(\"errorCode\", null);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "form", + "value": "{\"contentType\":\"{{contentType}}\",\"language\":\"{{language}}\",\"workflowActionId\":\"{{workflowActionId}}\", \"fields\": {{fields}}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + }, + { + "name": "Create Job Without Form Expect Failure", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Validate missing form data throws the correct error\", function () {", + " pm.response.to.have.status(400);", + " const errors = pm.response.json();", + "", + " const formError = errors.find(error => error.fieldName === \"jsonForm\");", + " pm.expect(formError).to.be.an(\"object\");", + " pm.expect(formError).to.have.property(\"message\", \"The form data is required.\");", + " pm.expect(formError).to.have.property(\"errorCode\", null);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "resources/ContentImportResource/test-import-content-job-final.csv" + } + ] + }, + "url": { + "raw": "{{serverURL}}/api/v1/content/_import", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "content", + "_import" + ] + }, + "description": "Creates a new job in the specified queue." + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{jwt}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + " ", + "if(!pm.collectionVariables.get('jwt')){", + " console.log(\"generating....\")", + " const serverURL = pm.environment.get('serverURL') || pm.collectionVariables.get('baseUrl'); // Get the server URL from the environment variable", + " const apiUrl = `${serverURL}/api/v1/apitoken`; // Construct the full API URL", + "", + " const username = pm.environment.get(\"user\") || pm.collectionVariables.get('user'); ", + " const password = pm.environment.get(\"password\") || pm.collectionVariables.get('password');", + " const basicAuth = Buffer.from(`${username}:${password}`).toString('base64');", + "", + " const requestOptions = {", + " url: apiUrl,", + " method: \"POST\",", + " header: {", + " \"accept\": \"*/*\",", + " \"content-type\": \"application/json\",", + " \"Authorization\": `Basic ${basicAuth}`", + " },", + " body: {", + " mode: \"raw\",", + " raw: JSON.stringify({", + " \"expirationSeconds\": 7200,", + " \"userId\": \"dotcms.org.1\",", + " \"network\": \"0.0.0.0/0\",", + " \"claims\": {\"label\": \"postman-tests\"}", + " })", + " }", + " };", + "", + " pm.sendRequest(requestOptions, function (err, response) {", + " if (err) {", + " console.log(err);", + " } else {", + " const jwt = response.json().entity.jwt;", + " pm.collectionVariables.set('jwt', jwt);", + " console.log(\"Successfully got a jwt :\" + jwt);", + " }", + " }); ", + "} " + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "queueName", + "value": "importContentlets", + "type": "string" + }, + { + "key": "jobId", + "value": "-1", + "type": "string" + }, + { + "key": "user", + "value": "admin@dotCMS.com", + "type": "string" + }, + { + "key": "password", + "value": "admin", + "type": "string" + }, + { + "key": "jwt", + "value": "" + }, + { + "key": "contentType", + "value": "Activity", + "type": "string" + }, + { + "key": "language", + "value": "1", + "type": "string" + }, + { + "key": "workflowActionId", + "value": "b9d89c80-3d88-4311-8365-187323c96436", + "type": "string" + }, + { + "key": "fields", + "value": "[\"title\"]", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/dotcms-postman/src/main/resources/postman/resources/ContentImportResource/test-import-content-job-final.csv b/dotcms-postman/src/main/resources/postman/resources/ContentImportResource/test-import-content-job-final.csv new file mode 100644 index 000000000000..317701b37337 --- /dev/null +++ b/dotcms-postman/src/main/resources/postman/resources/ContentImportResource/test-import-content-job-final.csv @@ -0,0 +1,2 @@ +contentHost,title,description,tags,image,altTag +48190c8c-42c4-46af-8d1a-0cd5db894797,Import Job Test Final,test desc,testTag,, \ No newline at end of file From 78525cd0d34bb5f03d23320a9ce9b4b7984d39e0 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 12:13:34 -0300 Subject: [PATCH 25/32] #30669 set to final --- .../java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java | 7 ++++--- .../rest/api/v1/content/_import/ContentImportHelper.java | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java index 77e9c6acb09f..38f104aed278 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java @@ -22,8 +22,8 @@ @ApplicationScoped public class JobQueueManagerHelper { - private JobQueueManagerAPI jobQueueManagerAPI; - private JobProcessorScanner scanner; + private final JobQueueManagerAPI jobQueueManagerAPI; + private final JobProcessorScanner scanner; /** * Constructor that injects the {@link JobProcessorScanner} and {@link JobQueueManagerAPI}. @@ -41,7 +41,8 @@ public JobQueueManagerHelper(final JobProcessorScanner scanner, final JobQueueMa * Default constructor required by CDI. */ public JobQueueManagerHelper() { - // Default constructor required by CDI + this.scanner = null; + this.jobQueueManagerAPI = null; } /** diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java index 57cd443f6d8a..aebe5847b44f 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java @@ -30,8 +30,8 @@ @ApplicationScoped public class ContentImportHelper { - private JobQueueManagerAPI jobQueueManagerAPI; - private JobQueueManagerHelper jobQueueManagerHelper; + private final JobQueueManagerAPI jobQueueManagerAPI; + private final JobQueueManagerHelper jobQueueManagerHelper; /** * Constructor for dependency injection. @@ -49,7 +49,8 @@ public ContentImportHelper(final JobQueueManagerAPI jobQueueManagerAPI, final Jo * Default constructor required for CDI. */ public ContentImportHelper() { - // Default constructor mandatory for CDI + this.jobQueueManagerAPI = null; + this.jobQueueManagerHelper = null; } /** From b15005b8c4519b9dfa00a18aca25f4950ca125f6 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 16:54:56 -0300 Subject: [PATCH 26/32] #30669 fix postman tests --- dotcms-postman/config.json | 1 + ...tentImportResource.postman_collection.json | 37 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/dotcms-postman/config.json b/dotcms-postman/config.json index 1490c0b9900e..06abde738b82 100644 --- a/dotcms-postman/config.json +++ b/dotcms-postman/config.json @@ -52,6 +52,7 @@ "name": "default-split", "collections": [ "ApiToken_Resource.postman_collection", + "ContentImportResource.postman_collection", "Manifest_Download_End_Point.postman_collection", "Osgi.postman_collection", "Page_Version_with_different_Templates.postman_collection", diff --git a/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json b/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json index 31651eac6d81..84d80490cada 100644 --- a/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json +++ b/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json @@ -54,13 +54,11 @@ "});", "", "// Parse the response body", - "const responseBody = pm.response.json();", "pm.test(\"Response body should contain a valid Job ID\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", + " pm.collectionVariables.set(\"jobId\", responseBody.entity);", "});", - "", - "const jobId = responseBody.entity;", - "pm.collectionVariables.set(\"jobId\", jobId);", "" ], "type": "text/javascript", @@ -120,21 +118,25 @@ "});", "", "// Parse the response body", - "const responseBody = pm.response.json();", "pm.test(\"Response body should contain a valid Job ID\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", "});", "", - "const job = responseBody.entity;", "", "pm.test(\"Job entity should be defined\", function () {", + " const responseBody = pm.response.json();", + " const job = responseBody.entity;", " pm.expect(job).to.be.an('object');", "});", "", "", "pm.test(\"Job should contain correct parameters\", function () {", + " const responseBody = pm.response.json();", + " const job = responseBody.entity;", " const jobParameters = job.parameters;", "", + " pm.expect(job).to.have.property('queueName', pm.collectionVariables.get('queueName'));", " pm.expect(jobParameters).to.be.an('object');", " pm.expect(jobParameters).to.have.property('cmd', 'publish');", " pm.expect(jobParameters).to.have.property('userId', 'dotcms.org.1');", @@ -144,10 +146,6 @@ " pm.expect(jobParameters).to.have.property('workflowActionId', pm.collectionVariables.get(\"workflowActionId\"));", " pm.expect(jobParameters).to.have.property('fields').that.deep.equals(JSON.parse(pm.collectionVariables.get(\"fields\")));", "", - "});", - "", - "pm.test(\"Job queue name should be correct\", function () {", - " pm.expect(job).to.have.property('queueName', pm.collectionVariables.get('queueName'));", "});" ], "type": "text/javascript", @@ -196,9 +194,8 @@ " pm.response.to.have.status(200);", "});", "", - "// Parse the response body", - "const responseBody = pm.response.json();", "pm.test(\"Response body should contain a valid Job ID\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", "});", "" @@ -260,8 +257,8 @@ "});", "", "// Parse the response body", - "const responseBody = pm.response.json();", "pm.test(\"Response body should contain a valid Job ID\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", "});", "" @@ -323,8 +320,8 @@ "});", "", "// Parse the response body", - "const responseBody = pm.response.json();", "pm.test(\"Response body should contain a valid Job ID\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('entity').that.is.not.empty;", "});", "" @@ -385,11 +382,10 @@ " pm.response.to.have.status(400);", "});", "", - "// Parse the response body", - "const responseBody = pm.response.json();", "", "// Validate that the response body contains the 'message' property and it is not empty", "pm.test(\"Response should have an error message\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('message').that.is.not.empty;", " pm.expect(responseBody.message).to.equal('A key identifying the different Language versions of the same content must be defined when importing multilingual files.');", "});", @@ -451,11 +447,10 @@ " pm.response.to.have.status(400);", "});", "", - "// Parse the response body", - "const responseBody = pm.response.json();", "", "// Validate that the response body contains the 'message' property and it is not empty", "pm.test(\"Response should have an error message\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('message').that.is.not.empty;", " pm.expect(responseBody.message).to.equal('Language [54321] not found.');", "});", @@ -517,11 +512,9 @@ " pm.response.to.have.status(400);", "});", "", - "// Parse the response body", - "const responseBody = pm.response.json();", - "", "// Validate that the response body contains the 'message' property and it is not empty", "pm.test(\"Response should have an error message\", function () {", + " const responseBody = pm.response.json();", " pm.expect(responseBody).to.have.property('message').that.is.not.empty;", " pm.expect(responseBody.message).to.equal('Content Type [doesNotExist] not found.');", "});", @@ -783,7 +776,7 @@ }, { "key": "contentType", - "value": "Activity", + "value": "TestImportJob", "type": "string" }, { From 6cd497debfeb899f6615c36cde6347859b96c6a1 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 16:56:33 -0300 Subject: [PATCH 27/32] #30669 fix unnecessary defining methods as static --- .../ContentImportResourceIntegrationTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java index df0440cd0a41..ce19df11c8d3 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java @@ -263,7 +263,7 @@ public void test_import_content_missing_form() throws IOException { * @throws DotDataException if there's an error retrieving the job from the queue * @throws AssertionError if any validation fails */ - private static void validateSuccessfulResponse(Response response, String expectedContentType, String expectedLanguage, List expectedFields, String expectedWorkflowActionId, String expectedCommand) throws DotDataException { + private void validateSuccessfulResponse(Response response, String expectedContentType, String expectedLanguage, List expectedFields, String expectedWorkflowActionId, String expectedCommand) throws DotDataException { // Validate Response object assertNotNull(response, "Import response should not be null"); assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), "Response status should be OK"); @@ -312,7 +312,7 @@ private static void validateSuccessfulResponse(Response response, String expecte * @return A temporary File object containing test CSV data * @throws IOException if there's an error creating or writing to the temporary file */ - private static File createTestCsvFile() throws IOException { + private File createTestCsvFile() throws IOException { String csv = "title,body\nTest Title 1,Test Body 1\nTest Title 2,Test Body 2\n"; File csvFile = File.createTempFile("test", ".csv"); Files.write(csvFile.toPath(), csv.getBytes()); @@ -326,7 +326,7 @@ private static File createTestCsvFile() throws IOException { * @param filename The name of the file to be included in the content disposition * @return A FormDataContentDisposition object configured for testing */ - private static FormDataContentDisposition createContentDisposition(String filename) { + private FormDataContentDisposition createContentDisposition(String filename) { return FormDataContentDisposition .name("file") .fileName(filename) @@ -343,7 +343,7 @@ private static FormDataContentDisposition createContentDisposition(String filena * @return A fully configured ContentImportParams object * @throws IOException if there's an error reading the file or serializing the form to JSON */ - private static ContentImportParams createContentImportParams(File file, ContentImportForm form) throws IOException { + private ContentImportParams createContentImportParams(File file, ContentImportForm form) throws IOException { ContentImportParams params = new ContentImportParams(); params.setFileInputStream(new FileInputStream(file)); params.setContentDisposition(createContentDisposition(file.getName())); @@ -361,7 +361,7 @@ private static ContentImportParams createContentImportParams(File file, ContentI * @return A ContentImportForm configured with the specified parameters * @throws ValidationException if required parameters (contentType or workflowActionId) are missing */ - private static ContentImportForm createContentImportForm(String contentType, String language, + private ContentImportForm createContentImportForm(String contentType, String language, String workflowActionId, List fields) { return new ContentImportForm(contentType, language, workflowActionId, fields); } @@ -375,7 +375,7 @@ private static ContentImportForm createContentImportForm(String contentType, Str * @param importContentResponse the HTTP response to check * @throws AssertionError if the response status is not BAD_REQUEST */ - private static void assertBadRequestResponse(Response importContentResponse) { + private void assertBadRequestResponse(Response importContentResponse) { assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), importContentResponse.getStatus(), "Expected BAD_REQUEST status"); } } From adc213a6df23e65cca2429fbfd5609e9e714b573 Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 16:57:17 -0300 Subject: [PATCH 28/32] #30669 fix add new integration test class to suit --- dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java b/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java index da6b48b34deb..151f7fb82c36 100644 --- a/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java +++ b/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java @@ -89,6 +89,7 @@ import com.dotcms.rest.api.v1.asset.AssetPathResolverImplIntegrationTest; import com.dotcms.rest.api.v1.asset.WebAssetHelperIntegrationTest; import com.dotcms.rest.api.v1.authentication.ResetPasswordTokenUtilTest; +import com.dotcms.rest.api.v1.content._import.ContentImportResourceIntegrationTest; import com.dotcms.rest.api.v1.menu.MenuResourceTest; import com.dotcms.rest.api.v1.system.ConfigurationHelperTest; import com.dotcms.rest.api.v1.taillog.TailLogResourceTest; @@ -414,7 +415,8 @@ FilesCollectorTest.class, SyncVanitiesCollectorTest.class, AsyncVanitiesCollectorTest.class, - HttpServletRequestImpersonatorTest.class + HttpServletRequestImpersonatorTest.class, + ContentImportResourceIntegrationTest.class, }) public class MainSuite2b { From 3ccb323ea57d718f96b3991ce556db7591ec2c1f Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 18:23:29 -0300 Subject: [PATCH 29/32] #30669 fix it --- .../src/test/java/com/dotcms/Junit5Suite1.java | 2 ++ .../src/test/java/com/dotcms/MainSuite2b.java | 2 -- .../_import/ContentImportResourceIntegrationTest.java | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java b/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java index 4055eca82377..d287a3b7712f 100644 --- a/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java +++ b/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java @@ -4,6 +4,7 @@ import com.dotcms.jobs.business.api.JobQueueManagerAPIIntegrationTest; import com.dotcms.jobs.business.processor.impl.ImportContentletsProcessorIntegrationTest; import com.dotcms.jobs.business.queue.PostgresJobQueueIntegrationTest; +import com.dotcms.rest.api.v1.content._import.ContentImportResourceIntegrationTest; import com.dotcms.rest.api.v1.job.JobQueueHelperIntegrationTest; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; @@ -15,6 +16,7 @@ JobQueueManagerAPIIntegrationTest.class, JobQueueHelperIntegrationTest.class, ImportContentletsProcessorIntegrationTest.class + ContentImportResourceIntegrationTest.class }) public class Junit5Suite1 { diff --git a/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java b/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java index 151f7fb82c36..5b338462d195 100644 --- a/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java +++ b/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java @@ -89,7 +89,6 @@ import com.dotcms.rest.api.v1.asset.AssetPathResolverImplIntegrationTest; import com.dotcms.rest.api.v1.asset.WebAssetHelperIntegrationTest; import com.dotcms.rest.api.v1.authentication.ResetPasswordTokenUtilTest; -import com.dotcms.rest.api.v1.content._import.ContentImportResourceIntegrationTest; import com.dotcms.rest.api.v1.menu.MenuResourceTest; import com.dotcms.rest.api.v1.system.ConfigurationHelperTest; import com.dotcms.rest.api.v1.taillog.TailLogResourceTest; @@ -416,7 +415,6 @@ SyncVanitiesCollectorTest.class, AsyncVanitiesCollectorTest.class, HttpServletRequestImpersonatorTest.class, - ContentImportResourceIntegrationTest.class, }) public class MainSuite2b { diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java index ce19df11c8d3..2d1e8edd834f 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; -import com.dotcms.JUnit4WeldRunner; import com.dotcms.Junit5WeldBaseTest; import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.datagen.TestDataUtils; @@ -27,9 +26,8 @@ import org.jboss.weld.junit5.EnableWeld; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; -import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Response; @@ -43,9 +41,7 @@ * Integration test suite for content import functionality. * Tests the ContentImportResource API endpoints for various scenarios. */ -@ApplicationScoped @EnableWeld -@RunWith(JUnit4WeldRunner.class) public class ContentImportResourceIntegrationTest extends Junit5WeldBaseTest { private static User adminUser; From 9563b64c877c25cd6999181d00f9ef7c3399667d Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Fri, 22 Nov 2024 18:26:33 -0300 Subject: [PATCH 30/32] typo --- dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java b/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java index d287a3b7712f..57c98fd8e38e 100644 --- a/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java +++ b/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java @@ -15,7 +15,7 @@ PostgresJobQueueIntegrationTest.class, JobQueueManagerAPIIntegrationTest.class, JobQueueHelperIntegrationTest.class, - ImportContentletsProcessorIntegrationTest.class + ImportContentletsProcessorIntegrationTest.class, ContentImportResourceIntegrationTest.class }) public class Junit5Suite1 { From 9fc62701a109b5801a938fe8ca283fee4c04111b Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Sat, 23 Nov 2024 18:57:19 -0300 Subject: [PATCH 31/32] #30669 fix it --- .../ContentImportResourceIntegrationTest.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java index 2d1e8edd834f..7c2bc884ab91 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java @@ -1,7 +1,6 @@ package com.dotcms.rest.api.v1.content._import; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; import com.dotcms.Junit5WeldBaseTest; import com.dotcms.contenttype.model.type.ContentType; @@ -9,11 +8,9 @@ import com.dotcms.datagen.TestUserUtils; import com.dotcms.jobs.business.api.JobQueueManagerAPI; import com.dotcms.jobs.business.job.Job; -import com.dotcms.jobs.business.processor.impl.ImportContentletsProcessor; import com.dotcms.jobs.business.util.JobUtil; import com.dotcms.mock.response.MockHttpResponse; import com.dotcms.rest.ResponseEntityView; -import com.dotcms.rest.api.v1.JobQueueManagerHelper; import com.dotcms.rest.exception.ValidationException; import com.dotcms.util.IntegrationTestInitService; import com.dotmarketing.beans.Host; @@ -25,6 +22,7 @@ import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.jboss.weld.junit5.EnableWeld; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import javax.inject.Inject; @@ -54,6 +52,12 @@ public class ContentImportResourceIntegrationTest extends Junit5WeldBaseTest { private final static String IMPORT_QUEUE_NAME = "importContentlets"; private static final String CMD_PUBLISH = com.dotmarketing.util.Constants.PUBLISH; + @Inject + ContentImportHelper contentImportHelper; + + @Inject + JobQueueManagerAPI jobQueueManagerAPI; + @BeforeAll static void setUp() throws Exception { IntegrationTestInitService.getInstance().init(); @@ -63,13 +67,11 @@ static void setUp() throws Exception { request = JobUtil.generateMockRequest(adminUser, defaultSite.getHostname()); response = new MockHttpResponse(); mapper = new ObjectMapper(); + } - JobQueueManagerHelper jobQueueManagerHelper = mock(JobQueueManagerHelper.class); - JobQueueManagerAPI jobQueueManagerAPI = APILocator.getJobQueueManagerAPI(); - jobQueueManagerAPI.registerProcessor(IMPORT_QUEUE_NAME, ImportContentletsProcessor.class); - - ContentImportHelper helper = new ContentImportHelper(jobQueueManagerAPI, jobQueueManagerHelper); - importResource = new ContentImportResource(helper); + @BeforeEach + void prepare() { + importResource = new ContentImportResource(contentImportHelper); } /** @@ -132,7 +134,6 @@ public void test_import_content_without_language_and_field_params() throws IOExc // Assert that the response status is BAD_REQUEST (400) assertBadRequestResponse(importResource.importContent(request, response, params)); - } /** @@ -278,7 +279,7 @@ private void validateSuccessfulResponse(Response response, String expectedConten assertFalse(responseEntityView.getEntity().isEmpty(), "Job ID should be a non-empty string"); // Retrieve and validate job exists in the queue - Job job = APILocator.getJobQueueManagerAPI().getJob(responseEntityView.getEntity()); + Job job = jobQueueManagerAPI.getJob(responseEntityView.getEntity()); assertNotNull(job, "Job should exist in queue"); // Validate core import parameters From b01e94db7c306c949981be11d2b163d24c66c1fb Mon Sep 17 00:00:00 2001 From: valentinogiardino Date: Sat, 23 Nov 2024 19:34:41 -0300 Subject: [PATCH 32/32] #30669 add cleanUp --- .../ContentImportResourceIntegrationTest.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java index 7c2bc884ab91..e005d0ca2296 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java @@ -4,6 +4,7 @@ import com.dotcms.Junit5WeldBaseTest; import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.datagen.ContentTypeDataGen; import com.dotcms.datagen.TestDataUtils; import com.dotcms.datagen.TestUserUtils; import com.dotcms.jobs.business.api.JobQueueManagerAPI; @@ -16,14 +17,13 @@ import com.dotmarketing.beans.Host; import com.dotmarketing.business.APILocator; import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.portlets.languagesmanager.model.Language; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.liferay.portal.model.User; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.jboss.weld.junit5.EnableWeld; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -48,10 +48,14 @@ public class ContentImportResourceIntegrationTest extends Junit5WeldBaseTest { private static Host defaultSite; private static ObjectMapper mapper; private static ContentImportResource importResource; + private static Language defaultLanguage; private final static String IMPORT_QUEUE_NAME = "importContentlets"; private static final String CMD_PUBLISH = com.dotmarketing.util.Constants.PUBLISH; + private static File csvFile; + private static ContentType contentType; + @Inject ContentImportHelper contentImportHelper; @@ -67,6 +71,10 @@ static void setUp() throws Exception { request = JobUtil.generateMockRequest(adminUser, defaultSite.getHostname()); response = new MockHttpResponse(); mapper = new ObjectMapper(); + + defaultLanguage = APILocator.getLanguageAPI().getDefaultLanguage(); + contentType = TestDataUtils.getRichTextLikeContentType(); + csvFile = createTestCsvFile(); } @BeforeEach @@ -74,6 +82,16 @@ void prepare() { importResource = new ContentImportResource(contentImportHelper); } + @AfterAll + static void cleanup() { + // Clean up the test file + if (csvFile != null && csvFile.exists()) { + csvFile.delete(); + } + // Clean up the test content type + ContentTypeDataGen.remove(contentType); + } + /** * Scenario: Import content with all parameters being passed (csv file, content type, language, workflow action, and fields). *

@@ -84,14 +102,11 @@ void prepare() { */ @Test public void test_import_content_with_valid_params() throws IOException, DotDataException { - ContentType contentType = TestDataUtils.getRichTextLikeContentType(); - File csvFile = createTestCsvFile(); - - ContentImportForm form = createContentImportForm(contentType.name(), "1", "workflow-action-id", List.of("title")); + ContentImportForm form = createContentImportForm(contentType.name(), String.valueOf(defaultLanguage.getId()), "workflow-action-id", List.of("title")); ContentImportParams params = createContentImportParams(csvFile, form); Response importContentResponse = importResource.importContent(request, response, params); - validateSuccessfulResponse(importContentResponse, contentType.name(), "1", List.of("title"), "workflow-action-id", CMD_PUBLISH); + validateSuccessfulResponse(importContentResponse, contentType.name(), String.valueOf(defaultLanguage.getId()), List.of("title"), "workflow-action-id", CMD_PUBLISH); } /** @@ -104,14 +119,11 @@ public void test_import_content_with_valid_params() throws IOException, DotDataE */ @Test public void test_import_content_with_language_iso_code() throws IOException, DotDataException { - ContentType contentType = TestDataUtils.getRichTextLikeContentType(); - File csvFile = createTestCsvFile(); - - ContentImportForm form = createContentImportForm(contentType.name(), "en-us", "workflow-action-id", List.of("title")); + ContentImportForm form = createContentImportForm(contentType.name(), defaultLanguage.getIsoCode(), "workflow-action-id", List.of("title")); ContentImportParams params = createContentImportParams(csvFile, form); Response importContentResponse = importResource.importContent(request, response, params); - validateSuccessfulResponse(importContentResponse, contentType.name(), "en-us", List.of("title"), "workflow-action-id", CMD_PUBLISH); + validateSuccessfulResponse(importContentResponse, contentType.name(), defaultLanguage.getIsoCode(), List.of("title"), "workflow-action-id", CMD_PUBLISH); } /** @@ -126,9 +138,6 @@ public void test_import_content_with_language_iso_code() throws IOException, Dot */ @Test public void test_import_content_without_language_and_field_params() throws IOException, DotDataException { - ContentType contentType = TestDataUtils.getRichTextLikeContentType(); - File csvFile = createTestCsvFile(); - ContentImportForm form = createContentImportForm(contentType.name(), null, "workflow-action-id-2", null); ContentImportParams params = createContentImportParams(csvFile, form); @@ -146,10 +155,7 @@ public void test_import_content_without_language_and_field_params() throws IOExc */ @Test public void test_import_content_with_invalid_language() throws IOException, DotDataException { - ContentType contentType = TestDataUtils.getRichTextLikeContentType(); - File csvFile = createTestCsvFile(); - - ContentImportForm form = createContentImportForm(contentType.name(), "123", "workflow-action-id-2", null); + ContentImportForm form = createContentImportForm(contentType.name(), "12345", "workflow-action-id-2", null); ContentImportParams params = createContentImportParams(csvFile, form); assertBadRequestResponse(importResource.importContent(request, response, params)); @@ -165,8 +171,6 @@ public void test_import_content_with_invalid_language() throws IOException, DotD */ @Test public void test_import_content_with_invalid_content_type() throws IOException, DotDataException { - File csvFile = createTestCsvFile(); - ContentImportForm form = createContentImportForm("doesNotExist", "12345", "workflow-action-id-2", null); ContentImportParams params = createContentImportParams(csvFile, form); @@ -197,7 +201,6 @@ public void test_import_content_without_content_type_in_form() { */ @Test public void test_import_content_without_workflow_action_in_form() { - ContentType contentType = TestDataUtils.getRichTextLikeContentType(); assertThrows(ValidationException.class, () -> createContentImportForm(contentType.name(), null, null, null)); } @@ -212,8 +215,7 @@ public void test_import_content_without_workflow_action_in_form() { */ @Test public void test_import_content_missing_file() throws JsonProcessingException { - ContentType contentType = TestDataUtils.getRichTextLikeContentType(); - ContentImportForm form = createContentImportForm(contentType.name(), "1", "workflow-action-id", null); + ContentImportForm form = createContentImportForm(contentType.name(), String.valueOf(defaultLanguage.getId()), "workflow-action-id", null); ContentImportParams params = new ContentImportParams(); params.setJsonForm(mapper.writeValueAsString(form)); @@ -232,8 +234,6 @@ public void test_import_content_missing_file() throws JsonProcessingException { */ @Test public void test_import_content_missing_form() throws IOException { - File csvFile = createTestCsvFile(); - ContentImportParams params = new ContentImportParams(); params.setFileInputStream(new FileInputStream(csvFile)); params.setContentDisposition(createContentDisposition(csvFile.getName())); @@ -309,7 +309,7 @@ private void validateSuccessfulResponse(Response response, String expectedConten * @return A temporary File object containing test CSV data * @throws IOException if there's an error creating or writing to the temporary file */ - private File createTestCsvFile() throws IOException { + private static File createTestCsvFile() throws IOException { String csv = "title,body\nTest Title 1,Test Body 1\nTest Title 2,Test Body 2\n"; File csvFile = File.createTempFile("test", ".csv"); Files.write(csvFile.toPath(), csv.getBytes());