From f771d56ec83484605a9b59fbe3aece0eda0304d6 Mon Sep 17 00:00:00 2001 From: Mathias Vandaele Date: Wed, 4 Dec 2024 17:29:29 +0100 Subject: [PATCH] feature(s3-connector): all features done --- .../connector/aws/s3/core/S3Executor.java | 16 +- .../io/camunda/connector/aws/s3/BaseTest.java | 65 +++++++ .../aws/s3/S3ConnectorFunctionTest.java | 79 +++++++++ .../connector/aws/s3/core/S3ExecutorTest.java | 162 ++++++++++++++++++ .../actions/deleteActionsExample.json | 15 ++ .../actions/downloadActionsExample.json | 18 ++ .../actions/uploadActionsExample.json | 22 +++ 7 files changed, 369 insertions(+), 8 deletions(-) create mode 100644 connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/BaseTest.java create mode 100644 connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/S3ConnectorFunctionTest.java create mode 100644 connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/core/S3ExecutorTest.java create mode 100644 connectors/aws/aws-s3/src/test/resources/actions/deleteActionsExample.json create mode 100644 connectors/aws/aws-s3/src/test/resources/actions/downloadActionsExample.json create mode 100644 connectors/aws/aws-s3/src/test/resources/actions/uploadActionsExample.json diff --git a/connectors/aws/aws-s3/src/main/java/io/camunda/connector/aws/s3/core/S3Executor.java b/connectors/aws/aws-s3/src/main/java/io/camunda/connector/aws/s3/core/S3Executor.java index bfa92cb407..e17e5bacab 100644 --- a/connectors/aws/aws-s3/src/main/java/io/camunda/connector/aws/s3/core/S3Executor.java +++ b/connectors/aws/aws-s3/src/main/java/io/camunda/connector/aws/s3/core/S3Executor.java @@ -36,19 +36,19 @@ public class S3Executor { private final S3Client s3Client; private final Function createDocument; - public S3Executor( - S3Request s3Request, Function createDocument) { - this.s3Client = - S3Client.builder() - .credentialsProvider(CredentialsProviderSupportV2.credentialsProvider(s3Request)) - .region(Region.of(s3Request.getConfiguration().region())) - .build(); + public S3Executor(S3Client s3Client, Function createDocument) { + this.s3Client = s3Client; this.createDocument = createDocument; } public static S3Executor create( S3Request s3Request, Function createDocument) { - return new S3Executor(s3Request, createDocument); + return new S3Executor( + S3Client.builder() + .credentialsProvider(CredentialsProviderSupportV2.credentialsProvider(s3Request)) + .region(Region.of(s3Request.getConfiguration().region())) + .build(), + createDocument); } public Object execute(S3Action s3Action) { diff --git a/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/BaseTest.java b/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/BaseTest.java new file mode 100644 index 0000000000..0941fd87d7 --- /dev/null +++ b/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/BaseTest.java @@ -0,0 +1,65 @@ +/* + * 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.aws.s3; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.Files.readString; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.camunda.connector.api.json.ConnectorsObjectMapperSupplier; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.stream.Stream; +import org.junit.jupiter.params.provider.Arguments; + +public class BaseTest { + + public static Stream loadUploadActionVariables() { + try { + return loadTestCasesFromResourceFile("src/test/resources/actions/uploadActionsExample.json"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Stream loadDownloadActionVariables() { + try { + return loadTestCasesFromResourceFile( + "src/test/resources/actions/downloadActionsExample.json"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Stream loadDeleteActionVariables() { + try { + return loadTestCasesFromResourceFile("src/test/resources/actions/deleteActionsExample.json"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + protected static Stream loadTestCasesFromResourceFile(final String fileWithTestCasesUri) + throws IOException { + final String cases = readString(new File(fileWithTestCasesUri).toPath(), UTF_8); + final ObjectMapper mapper = ConnectorsObjectMapperSupplier.getCopy(); + var array = mapper.readValue(cases, ArrayList.class); + return array.stream() + .map( + value -> { + try { + return mapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }) + .map(Arguments::of); + } +} diff --git a/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/S3ConnectorFunctionTest.java b/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/S3ConnectorFunctionTest.java new file mode 100644 index 0000000000..9bd8a160c9 --- /dev/null +++ b/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/S3ConnectorFunctionTest.java @@ -0,0 +1,79 @@ +/* + * 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.aws.s3; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import io.camunda.connector.aws.s3.core.S3Executor; +import io.camunda.connector.aws.s3.response.DeleteResponse; +import io.camunda.connector.aws.s3.response.DownloadResponse; +import io.camunda.connector.aws.s3.response.UploadResponse; +import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +class S3ConnectorFunctionTest extends BaseTest { + + @ParameterizedTest + @MethodSource("loadUploadActionVariables") + void executeUploadActionReturnsCorrectResult(String variables) { + + var bedrockConnectorFunction = new S3ConnectorFunction(); + var context = OutboundConnectorContextBuilder.create().variables(variables).build(); + + var s3Executor = Mockito.mock(S3Executor.class); + + try (MockedStatic s3ExecutorMockedStatic = Mockito.mockStatic(S3Executor.class)) { + s3ExecutorMockedStatic.when(() -> S3Executor.create(any(), any())).thenReturn(s3Executor); + when(s3Executor.execute(any())).thenReturn(new UploadResponse("test", "test", "link")); + var response = bedrockConnectorFunction.execute(context); + Assertions.assertNotNull(response); + Assertions.assertInstanceOf(UploadResponse.class, response); + } + } + + @ParameterizedTest + @MethodSource("loadDownloadActionVariables") + void executeDownloadActionReturnsCorrectResult(String variables) { + + var bedrockConnectorFunction = new S3ConnectorFunction(); + var context = OutboundConnectorContextBuilder.create().variables(variables).build(); + + var s3Executor = Mockito.mock(S3Executor.class); + + try (MockedStatic s3ExecutorMockedStatic = Mockito.mockStatic(S3Executor.class)) { + s3ExecutorMockedStatic.when(() -> S3Executor.create(any(), any())).thenReturn(s3Executor); + when(s3Executor.execute(any())).thenReturn(new DownloadResponse("test", "test", null, null)); + var response = bedrockConnectorFunction.execute(context); + Assertions.assertNotNull(response); + Assertions.assertInstanceOf(DownloadResponse.class, response); + } + } + + @ParameterizedTest + @MethodSource("loadDeleteActionVariables") + void executeDeleteActionReturnsCorrectResult(String variables) { + + var bedrockConnectorFunction = new S3ConnectorFunction(); + var context = OutboundConnectorContextBuilder.create().variables(variables).build(); + + var s3Executor = Mockito.mock(S3Executor.class); + + try (MockedStatic s3ExecutorMockedStatic = Mockito.mockStatic(S3Executor.class)) { + s3ExecutorMockedStatic.when(() -> S3Executor.create(any(), any())).thenReturn(s3Executor); + when(s3Executor.execute(any())).thenReturn(new DeleteResponse("test", "test")); + var response = bedrockConnectorFunction.execute(context); + Assertions.assertNotNull(response); + Assertions.assertInstanceOf(DeleteResponse.class, response); + } + } +} diff --git a/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/core/S3ExecutorTest.java b/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/core/S3ExecutorTest.java new file mode 100644 index 0000000000..16a09eb64c --- /dev/null +++ b/connectors/aws/aws-s3/src/test/java/io/camunda/connector/aws/s3/core/S3ExecutorTest.java @@ -0,0 +1,162 @@ +/* + * 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.aws.s3.core; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.camunda.connector.aws.s3.model.DeleteS3Action; +import io.camunda.connector.aws.s3.model.DownloadS3Action; +import io.camunda.connector.aws.s3.model.S3Action; +import io.camunda.connector.aws.s3.model.UploadS3Action; +import io.camunda.connector.aws.s3.response.DeleteResponse; +import io.camunda.connector.aws.s3.response.DownloadResponse; +import io.camunda.connector.aws.s3.response.UploadResponse; +import io.camunda.document.Document; +import io.camunda.document.store.DocumentCreationRequest; +import java.io.IOException; +import java.util.Base64; +import java.util.function.Function; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +class S3ExecutorTest { + + @Test + void executeDeleteAction() { + S3Client s3Client = mock(S3Client.class); + Function function = doc -> mock(Document.class); + S3Executor executor = new S3Executor(s3Client, function); + S3Action s3Action = new DeleteS3Action("test", "key"); + + Object object = executor.execute(s3Action); + + verify(s3Client, times(1)).deleteObject(any(DeleteObjectRequest.class)); + assertInstanceOf(DeleteResponse.class, object); + } + + @Test + void executeUploadAction() { + S3Client s3Client = mock(S3Client.class); + Function function = doc -> mock(Document.class); + S3Executor executor = new S3Executor(s3Client, function); + Document document = mock(Document.class, RETURNS_DEEP_STUBS); + S3Action s3Action = new UploadS3Action("test", "key", document); + + when(document.metadata().getSize()).thenReturn(42L); + when(document.metadata().getContentType()).thenReturn("application/octet-stream"); + + Object object = executor.execute(s3Action); + + verify(s3Client, times(1)).putObject(any(PutObjectRequest.class), any(RequestBody.class)); + assertInstanceOf(UploadResponse.class, object); + } + + @Test + void executeDownloadAsDocumentAction() { + + S3Client s3Client = mock(S3Client.class); + Function function = doc -> mock(Document.class); + S3Executor executor = new S3Executor(s3Client, function); + ResponseInputStream responseInputStream = mock(ResponseInputStream.class); + GetObjectResponse getObjectResponse = mock(GetObjectResponse.class); + S3Action s3Action = new DownloadS3Action("test", "key", true); + + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(responseInputStream); + when(responseInputStream.response()).thenReturn(getObjectResponse); + when(getObjectResponse.contentType()).thenReturn("application/octet-stream"); + Object object = executor.execute(s3Action); + + verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); + assertInstanceOf(DownloadResponse.class, object); + assertNull(((DownloadResponse) object).content()); + assertNotNull(((DownloadResponse) object).document()); + } + + @Test + void executeDownloadAsTextContentAction() throws IOException { + + S3Client s3Client = mock(S3Client.class); + Function function = doc -> mock(Document.class); + S3Executor executor = new S3Executor(s3Client, function); + ResponseInputStream responseInputStream = mock(ResponseInputStream.class); + GetObjectResponse getObjectResponse = mock(GetObjectResponse.class); + S3Action s3Action = new DownloadS3Action("test", "key", false); + + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(responseInputStream); + when(responseInputStream.response()).thenReturn(getObjectResponse); + when(responseInputStream.readAllBytes()).thenReturn("Hello World".getBytes()); + when(getObjectResponse.contentLength()).thenReturn(234L); + when(getObjectResponse.contentType()).thenReturn("text/plain"); + Object object = executor.execute(s3Action); + + verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); + assertInstanceOf(DownloadResponse.class, object); + assertNotNull(((DownloadResponse) object).content()); + assertNull(((DownloadResponse) object).document()); + assertEquals("Hello World", ((DownloadResponse) object).content()); + } + + @Test + void executeDownloadAsJsonContentAction() throws IOException { + + S3Client s3Client = mock(S3Client.class); + Function function = doc -> mock(Document.class); + S3Executor executor = new S3Executor(s3Client, function); + ResponseInputStream responseInputStream = mock(ResponseInputStream.class); + GetObjectResponse getObjectResponse = mock(GetObjectResponse.class); + S3Action s3Action = new DownloadS3Action("test", "key", false); + + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(responseInputStream); + when(responseInputStream.response()).thenReturn(getObjectResponse); + when(responseInputStream.readAllBytes()).thenReturn("{ \"Hello\" : \"World\" }".getBytes()); + when(getObjectResponse.contentLength()).thenReturn(234L); + when(getObjectResponse.contentType()).thenReturn("application/json"); + Object object = executor.execute(s3Action); + + verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); + assertInstanceOf(DownloadResponse.class, object); + DownloadResponse downloadResponse = (DownloadResponse) object; + assertNotNull(downloadResponse.content()); + assertNull(downloadResponse.document()); + assertEquals("World", ((ObjectNode) downloadResponse.content()).get("Hello").asText()); + } + + @Test + void executeDownloadAsBase64BytesContentAction() throws IOException { + + S3Client s3Client = mock(S3Client.class); + Function function = doc -> mock(Document.class); + S3Executor executor = new S3Executor(s3Client, function); + ResponseInputStream responseInputStream = mock(ResponseInputStream.class); + GetObjectResponse getObjectResponse = mock(GetObjectResponse.class); + S3Action s3Action = new DownloadS3Action("test", "key", false); + + when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(responseInputStream); + when(responseInputStream.response()).thenReturn(getObjectResponse); + when(responseInputStream.readAllBytes()).thenReturn("Hello".getBytes()); + when(getObjectResponse.contentLength()).thenReturn(234L); + when(getObjectResponse.contentType()).thenReturn("application/octet-stream"); + Object object = executor.execute(s3Action); + + verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); + assertInstanceOf(DownloadResponse.class, object); + DownloadResponse downloadResponse = (DownloadResponse) object; + assertNotNull(downloadResponse.content()); + assertNull(downloadResponse.document()); + assertEquals( + Base64.getEncoder().encodeToString("Hello".getBytes()), downloadResponse.content()); + } +} diff --git a/connectors/aws/aws-s3/src/test/resources/actions/deleteActionsExample.json b/connectors/aws/aws-s3/src/test/resources/actions/deleteActionsExample.json new file mode 100644 index 0000000000..3f0e048cb0 --- /dev/null +++ b/connectors/aws/aws-s3/src/test/resources/actions/deleteActionsExample.json @@ -0,0 +1,15 @@ +[ + { + "action":{ + "bucket":"connector-aws-s3-test", + "key":"test" + }, "configuration":{ + "region":"eu-central-1" + }, + "authentication":{ + "type":"credentials", + "accessKey":"test", + "secretKey":"test" + }, "actionDiscriminator":"deleteObject" + } +] \ No newline at end of file diff --git a/connectors/aws/aws-s3/src/test/resources/actions/downloadActionsExample.json b/connectors/aws/aws-s3/src/test/resources/actions/downloadActionsExample.json new file mode 100644 index 0000000000..e90a965984 --- /dev/null +++ b/connectors/aws/aws-s3/src/test/resources/actions/downloadActionsExample.json @@ -0,0 +1,18 @@ +[ + { + "action":{ + "bucket":"connector-aws-s3-test", + "key":"attachment", + "asFile":false + }, + "configuration":{ + "region":"eu-central-1" + }, + "authentication":{ + "type":"credentials", + "accessKey":"test", + "secretKey":"test" + }, + "actionDiscriminator":"downloadObject" + } +] \ No newline at end of file diff --git a/connectors/aws/aws-s3/src/test/resources/actions/uploadActionsExample.json b/connectors/aws/aws-s3/src/test/resources/actions/uploadActionsExample.json new file mode 100644 index 0000000000..ea23c5dd78 --- /dev/null +++ b/connectors/aws/aws-s3/src/test/resources/actions/uploadActionsExample.json @@ -0,0 +1,22 @@ +[ + { + "action":{ + "bucket":"connector-aws-s3-test", + "key":"attachment", + "document":{ + "storeId":"in-memory", + "documentId":"41d2a87f-f39c-4ddd-a116-18d2091cc695", + "metadata":{ + "contentType":"text/plain", "size":41730, + "fileName":"test.txt" + }, + "documentType":"camunda" + } + }, "configuration":{"region":"eu-central-1"}, + "authentication":{ + "type":"credentials", + "accessKey":"test", + "secretKey":"test" + }, "actionDiscriminator":"uploadObject" + } +] \ No newline at end of file