diff --git a/connectors-e2e-test/connectors-e2e-test-mail/src/test/java/io/camunda/connector/e2e/BaseEmailTest.java b/connectors-e2e-test/connectors-e2e-test-mail/src/test/java/io/camunda/connector/e2e/BaseEmailTest.java index f7a4f849ed..dee50cdcb0 100644 --- a/connectors-e2e-test/connectors-e2e-test-mail/src/test/java/io/camunda/connector/e2e/BaseEmailTest.java +++ b/connectors-e2e-test/connectors-e2e-test-mail/src/test/java/io/camunda/connector/e2e/BaseEmailTest.java @@ -25,7 +25,6 @@ import jakarta.mail.MessagingException; import jakarta.mail.Multipart; import jakarta.mail.internet.MimeBodyPart; -import jakarta.mail.Multipart; import jakarta.mail.internet.MimeMessage; import java.io.File; import java.io.IOException; diff --git a/connectors/email/element-templates/email-outbound-connector.json b/connectors/email/element-templates/email-outbound-connector.json index bc823a870b..600c10fbae 100644 --- a/connectors/email/element-templates/email-outbound-connector.json +++ b/connectors/email/element-templates/email-outbound-connector.json @@ -678,14 +678,14 @@ "tooltip" : "Email's Html content", "type" : "Text" }, { - "id" : "attachmentSmtp", + "id" : "attachmentsSmtp", "label" : "Attachment", - "description" : "Email's attachment", + "description" : "Email's attachment. e.g., =[ document1, document2]", "optional" : true, "feel" : "required", "group" : "sendEmailSmtp", "binding" : { - "name" : "data.smtpAction.attachment", + "name" : "data.smtpAction.attachments", "type" : "zeebe:input" }, "condition" : { @@ -699,6 +699,7 @@ "type" : "simple" } ] }, + "tooltip" : "Email's attachments, should be set as a list ", "type" : "String" }, { "id" : "pop3maxToBeRead", diff --git a/connectors/email/element-templates/hybrid/email-outbound-connector-hybrid.json b/connectors/email/element-templates/hybrid/email-outbound-connector-hybrid.json index 8a51603ebe..4aae017174 100644 --- a/connectors/email/element-templates/hybrid/email-outbound-connector-hybrid.json +++ b/connectors/email/element-templates/hybrid/email-outbound-connector-hybrid.json @@ -683,14 +683,14 @@ "tooltip" : "Email's Html content", "type" : "Text" }, { - "id" : "attachmentSmtp", + "id" : "attachmentsSmtp", "label" : "Attachment", - "description" : "Email's attachment", + "description" : "Email's attachment. e.g., =[ document1, document2]", "optional" : true, "feel" : "required", "group" : "sendEmailSmtp", "binding" : { - "name" : "data.smtpAction.attachment", + "name" : "data.smtpAction.attachments", "type" : "zeebe:input" }, "condition" : { @@ -704,6 +704,7 @@ "type" : "simple" } ] }, + "tooltip" : "Email's attachments, should be set as a list ", "type" : "String" }, { "id" : "pop3maxToBeRead", diff --git a/connectors/email/src/main/java/io/camunda/connector/email/client/jakarta/outbound/JakartaEmailActionExecutor.java b/connectors/email/src/main/java/io/camunda/connector/email/client/jakarta/outbound/JakartaEmailActionExecutor.java index a68fabbc8d..4659927c7a 100644 --- a/connectors/email/src/main/java/io/camunda/connector/email/client/jakarta/outbound/JakartaEmailActionExecutor.java +++ b/connectors/email/src/main/java/io/camunda/connector/email/client/jakarta/outbound/JakartaEmailActionExecutor.java @@ -24,9 +24,9 @@ import jakarta.mail.*; import jakarta.mail.internet.*; import jakarta.mail.search.*; -import java.nio.charset.StandardCharsets; import jakarta.mail.util.ByteArrayDataSource; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.Consumer; @@ -271,20 +271,7 @@ private SendEmailResponse smtpSendEmail( if (bcc.isPresent()) message.setRecipients(Message.RecipientType.BCC, bcc.get()); headers.ifPresent(stringObjectMap -> setMessageHeaders(stringObjectMap, message)); message.setSubject(smtpSendEmail.subject()); - Multipart multipart = getMultipart(smtpSendEmail); - if (!Objects.isNull(smtpSendEmail.attachment())) { - BodyPart attachment = new MimeBodyPart(); - DataSource dataSource = - new ByteArrayDataSource( - smtpSendEmail.attachment().asInputStream(), - smtpSendEmail.attachment().metadata().getContentType()); - attachment.setDataHandler(new DataHandler(dataSource)); - attachment.setFileName(smtpSendEmail.attachment().metadata().getFileName()); - multipart.addBodyPart(attachment); - Multipart multipart = new MimeMultipart(); - MimeBodyPart textContent = new MimeBodyPart(); - textContent.setText(smtpSendEmail.body()); - multipart.addBodyPart(textContent); + Multipart multipart = getMultipart(smtpSendEmail); if (!Objects.isNull(smtpSendEmail.attachments())) { smtpSendEmail.attachments().forEach(getDocumentConsumer(multipart)); } diff --git a/connectors/email/src/main/java/io/camunda/connector/email/outbound/protocols/actions/SmtpSendEmail.java b/connectors/email/src/main/java/io/camunda/connector/email/outbound/protocols/actions/SmtpSendEmail.java index b15c24e553..9b4e99cb61 100644 --- a/connectors/email/src/main/java/io/camunda/connector/email/outbound/protocols/actions/SmtpSendEmail.java +++ b/connectors/email/src/main/java/io/camunda/connector/email/outbound/protocols/actions/SmtpSendEmail.java @@ -126,7 +126,7 @@ public record SmtpSendEmail( property = "contentType", oneOf = {"HTML", "MULTIPART"})) @Valid - String htmlBody + String htmlBody, @TemplateProperty( label = "Attachment", group = "sendEmailSmtp", diff --git a/connectors/email/src/test/java/io/camunda/connector/email/client/jakarta/JakartaExecutorTest.java b/connectors/email/src/test/java/io/camunda/connector/email/client/jakarta/JakartaExecutorTest.java index 27857fbbff..bf072b36b3 100644 --- a/connectors/email/src/test/java/io/camunda/connector/email/client/jakarta/JakartaExecutorTest.java +++ b/connectors/email/src/test/java/io/camunda/connector/email/client/jakarta/JakartaExecutorTest.java @@ -6,6 +6,7 @@ */ package io.camunda.connector.email.client.jakarta; +import static org.apache.hc.core5.http.ContentType.IMAGE_PNG; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -35,37 +36,23 @@ import java.nio.file.Path; import java.time.OffsetDateTime; import java.util.*; -import org.apache.hc.core5.http.ContentType; import org.eclipse.angus.mail.pop3.POP3Folder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class JakartaExecutorTest { - protected static boolean messageContains(Message message, String... value) { - List values = Arrays.asList(value); - try { - boolean contains = false; - if (message.getContent() instanceof Multipart multipart) { - for (int i = 0; i < multipart.getCount(); i++) { - contains = contains || values.contains(multipart.getBodyPart(i).getContent().toString()); - } - } - return contains; - } catch (MessagingException | IOException e) { - throw new RuntimeException(e); - } - } + DocumentFactory documentFactory = new DocumentFactoryImpl(InMemoryDocumentStore.INSTANCE); private static boolean messageHasContentType(Message message, String... messageContentType) { List values = Arrays.asList(messageContentType); try { - boolean contains = true; + boolean contains = false; if (message.getContent() instanceof Multipart multipart) { for (int i = 0; i < multipart.getCount(); i++) { contains = contains - && values.contains(multipart.getBodyPart(i).getDataHandler().getContentType()); + || values.contains(multipart.getBodyPart(i).getDataHandler().getContentType()); } } return contains; @@ -74,32 +61,143 @@ private static boolean messageHasContentType(Message message, String... messageC } } - DocumentFactory documentFactory = new DocumentFactoryImpl(InMemoryDocumentStore.INSTANCE); + private boolean bodyContains(Object element, String toBeFound) + throws MessagingException, IOException { + return switch (element) { + case String str -> str.contains(toBeFound); + case MimeMultipart multipart -> { + try { + int max = multipart.getCount(); + boolean found = false; + for (int i = 0; i < max; i++) { + found = + found + || bodyContains(multipart.getBodyPart(i).getContent(), toBeFound) + || toBeFound.equals(multipart.getBodyPart(i).getFileName()); + } + yield found; + } catch (MessagingException | IOException e) { + throw new RuntimeException(e); + } + } + default -> false; + }; + } @Test void executeSmtpSendEmail() throws MessagingException { - buildSmtpTest(ContentType.PLAIN, "body", null, "text/plain; charset=UTF-8"); + JakartaUtils sessionFactory = mock(JakartaUtils.class); + ObjectMapper objectMapper = mock(ObjectMapper.class); + JakartaEmailActionExecutor actionExecutor = + JakartaEmailActionExecutor.create(sessionFactory, objectMapper); + + OutboundConnectorContext outboundConnectorContext = mock(OutboundConnectorContext.class); + EmailRequest emailRequest = mock(EmailRequest.class); + SmtpSendEmail smtpSendEmail = mock(SmtpSendEmail.class); + SimpleAuthentication simpleAuthentication = mock(SimpleAuthentication.class); + Protocol protocol = mock(Smtp.class); + Session session = mock(Session.class); + Transport transport = mock(Transport.class); + + // Authentication + when(simpleAuthentication.username()).thenReturn("user"); + when(simpleAuthentication.password()).thenReturn("secret"); + doNothing().when(transport).connect(any(), any()); + + when(outboundConnectorContext.bindVariables(any())).thenReturn(emailRequest); + when(emailRequest.authentication()).thenReturn(simpleAuthentication); + when(session.getProperties()).thenReturn(new Properties()); + when(emailRequest.data()).thenReturn(protocol); + when(protocol.getProtocolAction()).thenReturn(smtpSendEmail); + when(sessionFactory.createSession(any())).thenReturn(session); + when(smtpSendEmail.to()).thenReturn(List.of("to")); + when(smtpSendEmail.cc()).thenReturn(List.of("cc")); + when(smtpSendEmail.bcc()).thenReturn(List.of("bcc")); + when(smtpSendEmail.from()).thenReturn("myself"); + when(smtpSendEmail.contentType()).thenReturn(ContentType.PLAIN); + when(smtpSendEmail.body()).thenReturn("body"); + when(session.getTransport()).thenReturn(transport); + + actionExecutor.execute(outboundConnectorContext); + + verify(transport, times(1)) + .sendMessage( + argThat( + argument -> { + try { + return Arrays.stream(argument.getFrom()) + .allMatch(address -> address.toString().contains("myself")) + && bodyContains(argument.getContent(), "body") + && messageHasContentType(argument, "text/plain; charset=UTF-8"); + } catch (MessagingException | IOException e) { + throw new RuntimeException(e); + } + }), + argThat( + argument -> + Arrays.toString(argument).contains("to") + && Arrays.toString(argument).contains("cc") + && Arrays.toString(argument).contains("bcc"))); } @Test void executeSmtpSendEmailAsHtml() throws MessagingException { - buildSmtpTest( - ContentType.HTML, null, "body", "text/html; charset=utf-8"); + JakartaUtils sessionFactory = mock(JakartaUtils.class); + ObjectMapper objectMapper = mock(ObjectMapper.class); + JakartaEmailActionExecutor actionExecutor = + JakartaEmailActionExecutor.create(sessionFactory, objectMapper); + + OutboundConnectorContext outboundConnectorContext = mock(OutboundConnectorContext.class); + EmailRequest emailRequest = mock(EmailRequest.class); + SmtpSendEmail smtpSendEmail = mock(SmtpSendEmail.class); + SimpleAuthentication simpleAuthentication = mock(SimpleAuthentication.class); + Protocol protocol = mock(Smtp.class); + Session session = mock(Session.class); + Transport transport = mock(Transport.class); + + // Authentication + when(simpleAuthentication.username()).thenReturn("user"); + when(simpleAuthentication.password()).thenReturn("secret"); + doNothing().when(transport).connect(any(), any()); + + when(outboundConnectorContext.bindVariables(any())).thenReturn(emailRequest); + when(emailRequest.authentication()).thenReturn(simpleAuthentication); + when(session.getProperties()).thenReturn(new Properties()); + when(emailRequest.data()).thenReturn(protocol); + when(protocol.getProtocolAction()).thenReturn(smtpSendEmail); + when(sessionFactory.createSession(any())).thenReturn(session); + when(smtpSendEmail.to()).thenReturn(List.of("to")); + when(smtpSendEmail.cc()).thenReturn(List.of("cc")); + when(smtpSendEmail.bcc()).thenReturn(List.of("bcc")); + when(smtpSendEmail.from()).thenReturn("myself"); + when(smtpSendEmail.contentType()).thenReturn(ContentType.HTML); + when(smtpSendEmail.htmlBody()).thenReturn("body"); + when(session.getTransport()).thenReturn(transport); + + actionExecutor.execute(outboundConnectorContext); + + verify(transport, times(1)) + .sendMessage( + argThat( + argument -> { + try { + return Arrays.stream(argument.getFrom()) + .allMatch(address -> address.toString().contains("myself")) + && bodyContains(argument.getContent(), "body") + && messageHasContentType(argument, "text/html; charset=utf-8"); + } catch (MessagingException | IOException e) { + throw new RuntimeException(e); + } + }), + argThat( + argument -> + Arrays.toString(argument).contains("to") + && Arrays.toString(argument).contains("cc") + && Arrays.toString(argument).contains("bcc"))); } @Test void executeSmtpSendEmailAsMultiPart() throws MessagingException { - buildSmtpTest( - ContentType.MULTIPART, - "Hello", - "body", - "text/plain; charset=UTF-8", - "text/html; charset=utf-8"); - } - - void buildSmtpTest( - ContentType contentType, String body, String bodyAsHtml, String... messageContentType) - throws MessagingException { JakartaUtils sessionFactory = mock(JakartaUtils.class); ObjectMapper objectMapper = mock(ObjectMapper.class); JakartaEmailActionExecutor actionExecutor = @@ -128,7 +226,9 @@ void buildSmtpTest( when(smtpSendEmail.cc()).thenReturn(List.of("cc")); when(smtpSendEmail.bcc()).thenReturn(List.of("bcc")); when(smtpSendEmail.from()).thenReturn("myself"); - when(smtpSendEmail.body()).thenReturn("body"); + when(smtpSendEmail.contentType()).thenReturn(ContentType.MULTIPART); + when(smtpSendEmail.body()).thenReturn("Hello"); + when(smtpSendEmail.htmlBody()).thenReturn("body"); when(session.getTransport()).thenReturn(transport); actionExecutor.execute(outboundConnectorContext); @@ -140,7 +240,9 @@ void buildSmtpTest( try { return Arrays.stream(argument.getFrom()) .allMatch(address -> address.toString().contains("myself")) - && bodyContains(argument.getContent(), "body"); + && bodyContains(argument.getContent(), "body") + && messageHasContentType( + argument, "text/plain; charset=UTF-8", "text/html; charset=utf-8"); } catch (MessagingException | IOException e) { throw new RuntimeException(e); } @@ -184,24 +286,16 @@ void executeSmtpSendEmailWithAttachment() throws MessagingException, IOException when(smtpSendEmail.bcc()).thenReturn(List.of("bcc")); when(smtpSendEmail.from()).thenReturn("myself"); when(smtpSendEmail.body()).thenReturn("body"); - when(smtpSendEmail.attachment()) - .thenReturn( - this.documentFactory.create( - DocumentCreationRequest.from(new FileInputStream("src/test/resources/img/img.png")) - .contentType(ContentType.IMAGE_PNG.getMimeType()) - .fileName("test") - .build())); - when(smtpSendEmail.contentType()).thenReturn(contentType); - when(smtpSendEmail.body()).thenReturn(body); - when(smtpSendEmail.htmlBody()).thenReturn(bodyAsHtml); + when(smtpSendEmail.contentType()).thenReturn(ContentType.PLAIN); + try (FileInputStream fileInputStream = new FileInputStream("src/test/resources/img/img.png")) { when(smtpSendEmail.attachments()) .thenReturn( List.of( this.documentFactory.create( DocumentCreationRequest.from(fileInputStream) - .contentType(ContentType.IMAGE_PNG.getMimeType()) - .fileName("test") + .contentType(IMAGE_PNG.getMimeType()) + .fileName("testFile") .build()))); } when(session.getTransport()).thenReturn(transport); @@ -215,10 +309,9 @@ void executeSmtpSendEmailWithAttachment() throws MessagingException, IOException try { return Arrays.stream(argument.getFrom()) .allMatch(address -> address.toString().contains("myself")) - && messageContains(argument, body, bodyAsHtml) - && messageHasContentType(argument, messageContentType) - && bodyContains(argument.getContent(), "body") - && bodyContains(argument.getContent(), "test"); + && bodyContains(argument.getContent(), "testFile") + && messageHasContentType(argument, "text/plain; charset=UTF-8") + && bodyContains(argument.getContent(), "body"); } catch (MessagingException | IOException e) { throw new RuntimeException(e); } @@ -230,29 +323,6 @@ && bodyContains(argument.getContent(), "body") && Arrays.toString(argument).contains("bcc"))); } - private boolean bodyContains(Object element, String toBeFound) - throws MessagingException, IOException { - return switch (element) { - case String str -> str.contains(toBeFound); - case MimeMultipart multipart -> { - try { - int max = multipart.getCount(); - boolean found = false; - for (int i = 0; i < max; i++) { - found = - found - || bodyContains(multipart.getBodyPart(i).getContent(), toBeFound) - || toBeFound.equals(multipart.getBodyPart(i).getFileName()); - } - yield found; - } catch (MessagingException | IOException e) { - throw new RuntimeException(e); - } - } - default -> false; - }; - } - @Test void executeSmtpSendEmailWithHeaders() throws MessagingException { @@ -297,9 +367,8 @@ void executeSmtpSendEmailWithHeaders() throws MessagingException { return Arrays.stream(argument.getFrom()) .allMatch(address -> address.toString().contains("myself")) && bodyContains(argument.getContent(), "body") - && messageContains(argument, "body") && Arrays.stream(argument.getHeader("test")).toList().contains("header1"); - } catch (MessagingException e) { + } catch (MessagingException | IOException e) { throw new RuntimeException(e); } }),