From f4195a767cf92fe38cbf04657e5badb405e74318 Mon Sep 17 00:00:00 2001
From: Valentino Giardino <77643678+valentinogiardino@users.noreply.github.com>
Date: Mon, 25 Nov 2024 14:20:04 -0300
Subject: [PATCH] feat(JobQueue) Implement import content job rest endpoint
#30669 (#30698)
### Proposed Changes
* **Added a new REST resource (`ContentImportResource`)** to handle
content import job operations, including creating and enqueuing content
import jobs.
* **Added a helper class (`ContentImportHelper`)** to manage content
import operations, including methods for creating and managing jobs.
* **Created a bean class (`ContentImportParams`)** to encapsulate
multipart form parameters for content import operations.
* **Introduced a form object (`ContentImportForm`)** to represent JSON
parameters for content import operations.
### Checklist
- [x] Tests
- [x] Translations
- [x] Security Implications Contemplated (add notes if applicable)
### Additional Info
These changes aim to provide a REST endpoint for managing content import
jobs, enabling operations like job creation and queueing.
---
.../rest/api/v1/JobQueueManagerHelper.java | 121 +++
.../v1/content/_import/ContentImportForm.java | 64 ++
.../content/_import/ContentImportHelper.java | 193 +++++
.../content/_import/ContentImportParams.java | 89 ++
.../_import/ContentImportResource.java | 131 +++
.../rest/api/v1/job/JobQueueHelper.java | 79 +-
.../test/java/com/dotcms/Junit5Suite1.java | 4 +-
.../src/test/java/com/dotcms/MainSuite2b.java | 2 +-
.../ContentImportResourceIntegrationTest.java | 378 +++++++++
dotcms-postman/config.json | 1 +
...tentImportResource.postman_collection.json | 798 ++++++++++++++++++
.../test-import-content-job-final.csv | 2 +
12 files changed, 1796 insertions(+), 66 deletions(-)
create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java
create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportForm.java
create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java
create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportParams.java
create mode 100644 dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportResource.java
create mode 100644 dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java
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/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..38f104aed278
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java
@@ -0,0 +1,121 @@
+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.dotcms.util.AnnotationUtils;
+import com.dotmarketing.util.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+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.
+ */
+@ApplicationScoped
+public class JobQueueManagerHelper {
+
+ private final JobQueueManagerAPI jobQueueManagerAPI;
+ private final JobProcessorScanner scanner;
+
+ /**
+ * 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, final JobQueueManagerAPI jobQueueManagerAPI) {
+ this.scanner = scanner;
+ this.jobQueueManagerAPI = jobQueueManagerAPI;
+ }
+
+ /**
+ * Default constructor required by CDI.
+ */
+ public JobQueueManagerHelper() {
+ this.scanner = null;
+ this.jobQueueManagerAPI = null;
+ }
+
+ /**
+ * Registers all discovered job processors with the JobQueueManagerAPI.
+ * If the JobQueueManagerAPI is not started, it starts the API before registering the processors.
+ */
+ public void registerProcessors() {
+ if (!jobQueueManagerAPI.isStarted()) {
+ jobQueueManagerAPI.start();
+ Logger.info(this.getClass(), "JobQueueManagerAPI started");
+ }
+
+ List> processors = scanner.discoverJobProcessors();
+ processors.forEach(processor -> {
+ try {
+ 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);
+ }
+ });
+ }
+
+ /**
+ * 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 extends JobProcessor> processor) {
+ try {
+ Constructor extends JobProcessor> 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;
+ }
+
+ /**
+ * 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 processor the processor class to register
+ */
+ private void registerProcessor(final Class extends JobProcessor> processor) {
+ Queue queue = AnnotationUtils.getBeanAnnotation(processor, Queue.class);
+ if (Objects.nonNull(queue)) {
+ jobQueueManagerAPI.registerProcessor(queue.value(), processor);
+ } else {
+ jobQueueManagerAPI.registerProcessor(processor.getName(), processor);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void shutdown() {
+ 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/content/_import/ContentImportForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportForm.java
new file mode 100644
index 000000000000..abb1cd2f446f
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportForm.java
@@ -0,0 +1,64 @@
+package com.dotcms.rest.api.v1.content._import;
+
+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 = "A Content Type id or variable is required")
+ private final String contentType;
+
+ private final String language;
+
+ @NotNull(message = "A 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;
+ this.checkValid();
+ }
+
+ 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
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
new file mode 100644
index 000000000000..aebe5847b44f
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java
@@ -0,0 +1,193 @@
+package com.dotcms.rest.api.v1.content._import;
+
+import com.dotcms.jobs.business.api.JobQueueManagerAPI;
+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;
+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.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 final JobQueueManagerAPI jobQueueManagerAPI;
+ private final 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() {
+ this.jobQueueManagerAPI = null;
+ this.jobQueueManagerHelper = null;
+ }
+
+ /**
+ * 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 and submits it to the job queue.
+ *
+ * @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 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);
+
+ return jobQueueManagerAPI.createJob(queueName, jobParameters);
+ }
+
+ /**
+ * 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,
+ final ContentImportParams params,
+ final User user,
+ final HttpServletRequest request) throws JsonProcessingException {
+
+ final Map jobParameters = new HashMap<>();
+
+ // Add required parameters
+ jobParameters.put("cmd", command);
+ 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 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 ContentImportParams params,
+ final Map jobParameters) throws JsonProcessingException {
+
+ final 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 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 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 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,
+ final Map jobParameters,
+ final HttpServletRequest request) throws DotDataException {
+
+ 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportParams.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportParams.java
new file mode 100644
index 000000000000..61e993280d11
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportParams.java
@@ -0,0 +1,89 @@
+package com.dotcms.rest.api.v1.content._import;
+
+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 net.minidev.json.annotate.JsonIgnore;
+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 {
+
+ @NotNull(message = "The file is required.")
+ @FormDataParam("file")
+ private InputStream fileInputStream;
+
+ @JsonIgnore
+ @FormDataParam("file")
+ private FormDataContentDisposition contentDisposition;
+
+ @FormDataParam("form")
+ private ContentImportForm form;
+
+ @NotNull(message = "The form data is required.")
+ @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 void setJsonForm(String jsonForm) {
+ this.jsonForm = jsonForm;
+ }
+
+ public String getJsonForm() {
+ return jsonForm;
+ }
+
+ public void setForm(ContentImportForm form) {
+ this.form = form;
+ }
+
+ /**
+ * Gets the parsed form object, lazily parsing the JSON if needed
+ * @return The ContentImportForm object
+ */
+ public ContentImportForm getForm() throws JsonProcessingException {
+ if (null == form && (null != jsonForm && !jsonForm.isEmpty())) {
+ form = new ObjectMapper().readValue(jsonForm, ContentImportForm.class);
+ }
+ return form;
+ }
+
+ @Override
+ public String toString() {
+ return "ContentImportParams{" +
+ "form=" + getJsonForm() +
+ ", hasFile=" + (fileInputStream != null) +
+ ", 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
diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportResource.java
new file mode 100644
index 000000000000..07319756379d
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportResource.java
@@ -0,0 +1,131 @@
+package com.dotcms.rest.api.v1.content._import;
+
+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.dotmarketing.util.Logger;
+import com.fasterxml.jackson.core.JsonProcessingException;
+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;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+
+/**
+ * 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 {
+
+ private final WebResource webResource;
+ private final ContentImportHelper importHelper;
+ private static final String IMPORT_QUEUE_NAME = "importContentlets";
+
+ // Constants for commands
+ private static final String CMD_PUBLISH = com.dotmarketing.util.Constants.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, 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 {
+
+ // Initialize the WebResource and set required user information
+ final var initDataObject = new WebResource.InitBuilder(webResource)
+ .requiredBackendUser(true)
+ .requiredFrontendUser(false)
+ .requestAndResponse(request, response)
+ .rejectWhenNoUser(true)
+ .init();
+
+ Logger.debug(this, ()->String.format(" user %s is importing content: %s", initDataObject.getUser().getUserId(), 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) {
+ // Handle validation exception and return appropriate error message
+ return ExceptionMapperUtil.createResponse(null, e.getMessage());
+ }
+ }
+}
\ No newline at end of file
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..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,13 @@
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;
import com.dotmarketing.business.APILocator;
@@ -22,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;
@@ -45,73 +42,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 extends JobProcessor> processor) {
- try {
- final Constructor extends JobProcessor> 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;
}
/**
@@ -124,6 +65,16 @@ void registerProcessor(final String queueName, final Class extends JobProcesso
jobQueueManagerAPI.registerProcessor(queueName, processor);
}
+ @PostConstruct
+ public void onInit() {
+ jobQueueManagerHelper.registerProcessors();
+ }
+
+ @PreDestroy
+ public void onDestroy() {
+ jobQueueManagerHelper.shutdown();
+ }
+
/**
* Creates a job
*
diff --git a/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java b/dotcms-integration/src/test/java/com/dotcms/Junit5Suite1.java
index 4055eca82377..57c98fd8e38e 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;
@@ -14,7 +15,8 @@
PostgresJobQueueIntegrationTest.class,
JobQueueManagerAPIIntegrationTest.class,
JobQueueHelperIntegrationTest.class,
- ImportContentletsProcessorIntegrationTest.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 da6b48b34deb..5b338462d195 100644
--- a/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java
+++ b/dotcms-integration/src/test/java/com/dotcms/MainSuite2b.java
@@ -414,7 +414,7 @@
FilesCollectorTest.class,
SyncVanitiesCollectorTest.class,
AsyncVanitiesCollectorTest.class,
- HttpServletRequestImpersonatorTest.class
+ HttpServletRequestImpersonatorTest.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
new file mode 100644
index 000000000000..e005d0ca2296
--- /dev/null
+++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/_import/ContentImportResourceIntegrationTest.java
@@ -0,0 +1,378 @@
+package com.dotcms.rest.api.v1.content._import;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+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;
+import com.dotcms.jobs.business.job.Job;
+import com.dotcms.jobs.business.util.JobUtil;
+import com.dotcms.mock.response.MockHttpResponse;
+import com.dotcms.rest.ResponseEntityView;
+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.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.*;
+
+import javax.inject.Inject;
+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;
+import java.nio.file.Files;
+import java.util.List;
+
+/**
+ * Integration test suite for content import functionality.
+ * Tests the ContentImportResource API endpoints for various scenarios.
+ */
+@EnableWeld
+public class ContentImportResourceIntegrationTest 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;
+ 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;
+
+ @Inject
+ JobQueueManagerAPI jobQueueManagerAPI;
+
+ @BeforeAll
+ static void setUp() throws Exception {
+ IntegrationTestInitService.getInstance().init();
+
+ adminUser = TestUserUtils.getAdminUser();
+ defaultSite = APILocator.getHostAPI().findDefaultHost(adminUser, false);
+ request = JobUtil.generateMockRequest(adminUser, defaultSite.getHostname());
+ response = new MockHttpResponse();
+ mapper = new ObjectMapper();
+
+ defaultLanguage = APILocator.getLanguageAPI().getDefaultLanguage();
+ contentType = TestDataUtils.getRichTextLikeContentType();
+ csvFile = createTestCsvFile();
+ }
+
+ @BeforeEach
+ 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).
+ *
+ * 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 {
+ 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(), String.valueOf(defaultLanguage.getId()), List.of("title"), "workflow-action-id", CMD_PUBLISH);
+ }
+
+ /**
+ * 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_with_language_iso_code() throws IOException, DotDataException {
+ 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(), defaultLanguage.getIsoCode(), List.of("title"), "workflow-action-id", CMD_PUBLISH);
+ }
+
+ /**
+ * 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_language_and_field_params() throws IOException, DotDataException {
+ ContentImportForm form = createContentImportForm(contentType.name(), null, "workflow-action-id-2", null);
+ ContentImportParams params = createContentImportParams(csvFile, form);
+
+ // Assert that the response status is BAD_REQUEST (400)
+ assertBadRequestResponse(importResource.importContent(request, response, params));
+ }
+
+ /**
+ * 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_with_invalid_language() throws IOException, DotDataException {
+ ContentImportForm form = createContentImportForm(contentType.name(), "12345", "workflow-action-id-2", null);
+ ContentImportParams params = createContentImportParams(csvFile, form);
+
+ 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 {
+ 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));
+ }
+
+ /**
+ * 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_without_workflow_action_in_form() {
+ 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 {
+ ContentImportForm form = createContentImportForm(contentType.name(), String.valueOf(defaultLanguage.getId()), "workflow-action-id", null);
+
+ ContentImportParams params = new ContentImportParams();
+ params.setJsonForm(mapper.writeValueAsString(form));
+
+ assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params));
+ }
+
+ /**
+ * 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 IOException {
+ ContentImportParams params = new ContentImportParams();
+ params.setFileInputStream(new FileInputStream(csvFile));
+ params.setContentDisposition(createContentDisposition(csvFile.getName()));
+
+ assertThrows(ValidationException.class, () -> importResource.importContent(request, response, params));
+ }
+
+ /**
+ * 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 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(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 = jobQueueManagerAPI.getJob(responseEntityView.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");
+ }
+ }
+
+ /**
+ * 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");
+ Files.write(csvFile.toPath(), csv.getBytes());
+ 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 FormDataContentDisposition createContentDisposition(String filename) {
+ return FormDataContentDisposition
+ .name("file")
+ .fileName(filename)
+ .size(100L)
+ .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 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;
+ }
+
+ /**
+ * 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 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 void assertBadRequestResponse(Response importContentResponse) {
+ assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), importContentResponse.getStatus(), "Expected BAD_REQUEST status");
+ }
+}
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
new file mode 100644
index 000000000000..84d80490cada
--- /dev/null
+++ b/dotcms-postman/src/main/resources/postman/ContentImportResource.postman_collection.json
@@ -0,0 +1,798 @@
+{
+ "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",
+ "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);",
+ "});",
+ ""
+ ],
+ "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",
+ "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.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');",
+ "",
+ " 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\")));",
+ "",
+ "});"
+ ],
+ "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);",
+ "});",
+ "",
+ "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;",
+ "});",
+ ""
+ ],
+ "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",
+ "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;",
+ "});",
+ ""
+ ],
+ "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",
+ "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;",
+ "});",
+ ""
+ ],
+ "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);",
+ "});",
+ "",
+ "",
+ "// 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.');",
+ "});",
+ ""
+ ],
+ "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);",
+ "});",
+ "",
+ "",
+ "// 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.');",
+ "});",
+ ""
+ ],
+ "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);",
+ "});",
+ "",
+ "// 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.');",
+ "});",
+ ""
+ ],
+ "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": "TestImportJob",
+ "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