diff --git a/connectors/google/google-drive/element-templates/google-drive-outbound-connector.json b/connectors/google/google-drive/element-templates/google-drive-outbound-connector.json index 3a9f07aa9a..e55438df4c 100644 --- a/connectors/google/google-drive/element-templates/google-drive-outbound-connector.json +++ b/connectors/google/google-drive/element-templates/google-drive-outbound-connector.json @@ -163,6 +163,12 @@ }, { "name" : "Create file from template", "value" : "file" + }, { + "name" : "Upload file", + "value" : "upload" + }, { + "name" : "Download file", + "value" : "download" } ] }, { "id" : "resource.name", @@ -177,6 +183,11 @@ "name" : "resource.name", "type" : "zeebe:input" }, + "condition" : { + "property" : "resource.type", + "oneOf" : [ "folder", "file" ], + "type" : "simple" + }, "type" : "String" }, { "id" : "resource.parent", @@ -189,6 +200,11 @@ "name" : "resource.parent", "type" : "zeebe:input" }, + "condition" : { + "property" : "resource.type", + "oneOf" : [ "folder", "file", "upload" ], + "type" : "simple" + }, "type" : "String" }, { "id" : "resource.additionalGoogleDriveProperties", @@ -241,6 +257,44 @@ "type" : "simple" }, "type" : "String" + }, { + "id" : "resource.downloadData.fileId", + "label" : "File ID", + "optional" : false, + "constraints" : { + "notEmpty" : true + }, + "feel" : "optional", + "group" : "operationDetails", + "binding" : { + "name" : "resource.downloadData.fileId", + "type" : "zeebe:input" + }, + "condition" : { + "property" : "resource.type", + "equals" : "download", + "type" : "simple" + }, + "type" : "String" + }, { + "id" : "resource.uploadData.document", + "label" : "Document", + "optional" : false, + "constraints" : { + "notEmpty" : true + }, + "feel" : "required", + "group" : "operationDetails", + "binding" : { + "name" : "resource.uploadData.document", + "type" : "zeebe:input" + }, + "condition" : { + "property" : "resource.type", + "equals" : "upload", + "type" : "simple" + }, + "type" : "String" }, { "id" : "resultVariable", "label" : "Result variable", diff --git a/connectors/google/google-drive/element-templates/hybrid/google-drive-outbound-connector-hybrid.json b/connectors/google/google-drive/element-templates/hybrid/google-drive-outbound-connector-hybrid.json index bb63c5a67a..4e6d067f1d 100644 --- a/connectors/google/google-drive/element-templates/hybrid/google-drive-outbound-connector-hybrid.json +++ b/connectors/google/google-drive/element-templates/hybrid/google-drive-outbound-connector-hybrid.json @@ -168,6 +168,12 @@ }, { "name" : "Create file from template", "value" : "file" + }, { + "name" : "Upload file", + "value" : "upload" + }, { + "name" : "Download file", + "value" : "download" } ] }, { "id" : "resource.name", @@ -182,6 +188,11 @@ "name" : "resource.name", "type" : "zeebe:input" }, + "condition" : { + "property" : "resource.type", + "oneOf" : [ "folder", "file" ], + "type" : "simple" + }, "type" : "String" }, { "id" : "resource.parent", @@ -194,6 +205,11 @@ "name" : "resource.parent", "type" : "zeebe:input" }, + "condition" : { + "property" : "resource.type", + "oneOf" : [ "folder", "file", "upload" ], + "type" : "simple" + }, "type" : "String" }, { "id" : "resource.additionalGoogleDriveProperties", @@ -246,6 +262,44 @@ "type" : "simple" }, "type" : "String" + }, { + "id" : "resource.downloadData.fileId", + "label" : "File ID", + "optional" : false, + "constraints" : { + "notEmpty" : true + }, + "feel" : "optional", + "group" : "operationDetails", + "binding" : { + "name" : "resource.downloadData.fileId", + "type" : "zeebe:input" + }, + "condition" : { + "property" : "resource.type", + "equals" : "download", + "type" : "simple" + }, + "type" : "String" + }, { + "id" : "resource.uploadData.document", + "label" : "Document", + "optional" : false, + "constraints" : { + "notEmpty" : true + }, + "feel" : "required", + "group" : "operationDetails", + "binding" : { + "name" : "resource.uploadData.document", + "type" : "zeebe:input" + }, + "condition" : { + "property" : "resource.type", + "equals" : "upload", + "type" : "simple" + }, + "type" : "String" }, { "id" : "resultVariable", "label" : "Result variable", diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveClient.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveClient.java index d5d101f8e8..19d2750e40 100644 --- a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveClient.java +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveClient.java @@ -51,4 +51,12 @@ public BatchUpdateDocumentResponse updateDocument( throw new RuntimeException(e); } } + + public Drive getDriveService() { + return driveService; + } + + public Docs getDocsService() { + return docsService; + } } diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveFunction.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveFunction.java index d40dc78d6f..b41d2df67c 100644 --- a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveFunction.java +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveFunction.java @@ -9,7 +9,7 @@ import io.camunda.connector.api.annotation.OutboundConnector; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.api.outbound.OutboundConnectorFunction; -import io.camunda.connector.gdrive.model.GoogleDriveResult; +import io.camunda.connector.gdrive.mapper.DocumentMapper; import io.camunda.connector.gdrive.model.request.GoogleDriveRequest; import io.camunda.connector.gdrive.supliers.GoogleDocsServiceSupplier; import io.camunda.connector.generator.java.annotation.ElementTemplate; @@ -53,16 +53,21 @@ public GoogleDriveFunction(final GoogleDriveService service) { @Override public Object execute(final OutboundConnectorContext context) { + var request = context.bindVariables(GoogleDriveRequest.class); + service.setDocumentMapper(new DocumentMapper(context)); + return executeConnector(request); } - private GoogleDriveResult executeConnector(final GoogleDriveRequest request) { + private Object executeConnector(final GoogleDriveRequest request) { LOGGER.debug("Executing my connector with request {}", request); + GoogleDriveClient drive = new GoogleDriveClient( GoogleDriveServiceSupplier.createDriveClientInstance(request.getAuthentication()), GoogleDocsServiceSupplier.createDocsClientInstance(request.getAuthentication())); + return service.execute(drive, request.getResource()); } } diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveService.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveService.java index 0a9265e4be..ab7eb083a7 100644 --- a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveService.java +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/GoogleDriveService.java @@ -6,37 +6,53 @@ */ package io.camunda.connector.gdrive; +import com.google.api.client.http.ByteArrayContent; import com.google.api.client.json.JsonParser; import com.google.api.client.json.gson.GsonFactory; import com.google.api.services.docs.v1.model.BatchUpdateDocumentResponse; import com.google.api.services.docs.v1.model.Request; +import com.google.api.services.drive.Drive; import com.google.api.services.drive.model.File; import com.google.common.reflect.TypeToken; +import io.camunda.connector.gdrive.mapper.DocumentMapper; import io.camunda.connector.gdrive.model.GoogleDriveResult; import io.camunda.connector.gdrive.model.MimeTypeUrl; import io.camunda.connector.gdrive.model.request.Resource; import io.camunda.connector.gdrive.model.request.Template; import io.camunda.connector.gdrive.model.request.Type; import io.camunda.connector.gdrive.model.request.Variables; +import io.camunda.document.Document; import io.camunda.google.supplier.GsonComponentSupplier; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class GoogleDriveService { + + public static final long MAX_DIRECT_UPLOAD_FILE_SIZE_BYTES = 5_242_880L; // 5MB private static final Logger LOGGER = LoggerFactory.getLogger(GoogleDriveService.class); private final GsonFactory gsonFactory = GsonComponentSupplier.gsonFactoryInstance(); + private DocumentMapper documentMapper; + + public GoogleDriveService(DocumentMapper documentMapper) { + this.documentMapper = documentMapper; + } + public GoogleDriveService() {} - public GoogleDriveResult execute(final GoogleDriveClient client, final Resource resource) { + public Object execute(final GoogleDriveClient client, final Resource resource) { return switch (resource.type()) { case FOLDER -> createFolder(client, resource); case FILE -> createFile(client, resource); + case UPLOAD -> uploadFile(client, resource); + case DOWNLOAD -> downloadFile(client, resource); }; } @@ -116,4 +132,55 @@ private void updateWithRequests( metaData.getMimeType()); } } + + private GoogleDriveResult uploadFile(final GoogleDriveClient client, final Resource resource) { + try { + var document = resource.uploadData().document(); + File fileMetaData = prepareFileMetaData(document, resource.parent()); + + var content = + new ByteArrayContent(document.metadata().getContentType(), document.asByteArray()); + + Drive drive = client.getDriveService(); + Drive.Files.Create createRequest = drive.files().create(fileMetaData, content); + + if (document.metadata().getSize() > MAX_DIRECT_UPLOAD_FILE_SIZE_BYTES) { + createRequest.getMediaHttpUploader().setProgressListener(new LoggerProgressListener()); + } + + File file = createRequest.execute(); + return new GoogleDriveResult(file.getId(), MimeTypeUrl.getFileUrl(file.getId())); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Document downloadFile(final GoogleDriveClient client, final Resource resource) { + Drive drive = client.getDriveService(); + try { + String fileId = resource.downloadData().fileId(); + File fileMetaData = drive.files().get(fileId).execute(); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + drive.files().get(fileId).executeMediaAndDownloadTo(outputStream); + return documentMapper.mapToDocument(outputStream.toByteArray(), fileMetaData); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private File prepareFileMetaData(Document document, String parent) { + File fileMetaData = new File(); + fileMetaData.setName(document.metadata().getFileName()); + + Optional.ofNullable(parent) + .filter(StringUtils::isNoneBlank) + .ifPresent(folder -> fileMetaData.setParents(List.of(folder))); + + return fileMetaData; + } + + public void setDocumentMapper(DocumentMapper documentMapper) { + this.documentMapper = documentMapper; + } } diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/LoggerProgressListener.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/LoggerProgressListener.java new file mode 100644 index 0000000000..517bb7bbb8 --- /dev/null +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/LoggerProgressListener.java @@ -0,0 +1,41 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.gdrive; + +import com.google.api.client.googleapis.media.MediaHttpUploader; +import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Resumable upload official example: ... + */ +public class LoggerProgressListener implements MediaHttpUploaderProgressListener { + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerProgressListener.class); + + @Override + public void progressChanged(MediaHttpUploader uploader) throws IOException { + switch (uploader.getUploadState()) { + case NOT_STARTED: + LOGGER.debug("Upload not started"); + break; + case INITIATION_STARTED: + LOGGER.debug("Initiation has started!"); + break; + case INITIATION_COMPLETE: + LOGGER.debug("Initiation is complete!"); + break; + case MEDIA_IN_PROGRESS: + LOGGER.debug("Upload progress: {}", uploader.getProgress()); + break; + case MEDIA_COMPLETE: + LOGGER.debug("Upload is complete!"); + } + } +} diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/mapper/DocumentMapper.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/mapper/DocumentMapper.java new file mode 100644 index 0000000000..f136b8b8ae --- /dev/null +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/mapper/DocumentMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.gdrive.mapper; + +import com.google.api.services.drive.model.File; +import io.camunda.connector.api.outbound.OutboundConnectorContext; +import io.camunda.document.Document; +import io.camunda.document.store.DocumentCreationRequest; + +public class DocumentMapper { + + private final OutboundConnectorContext context; + + public DocumentMapper(OutboundConnectorContext context) { + this.context = context; + } + + public Document mapToDocument(byte[] bytes, File fileMetaData) { + return context.createDocument( + DocumentCreationRequest.from(bytes) + .contentType(fileMetaData.getMimeType()) + .fileName(fileMetaData.getName()) + .build()); + } +} diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/MimeTypeUrl.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/MimeTypeUrl.java index dc3d0ebf54..33a12426b3 100644 --- a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/MimeTypeUrl.java +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/MimeTypeUrl.java @@ -18,6 +18,8 @@ public enum MimeTypeUrl { "application/vnd.google-apps.presentation", "https://docs.google.com/presentation/d/%s"), FORM("application/vnd.google-apps.form", "https://docs.google.com/forms/d/%s"); + public static final String FILE_TEMPLATE_URL = "https://drive.google.com/file/d/%s/view"; + private String mimeType; private String templateUrl; @@ -41,6 +43,10 @@ public static String getResourceUrl(final String mimeType, final String id) { .orElse(null); } + public static String getFileUrl(final String id) { + return String.format(FILE_TEMPLATE_URL, id); + } + public String getMimeType() { return mimeType; } diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/DownloadData.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/DownloadData.java new file mode 100644 index 0000000000..574b396b26 --- /dev/null +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/DownloadData.java @@ -0,0 +1,22 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.gdrive.model.request; + +import io.camunda.connector.generator.dsl.Property; +import io.camunda.connector.generator.java.annotation.TemplateProperty; + +public record DownloadData( + @TemplateProperty( + group = "operationDetails", + label = "File ID", + feel = Property.FeelMode.optional, + condition = + @TemplateProperty.PropertyCondition( + property = "resource.type", + equals = "download"), + constraints = @TemplateProperty.PropertyConstraints(notEmpty = true)) + String fileId) {} diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Resource.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Resource.java index 3db4f48264..cd286f9f24 100644 --- a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Resource.java +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Resource.java @@ -15,7 +15,6 @@ import io.camunda.connector.generator.java.annotation.TemplateProperty.PropertyCondition; import io.camunda.connector.generator.java.annotation.TemplateProperty.PropertyConstraints; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; public record Resource( @@ -28,7 +27,9 @@ public record Resource( constraints = @PropertyConstraints(notEmpty = true), choices = { @DropdownPropertyChoice(label = "Create folder", value = "folder"), - @DropdownPropertyChoice(label = "Create file from template", value = "file") + @DropdownPropertyChoice(label = "Create file from template", value = "file"), + @DropdownPropertyChoice(label = "Upload file", value = "upload"), + @DropdownPropertyChoice(label = "Download file", value = "download") }) @NotNull Type type, @@ -36,8 +37,12 @@ public record Resource( id = "name", label = "New resource name", group = "operationDetails", - feel = FeelMode.optional) - @NotEmpty + feel = FeelMode.optional, + condition = + @TemplateProperty.PropertyCondition( + property = "resource.type", + oneOf = {"folder", "file"}), + constraints = @TemplateProperty.PropertyConstraints(notEmpty = true)) String name, @TemplateProperty( id = "parent", @@ -47,7 +52,11 @@ public record Resource( + "If left empty, a new resource will appear in the root folder", group = "operationDetails", optional = true, - feel = FeelMode.optional) + feel = FeelMode.optional, + condition = + @TemplateProperty.PropertyCondition( + property = "resource.type", + oneOf = {"folder", "file", "upload"})) String parent, @TemplateProperty( id = "additionalGoogleDriveProperties", @@ -57,4 +66,6 @@ public record Resource( optional = true, condition = @PropertyCondition(property = "resource.type", equals = "folder")) JsonNode additionalGoogleDriveProperties, - @Valid Template template) {} + @Valid Template template, + @Valid DownloadData downloadData, + @Valid UploadData uploadData) {} diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Type.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Type.java index 5405731bc8..b8dc8a97fa 100644 --- a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Type.java +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/Type.java @@ -12,5 +12,9 @@ public enum Type { @JsonProperty("folder") FOLDER, @JsonProperty("file") - FILE + FILE, + @JsonProperty("upload") + UPLOAD, + @JsonProperty("download") + DOWNLOAD } diff --git a/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/UploadData.java b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/UploadData.java new file mode 100644 index 0000000000..97f764e8be --- /dev/null +++ b/connectors/google/google-drive/src/main/java/io/camunda/connector/gdrive/model/request/UploadData.java @@ -0,0 +1,22 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.gdrive.model.request; + +import io.camunda.connector.generator.dsl.Property; +import io.camunda.connector.generator.java.annotation.TemplateProperty; +import io.camunda.document.Document; + +public record UploadData( + @TemplateProperty( + group = "operationDetails", + label = "Document", + feel = Property.FeelMode.required, + type = TemplateProperty.PropertyType.String, + condition = + @TemplateProperty.PropertyCondition(property = "resource.type", equals = "upload"), + constraints = @TemplateProperty.PropertyConstraints(notEmpty = true)) + Document document) {} diff --git a/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveFunctionTest.java b/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveFunctionTest.java index 15f3003e8a..d35d72d97a 100644 --- a/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveFunctionTest.java +++ b/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveFunctionTest.java @@ -8,11 +8,14 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.gdrive.model.GoogleDriveResult; +import io.camunda.connector.gdrive.model.MimeTypeUrl; import io.camunda.connector.gdrive.model.request.Resource; import io.camunda.connector.validation.impl.DefaultValidationProvider; +import io.camunda.document.Document; import java.io.IOException; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -24,13 +27,17 @@ class GoogleDriveFunctionTest extends BaseTest { private static final String SUCCESS_CASES_RESOURCE_PATH = "src/test/resources/requests/request-success-test-cases.json"; + private static final String SUCCESS_UPLOAD_RESOURCE_PATH = + "src/test/resources/requests/file-success-upload.json"; + private static final String SUCCESS_DOWNLOAD_RESOURCE_PATH = + "src/test/resources/requests/file-success-download.json"; @DisplayName("Should execute connector and return success result") @ParameterizedTest(name = "Executing test case # {index}") @MethodSource("successRequestCases") void execute_shouldExecuteAndReturnResultWhenGiveContext(String input) { // Given - GoogleDriveService googleDriveServiceMock = Mockito.mock(GoogleDriveService.class); + GoogleDriveService googleDriveServiceMock = mock(GoogleDriveService.class); GoogleDriveFunction service = new GoogleDriveFunction(googleDriveServiceMock); OutboundConnectorContext context = @@ -53,7 +60,65 @@ void execute_shouldExecuteAndReturnResultWhenGiveContext(String input) { assertThat(((GoogleDriveResult) execute).getGoogleDriveResourceUrl()).isEqualTo(FILE_URL); } + @ParameterizedTest(name = "Executing test case # {index}") + @MethodSource("successUploadCases") + void execute_shouldExecuteFileUpload(String input) { + // Given + GoogleDriveService googleDriveServiceMock = mock(GoogleDriveService.class); + GoogleDriveFunction service = new GoogleDriveFunction(googleDriveServiceMock); + + OutboundConnectorContext context = + getContextBuilderWithSecrets() + .variables(input) + .validation(new DefaultValidationProvider()) + .build(); + + GoogleDriveResult googleDriveResult = new GoogleDriveResult(); + googleDriveResult.setGoogleDriveResourceId(FILE_ID); + googleDriveResult.setGoogleDriveResourceUrl( + String.format(MimeTypeUrl.FILE_TEMPLATE_URL, FILE_ID)); + + Mockito.when(googleDriveServiceMock.execute(any(GoogleDriveClient.class), any(Resource.class))) + .thenReturn(googleDriveResult); + // When + Object execute = service.execute(context); + // Then + assertThat(execute).isInstanceOf(GoogleDriveResult.class); + assertThat(((GoogleDriveResult) execute).getGoogleDriveResourceId()).isEqualTo(FILE_ID); + assertThat(((GoogleDriveResult) execute).getGoogleDriveResourceUrl()) + .isEqualTo((String.format(MimeTypeUrl.FILE_TEMPLATE_URL, FILE_ID))); + } + + @ParameterizedTest(name = "Executing test case # {index}") + @MethodSource("successDownloadCases") + void execute_shouldExecuteFileDownload(String input) { + // Given + GoogleDriveService googleDriveServiceMock = mock(GoogleDriveService.class); + GoogleDriveFunction service = new GoogleDriveFunction(googleDriveServiceMock); + + OutboundConnectorContext context = + getContextBuilderWithSecrets() + .variables(input) + .validation(new DefaultValidationProvider()) + .build(); + + Mockito.when(googleDriveServiceMock.execute(any(GoogleDriveClient.class), any(Resource.class))) + .thenReturn(mock(Document.class)); + + var result = (Document) service.execute(context); + + assertThat(result).isInstanceOf(Document.class); + } + private static Stream successRequestCases() throws IOException { return BaseTest.loadTestCasesFromResourceFile(SUCCESS_CASES_RESOURCE_PATH); } + + private static Stream successUploadCases() throws IOException { + return BaseTest.loadTestCasesFromResourceFile(SUCCESS_UPLOAD_RESOURCE_PATH); + } + + private static Stream successDownloadCases() throws IOException { + return BaseTest.loadTestCasesFromResourceFile(SUCCESS_DOWNLOAD_RESOURCE_PATH); + } } diff --git a/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveServiceTest.java b/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveServiceTest.java index edc49cc122..83f134ddf7 100644 --- a/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveServiceTest.java +++ b/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/GoogleDriveServiceTest.java @@ -9,21 +9,25 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; +import com.google.api.client.http.AbstractInputStreamContent; import com.google.api.services.docs.v1.model.BatchUpdateDocumentResponse; import com.google.api.services.docs.v1.model.Request; +import com.google.api.services.drive.Drive; import com.google.api.services.drive.model.File; +import io.camunda.connector.document.annotation.jackson.DocumentReferenceModel; +import io.camunda.connector.gdrive.mapper.DocumentMapper; import io.camunda.connector.gdrive.model.GoogleDriveResult; import io.camunda.connector.gdrive.model.MimeTypeUrl; -import io.camunda.connector.gdrive.model.request.GoogleDriveRequest; +import io.camunda.connector.gdrive.model.request.*; +import io.camunda.document.Document; import java.io.IOException; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; @@ -37,13 +41,15 @@ class GoogleDriveServiceTest extends BaseTest { private static final String SUCCESS_FILE_CASES_RESOURCE_PATH = "src/test/resources/requests/file-success-test-cases.json"; + private DocumentMapper documentMapper; private GoogleDriveService service; private GoogleDriveClient googleDriveClient; private File file; @BeforeEach public void before() { - service = new GoogleDriveService(); + documentMapper = mock(DocumentMapper.class); + service = new GoogleDriveService(documentMapper); googleDriveClient = mock(GoogleDriveClient.class); file = new File(); file.setId(FILE_ID); @@ -60,7 +66,7 @@ void execute_shouldCreateFolderFromRequest(String input) { GoogleDriveRequest request = context.bindVariables(GoogleDriveRequest.class); ArgumentCaptor captor = ArgumentCaptor.forClass(File.class); // When - GoogleDriveResult execute = service.execute(googleDriveClient, request.getResource()); + var execute = (GoogleDriveResult) service.execute(googleDriveClient, request.getResource()); // Then Mockito.verify(googleDriveClient).createWithMetadata(captor.capture()); File value = captor.getValue(); @@ -84,7 +90,7 @@ void execute_shouldCreateFileFromRequest(String input) { Mockito.when(googleDriveClient.createWithTemplate(any(), eq(templateId))).thenReturn(file); ArgumentCaptor captor = ArgumentCaptor.forClass(File.class); // When - GoogleDriveResult execute = service.execute(googleDriveClient, request.getResource()); + var execute = (GoogleDriveResult) service.execute(googleDriveClient, request.getResource()); // Then Mockito.verify(googleDriveClient).createWithTemplate(captor.capture(), eq(templateId)); File value = captor.getValue(); @@ -105,7 +111,7 @@ void execute_shouldCreateFileWithoutTemplate(String input) { Mockito.when(googleDriveClient.createWithTemplate(any(), eq(templateId))).thenReturn(file); ArgumentCaptor captor = ArgumentCaptor.forClass(File.class); // When - GoogleDriveResult execute = service.execute(googleDriveClient, request.getResource()); + var execute = (GoogleDriveResult) service.execute(googleDriveClient, request.getResource()); // Then Mockito.verify(googleDriveClient).createWithTemplate(captor.capture(), eq(templateId)); File value = captor.getValue(); @@ -133,7 +139,7 @@ void execute_shouldCreateFileAndUpdateDocument(String input) { Mockito.when(googleDriveClient.updateDocument(any(), any())).thenReturn(response); ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); // When - GoogleDriveResult execute = service.execute(googleDriveClient, request.getResource()); + var execute = (GoogleDriveResult) service.execute(googleDriveClient, request.getResource()); // Then verify(googleDriveClient).updateDocument(eq(FILE_ID), captor.capture()); @@ -161,6 +167,49 @@ void execute_shouldDoNotUpdateDocumentIfTypeNotDocument(String input) { verify(googleDriveClient, never()).updateDocument(any(), any()); } + @Test + void execute_shouldUploadFile() throws IOException { + // Given + var docMetadata = + new DocumentReferenceModel.CamundaDocumentMetadataModel( + "image/png", null, 66497L, "picture", null, null, null); + + Document document = Mockito.mock(Document.class); + when(document.metadata()).thenReturn(docMetadata); + when(document.asByteArray()).thenReturn(new byte[1]); + + var resource = + new Resource(Type.UPLOAD, null, null, null, null, null, new UploadData(document)); + + Drive drive = mock(Drive.class, Mockito.RETURNS_DEEP_STUBS); + + when(googleDriveClient.getDriveService()).thenReturn(drive); + when(drive.files().create(any(File.class), any(AbstractInputStreamContent.class)).execute()) + .thenReturn(new File().setId("1")); + + var result = service.execute(googleDriveClient, resource); + assertThat(result).isInstanceOf(GoogleDriveResult.class); + assertThat(((GoogleDriveResult) result).getGoogleDriveResourceId()).isEqualTo("1"); + assertThat(((GoogleDriveResult) result).getGoogleDriveResourceUrl()) + .isEqualTo(String.format(MimeTypeUrl.FILE_TEMPLATE_URL, "1")); + } + + @Test + void execute_shouldDownloadFile() throws IOException { + var resource = new Resource(Type.DOWNLOAD, null, null, null, null, new DownloadData("1"), null); + + when(documentMapper.mapToDocument(any(), any(File.class))).thenReturn(mock(Document.class)); + Drive drive = mock(Drive.class, Mockito.RETURNS_DEEP_STUBS); + when(googleDriveClient.getDriveService()).thenReturn(drive); + when(drive.files().get(anyString()).execute()).thenReturn(new File()); + Drive.Files.Get getFile = mock(Drive.Files.Get.class); + doNothing().when(getFile).executeAndDownloadTo(any()); + + var result = service.execute(googleDriveClient, resource); + + assertThat(result).isInstanceOf(Document.class); + } + private static Stream successFolderRequestCases() throws IOException { return BaseTest.loadTestCasesFromResourceFile(SUCCESS_FOLDER_CASES_RESOURCE_PATH); } diff --git a/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/mapper/DocumentMapperTest.java b/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/mapper/DocumentMapperTest.java new file mode 100644 index 0000000000..6f19dc1b5f --- /dev/null +++ b/connectors/google/google-drive/src/test/java/io/camunda/connector/gdrive/mapper/DocumentMapperTest.java @@ -0,0 +1,31 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.gdrive.mapper; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.google.api.services.drive.model.File; +import io.camunda.connector.api.outbound.OutboundConnectorContext; +import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder; +import org.junit.jupiter.api.Test; + +class DocumentMapperTest { + + @Test + void mapToDocument() { + OutboundConnectorContext context = OutboundConnectorContextBuilder.create().build(); + DocumentMapper mapper = new DocumentMapper(context); + + File file = new File(); + file.setMimeType("text/plain"); + file.setName("book.txt"); + var document = mapper.mapToDocument(new byte[1], file); + + assertThat(document.metadata().getFileName()).isEqualTo("book.txt"); + assertThat(document.metadata().getContentType()).isEqualTo("text/plain"); + } +} diff --git a/connectors/google/google-drive/src/test/resources/requests/file-success-download.json b/connectors/google/google-drive/src/test/resources/requests/file-success-download.json new file mode 100644 index 0000000000..b7c68debe8 --- /dev/null +++ b/connectors/google/google-drive/src/test/resources/requests/file-success-download.json @@ -0,0 +1,24 @@ +[ + { + "resource" : { + "type" : "upload", + "uploadData" : { + "document" : { + "camunda.document.type" : "camunda", + "storeId" : "in-memory", + "documentId" : "ffcf4b5e-6427-40aa-9131-65c142a8f380", + "metadata" : { + "contentType" : "image/png", + "fileName" : "google-my-business-logo-png-transparent.png", + "size" : 66497, + "customProperties" : { } + } + } + } + }, + "authentication" : { + "authType" : "bearer", + "bearerToken" : "{{secrets.MyToken}}" + } + } +] \ No newline at end of file diff --git a/connectors/google/google-drive/src/test/resources/requests/file-success-upload.json b/connectors/google/google-drive/src/test/resources/requests/file-success-upload.json new file mode 100644 index 0000000000..213173fa68 --- /dev/null +++ b/connectors/google/google-drive/src/test/resources/requests/file-success-upload.json @@ -0,0 +1,25 @@ +[ + { + "resource": { + "type": "upload", + "parent": "1naPUVnG7at2Z058e1uMYHj3PHo3DrdCZ", + "uploadData": { + "document": { + "camunda.document.type": "camunda", + "storeId": "in-memory", + "documentId": "ffcf4b5e-6427-40aa-9131-65c142a8f380", + "metadata": { + "contentType": "image/png", + "fileName": "google-my-business-logo-png-transparent.png", + "size": 66497, + "customProperties": {} + } + } + } + }, + "authentication": { + "authType": "bearer", + "bearerToken": "{{secrets.MyToken}}" + } + } +] \ No newline at end of file diff --git a/connectors/google/google-drive/src/test/resources/requests/request-fail-test-cases.json b/connectors/google/google-drive/src/test/resources/requests/request-fail-test-cases.json index a53b5e1f63..d65da9890b 100644 --- a/connectors/google/google-drive/src/test/resources/requests/request-fail-test-cases.json +++ b/connectors/google/google-drive/src/test/resources/requests/request-fail-test-cases.json @@ -10,50 +10,6 @@ "parent":"1wF4ME7XcM_6ZIaNCMADMXawzV5" } }, - { - "testDescription": "Folder without a name", - "authentication": { - "authType": "bearer", - "bearerToken": "MyRealToken" - }, - "resource": { - "type":"folder", - "additionalGoogleDriveProperties":{ - "description":"xxx" - }, - "parent":"1wF4ME7XcM_6ZIaNCMADMXawzV5DpVqXw" - } - }, - { - "testDescription": "File without a name", - "authentication": { - "authType": "bearer", - "bearerToken": "myRealToken" - }, - "resource": { - "type": "file", - "parent": "optional my idFolderParent", - "additionalGoogleDriveProperties": { - "description": " description" - }, - "template": { - "id": "myTemplateId", - "variables": { - "requests":[ - { - "replaceAllText":{ - "containsText":{ - "text":"replaceFrom", - "matchCase":"true" - }, - "replaceText":"replaceTo" - } - } - ] - } - } - } - }, { "resource": { "type": "file",