This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch 2.7.x in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 858b9e73fab7fb0649da8baf8cd2bd21b649bb19 Author: JiriOndrusek <[email protected]> AuthorDate: Tue Apr 5 09:48:49 2022 +0200 Improve mail test coverage #3674 --- integration-tests/mail/pom.xml | 58 ++++- .../quarkus/component/mail/CamelResource.java | 188 +++++++++++++- .../camel/quarkus/component/mail/CamelRoute.java | 205 ++++++++++++++- .../camel/quarkus/component/mail/MockMailbox.java | 55 ---- .../mail/src/main/resources/application.properties | 17 ++ .../mail/src/main/resources/data/logo.jpeg | Bin 0 -> 10249 bytes .../camel/quarkus/component/mail/MailTest.java | 283 +++++++++++++++++++-- .../quarkus/component/mail/MailTestResource.java | 88 +++++++ pom.xml | 2 + poms/bom-test/pom.xml | 6 + poms/bom/src/main/generated/flattened-full-pom.xml | 22 +- .../generated/flattened-reduced-verbose-pom.xml | 22 +- 12 files changed, 824 insertions(+), 122 deletions(-) diff --git a/integration-tests/mail/pom.xml b/integration-tests/mail/pom.xml index 2d0fb26678..d3f43fc6da 100644 --- a/integration-tests/mail/pom.xml +++ b/integration-tests/mail/pom.xml @@ -39,20 +39,18 @@ <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-direct</artifactId> </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-seda</artifactId> + </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy</artifactId> </dependency> <dependency> - <groupId>org.jvnet.mock-javamail</groupId> - <artifactId>mock-javamail</artifactId> - <exclusions> - <exclusion> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - </exclusion> - </exclusions> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-resteasy-jsonb</artifactId> </dependency> <!-- test dependencies --> @@ -72,7 +70,38 @@ </exclusion> </exclusions> </dependency> - + <dependency> + <groupId>org.awaitility</groupId> + <artifactId>awaitility</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <scope>test</scope> + <exclusions> + <exclusion> + <artifactId>junit</artifactId> + <groupId>junit</groupId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>com.icegreen</groupId> + <artifactId>greenmail</artifactId> + <scope>test</scope> + <exclusions> + <exclusion> + <artifactId>junit</artifactId> + <groupId>junit</groupId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-junit4-mock</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> @@ -149,6 +178,17 @@ </dependency> </dependencies> </profile> + <profile> + <id>skip-testcontainers-tests</id> + <activation> + <property> + <name>skip-testcontainers-tests</name> + </property> + </activation> + <properties> + <skipTests>true</skipTests> + </properties> + </profile> </profiles> diff --git a/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelResource.java b/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelResource.java index 0bc7b44a17..a1e9f671f7 100644 --- a/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelResource.java +++ b/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelResource.java @@ -16,44 +16,129 @@ */ package org.apache.camel.quarkus.component.mail; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.activation.FileDataSource; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import javax.inject.Named; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; import javax.mail.util.ByteArrayDataSource; import javax.ws.rs.Consumes; +import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import com.sun.mail.imap.SortTerm; +import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.ProducerTemplate; +import org.apache.camel.ServiceStatus; import org.apache.camel.attachment.AttachmentMessage; import org.apache.camel.attachment.DefaultAttachment; +import org.apache.camel.component.mail.DefaultJavaMailSender; +import org.apache.camel.component.mail.JavaMailSender; +import org.apache.camel.component.mail.MailSorter; @Path("/mail") @ApplicationScoped public class CamelResource { @Inject - ProducerTemplate template; + ProducerTemplate producerTemplate; + + @Inject + CamelContext camelContext; + + @Inject + @Named("mailReceivedMessages") + List<Map<String, Object>> mailReceivedMessages; + + @Path("/send") + @POST + @Consumes(MediaType.TEXT_PLAIN) + public void sendMail( + @QueryParam("subject") String subject, + @QueryParam("from") String from, + @QueryParam("to") String to, + @QueryParam("secured") Boolean secured, + String body) { + + String path = (secured != null && secured) ? "sendMailSecured" : "sendMail"; + producerTemplate.send("direct:" + path, exchange -> { + org.apache.camel.Message message = exchange.getMessage(); + message.setHeader("Subject", subject); + message.setHeader("From", from); + message.setHeader("To", to); + message.setBody(body); + }); + } + + @Path("/send/attachment/{fileName}") + @POST + @Consumes(MediaType.TEXT_PLAIN) + public void sendMailWithAttachment( + @PathParam("fileName") String fileName, + @QueryParam("subject") String subject, + @QueryParam("from") String from, + @QueryParam("to") String to, + String body) { + + producerTemplate.send("direct:sendMail", exchange -> { + AttachmentMessage in = exchange.getMessage(AttachmentMessage.class); + + DefaultAttachment attachment; + if (fileName.startsWith("/")) { + attachment = new DefaultAttachment(new FileDataSource(fileName)); + } else { + attachment = new DefaultAttachment( + new ByteArrayDataSource( + Thread.currentThread().getContextClassLoader().getResourceAsStream("data/" + fileName), + "image/jpeg")); + } + + in.addAttachmentObject(fileName, attachment); + + org.apache.camel.Message message = exchange.getMessage(); + message.setHeader("Subject", subject); + message.setHeader("From", from); + message.setHeader("To", to); + message.setBody(body); + }); + } - @Path("/route/{route}") + @Path("/mimeMultipartUnmarshalMarshal") @POST @Consumes(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN) - public String route(String statement, @PathParam("route") String route) throws Exception { - return template.requestBody("direct:" + route, statement, String.class); + public String mimeMultipartUnmarshalMarshal(String body) { + return producerTemplate.requestBody("direct:mimeMultipartUnmarshalMarshal", body, String.class); } @Path("/mimeMultipartMarshal/{fileName}/{fileContent}") @POST @Consumes(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN) - public String mimeMultipart(String body, @PathParam("fileName") String fileName, - @PathParam("fileContent") String fileContent) throws Exception { + public String mimeMultipart( + @PathParam("fileName") String fileName, + @PathParam("fileContent") String fileContent, + String body) { - return template.request("direct:mimeMultipartMarshal", e -> { + return producerTemplate.request("direct:mimeMultipartMarshal", e -> { AttachmentMessage in = e.getMessage(AttachmentMessage.class); in.setBody(body); in.setHeader(Exchange.CONTENT_TYPE, "text/plain;charset=iso8859-1;other-parameter=true"); @@ -67,4 +152,93 @@ public class CamelResource { }).getMessage().getBody(String.class); } + // ------------------------------------------------ + + @Path("/getReceived") + @GET + @Produces(MediaType.APPLICATION_JSON) + public List<Map<String, Object>> getReceived() { + return mailReceivedMessages; + } + + @Path("/getReceivedAsString") + @GET + @Produces(MediaType.APPLICATION_JSON) + public List<Map<String, Object>> getReceivedAsString() throws MessagingException, IOException { + List<Map<String, Object>> result = new LinkedList(); + for (Map<String, Object> email : mailReceivedMessages) { + InputStream is = (InputStream) email.get("convertedStream"); + result.add(Collections.singletonMap("body", camelContext.getTypeConverter().convertTo(String.class, is))); + } + mailReceivedMessages.clear(); + return result; + + } + + @Path("/clear") + @GET + public void clear() { + mailReceivedMessages.clear(); + } + + @GET + @Path("/route/{routeId}/{operation}") + @Produces(MediaType.TEXT_PLAIN) + public String controlRoute(@PathParam("routeId") String routeId, @PathParam("operation") String operation) + throws Exception { + switch (operation) { + case "stop": + camelContext.getRouteController().stopRoute(routeId); + break; + case "start": + camelContext.getRouteController().startRoute(routeId); + break; + case "status": + return camelContext.getRouteController().getRouteStatus(routeId).name(); + } + return null; + } + + @GET + @Path("/stopConsumers") + @Produces(MediaType.TEXT_PLAIN) + public void stopConsumers() + throws Exception { + Arrays.stream(CamelRoute.Routes.values()).forEach(r -> { + try { + if (camelContext.getRouteController().getRouteStatus(r.name()) == ServiceStatus.Started) { + camelContext.getRouteController().stopRoute(r.name()); + } + } catch (Exception e) { + //ignore + } + }); + } + + @Path("/sort") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public List<String> sort(List<String> messages) throws Exception { + JavaMailSender sender = new DefaultJavaMailSender(); + // inserts new messages + Message[] msgs = new Message[messages.size()]; + int i = 0; + for (String msg : messages) { + msgs[i] = new MimeMessage(sender.getSession()); + msgs[i].setHeader("Subject", msg); + msgs[i++].setText(msg); + } + MailSorter.sortMessages(msgs, new SortTerm[] { + SortTerm.SUBJECT }); + + return Stream.of(msgs).map(m -> { + try { + return String.valueOf(m.getContent()); + } catch (Exception e) { + return "error"; + } + }).collect(Collectors.toList()); + } + } diff --git a/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelRoute.java b/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelRoute.java index 6efc00ee7b..b48c9e491d 100644 --- a/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelRoute.java +++ b/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/CamelRoute.java @@ -16,38 +16,215 @@ */ package org.apache.camel.quarkus.component.mail; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.activation.DataHandler; +import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; -import javax.mail.Session; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.mail.Folder; +import javax.mail.MessagingException; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePropertyKey; +import org.apache.camel.attachment.AttachmentMessage; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.mail.MailComponent; +import org.apache.camel.component.mail.MailConverters; +import org.apache.camel.component.mail.MailMessage; +import org.apache.camel.support.jsse.KeyManagersParameters; +import org.apache.camel.support.jsse.SSLContextParameters; +import org.apache.camel.support.jsse.TrustManagersParameters; +import org.eclipse.microprofile.config.inject.ConfigProperty; +@ApplicationScoped public class CamelRoute extends RouteBuilder { + + enum Routes { + convertersRoute, batchReceiveRoute, imapReceiveRoute, imapsReceiveRoute, pop3ReceiveRoute, pop3sReceiveRoute; + } + + @Inject + @Named("mailReceivedMessages") + List<Map<String, Object>> mailReceivedMessages; + + @Inject + CamelContext camelContext; + + static final String EMAIL_ADDRESS = "test@localhost"; + static final String USERNAME = "test"; + static final String PASSWORD = "s3cr3t"; + + @ConfigProperty(name = "mail.smtp.port") + int smtpPort; + + @ConfigProperty(name = "mail.smtps.port") + int smtpsPort; + + @ConfigProperty(name = "mail.pop3.port") + int pop3Port; + + @ConfigProperty(name = "mail.pop3s.port") + int pop3sPort; + + @ConfigProperty(name = "mail.imap.port") + int imapPort; + + @ConfigProperty(name = "mail.imaps.port") + int imapsPort; + @Override public void configure() { - bindToRegistry("smtp", smtp()); - from("direct:mailtext") - .setHeader("Subject", constant("Hello World")) - .setHeader("To", constant("james@localhost")) - .setHeader("From", constant("claus@localhost")) - .to("smtp://localhost?initialDelay=100&delay=100"); + from("direct:sendMail") + .toF("smtp://localhost:%d?username=%s&password=%s", smtpPort, USERNAME, PASSWORD); + + from("direct:sendMailSecured").toF( + "smtps://localhost:%d?username=%s&password=%s&sslContextParameters=#sslContextParameters&additionalJavaMailProperties=#additionalProperties", + smtpsPort, USERNAME, PASSWORD); from("direct:mimeMultipartMarshal") .marshal().mimeMultipart(); + from("direct:mimeMultipartUnmarshalMarshal") .unmarshal().mimeMultipart() .marshal().mimeMultipart(); + fromF("pop3://localhost:%d?initialDelay=100&delay=500&username=%s&password=%s&delete=true", pop3Port, USERNAME, + PASSWORD) + .id(Routes.pop3ReceiveRoute.name()) + .autoStartup(false) + .process(exchange -> handleMail(exchange)); + + fromF("pop3s://localhost:%d?initialDelay=100&delay=500&username=%s&password=%s&delete=true&sslContextParameters=#sslContextParameters&additionalJavaMailProperties=#additionalProperties", + pop3sPort, USERNAME, + PASSWORD) + .id(Routes.pop3sReceiveRoute.name()) + .autoStartup(false) + .process(exchange -> handleMail(exchange)); + + fromF("imap://localhost:%d?initialDelay=100&delay=500&username=%s&password=%s&delete=true", imapPort, USERNAME, + PASSWORD) + .id(Routes.imapReceiveRoute.name()) + .autoStartup(false) + .process(exchange -> handleMail(exchange)); + + fromF("imaps://localhost:%d?initialDelay=100&delay=500&username=%s&password=%s&delete=true&sslContextParameters=#sslContextParameters&additionalJavaMailProperties=#additionalProperties", + imapsPort, USERNAME, + PASSWORD) + .id(Routes.imapsReceiveRoute.name()) + .autoStartup(false) + .process(exchange -> handleMail(exchange)); + + fromF("pop3://localhost:%d?initialDelay=100&delay=500&username=%s&password=%s" + + "&delete=true&maxMessagesPerPoll=3", pop3Port, USERNAME, PASSWORD) + .id(Routes.batchReceiveRoute.name()) + .autoStartup(false) + .process(e -> { + Map<String, Object> map = handleMail(e); + map.put(ExchangePropertyKey.BATCH_INDEX.getName(), e.getProperty(ExchangePropertyKey.BATCH_INDEX)); + map.put(ExchangePropertyKey.BATCH_COMPLETE.getName(), + e.getProperty(ExchangePropertyKey.BATCH_COMPLETE)); + map.put(ExchangePropertyKey.BATCH_SIZE.getName(), e.getProperty(ExchangePropertyKey.BATCH_SIZE)); + }); + + fromF("pop3://localhost:%d?initialDelay=100&delay=500&username=%s&password=%s&delete=true", pop3Port, USERNAME, + PASSWORD) + .id(Routes.convertersRoute.name()) + .autoStartup(false) + .process(e -> { + MailConverters.toInputStream(e.getIn().getBody(MailMessage.class).getMessage()); + InputStream is = MailConverters.toInputStream(e.getIn().getBody(MailMessage.class).getMessage()); + Map<String, Object> map = handleMail(e); + map.put("convertedStream", is); + }); + + } + + private Map<String, Object> handleMail(Exchange exchange) throws MessagingException { + Map<String, Object> result = new HashMap<>(); + MailMessage mailMessage = exchange.getMessage(MailMessage.class); + AttachmentMessage attachmentMessage = exchange.getMessage(AttachmentMessage.class); + Map<String, DataHandler> attachments = attachmentMessage.getAttachments(); + if (attachments != null) { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + attachments.forEach((id, dataHandler) -> { + JsonObjectBuilder attachmentObject = Json.createObjectBuilder(); + attachmentObject.add("attachmentFilename", dataHandler.getName()); + attachmentObject.add("attachmentContentType", dataHandler.getContentType()); + + if (dataHandler.getName().endsWith(".txt")) { + try { + String content = camelContext.getTypeConverter().convertTo(String.class, + dataHandler.getInputStream()); + attachmentObject.add("attachmentContent", content); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + arrayBuilder.add(attachmentObject.build()); + }); + + result.put("attachments", arrayBuilder.build()); + } + + Folder folder = mailMessage.getOriginalMessage().getFolder(); + if (!folder.isOpen()) { + folder.open(Folder.READ_ONLY); + } + + result.put("subject", mailMessage.getMessage().getSubject()); + result.put("content", mailMessage.getBody(String.class).trim()); + + mailReceivedMessages.add(result); + + return result; } - @Produces - MailComponent smtp() { - MailComponent mail = new MailComponent(getContext()); - Session session = Session.getInstance(new Properties()); - mail.getConfiguration().setSession(session); - return mail; + static class Producers { + + @Singleton + @Produces + @Named("mailReceivedMessages") + List<Map<String, Object>> mailReceivedMessages() { + return new CopyOnWriteArrayList<>(); + } + + @Singleton + @Named + public SSLContextParameters sslContextParameters() { + TrustManagersParameters trustManagersParameters = new TrustManagersParameters(); + SSLContextParameters sslContextParameters = new SSLContextParameters(); + sslContextParameters.setTrustManagers(trustManagersParameters); + + KeyManagersParameters keyManagersParameters = new KeyManagersParameters(); + sslContextParameters.setKeyManagers(keyManagersParameters); + + return sslContextParameters; + } + + @Singleton + @Named + public Properties additionalProperties() { + Properties prop = new Properties(); + + prop.setProperty("mail.smtps.ssl.trust", "*"); + prop.setProperty("mail.pop3s.ssl.trust", "*"); + prop.setProperty("mail.imaps.ssl.trust", "*"); + + return prop; + } } } diff --git a/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/MockMailbox.java b/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/MockMailbox.java deleted file mode 100644 index c0e16e5649..0000000000 --- a/integration-tests/mail/src/main/java/org/apache/camel/quarkus/component/mail/MockMailbox.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.quarkus.component.mail; - -import javax.enterprise.context.ApplicationScoped; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - -import org.jvnet.mock_javamail.Mailbox; - -@Path("/mock/{username}") -@ApplicationScoped -public class MockMailbox { - - @GET - @Path("/size") - @Produces(MediaType.TEXT_PLAIN) - public String getSize(@PathParam("username") String username) throws Exception { - Mailbox mailbox = Mailbox.get(username); - return Integer.toString(mailbox.size()); - } - - @GET - @Path("/{id}/content") - @Produces(MediaType.TEXT_PLAIN) - public String getContent(@PathParam("username") String username, @PathParam("id") int id) throws Exception { - Mailbox mailbox = Mailbox.get(username); - return mailbox.get(id).getContent().toString(); - } - - @GET - @Path("/{id}/subject") - @Produces(MediaType.TEXT_PLAIN) - public String getSubject(@PathParam("username") String username, @PathParam("id") int id) throws Exception { - Mailbox mailbox = Mailbox.get(username); - return mailbox.get(id).getSubject(); - } -} diff --git a/integration-tests/mail/src/main/resources/application.properties b/integration-tests/mail/src/main/resources/application.properties new file mode 100644 index 0000000000..7508c1d5ed --- /dev/null +++ b/integration-tests/mail/src/main/resources/application.properties @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## --------------------------------------------------------------------------- +quarkus.native.resources.includes = data/logo.jpeg \ No newline at end of file diff --git a/integration-tests/mail/src/main/resources/data/logo.jpeg b/integration-tests/mail/src/main/resources/data/logo.jpeg new file mode 100644 index 0000000000..b635017d1e Binary files /dev/null and b/integration-tests/mail/src/main/resources/data/logo.jpeg differ diff --git a/integration-tests/mail/src/test/java/org/apache/camel/quarkus/component/mail/MailTest.java b/integration-tests/mail/src/test/java/org/apache/camel/quarkus/component/mail/MailTest.java index 359e87a03f..3503fea28e 100644 --- a/integration-tests/mail/src/test/java/org/apache/camel/quarkus/component/mail/MailTest.java +++ b/integration-tests/mail/src/test/java/org/apache/camel/quarkus/component/mail/MailTest.java @@ -16,18 +16,43 @@ */ package org.apache.camel.quarkus.component.mail; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.restassured.http.ContentType; +import org.apache.camel.ExchangePropertyKey; +import org.apache.camel.ServiceStatus; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.testcontainers.shaded.org.awaitility.Awaitility; -import static org.hamcrest.core.Is.is; +import static org.apache.camel.quarkus.component.mail.CamelRoute.EMAIL_ADDRESS; +import static org.apache.camel.quarkus.component.mail.CamelRoute.PASSWORD; +import static org.apache.camel.quarkus.component.mail.CamelRoute.USERNAME; @QuarkusTest +@QuarkusTestResource(MailTestResource.class) public class MailTest { private static final Pattern DELIMITER_PATTERN = Pattern.compile("\r\n[^\r\n]+"); private static final String EXPECTED_TEMPLATE = "${delimiter}\r\n" @@ -45,27 +70,79 @@ public class MailTest { + "Hello attachment!" + "${delimiter}--\r\n"; - @Test - public void testSendAsMail() { + @BeforeEach + public void beforeEach() { + // Configure users + Config config = ConfigProvider.getConfig(); + String userJson = String.format("{ \"email\": \"%s\", \"login\": \"%s\", \"password\": \"%s\"}", EMAIL_ADDRESS, + USERNAME, PASSWORD); RestAssured.given() - .contentType(ContentType.TEXT) - .body("Hi how are you") - .post("/mail/route/mailtext") + .contentType(ContentType.JSON) + .body(userJson) + .post("http://localhost:" + config.getValue("mail.api.port", Integer.class) + "/api/user") .then() .statusCode(200); + } + @AfterEach + public void afterEach() { + // Clear mailboxes + Config config = ConfigProvider.getConfig(); RestAssured.given() - .get("/mock/{username}/size", "james@localhost") + .port(config.getValue("mail.api.port", Integer.class)) + .post("/api/service/reset") .then() - .body(is("1")); - RestAssured.given() - .get("/mock/{username}/{id}/content", "james@localhost", 0) + .statusCode(200) + .body("message", Matchers.is("Performed reset")); + + RestAssured.get("/mail/stopConsumers") + .then() + .statusCode(204); + + RestAssured.get("/mail/clear") .then() - .body(is("Hi how are you")); + .statusCode(204); + } + + @ParameterizedTest + @ValueSource(strings = { "pop3", "imap" }) + public void receive(String protocol) { + //start route + startRoute("pop3".equals(protocol) ? CamelRoute.Routes.pop3ReceiveRoute : CamelRoute.Routes.imapReceiveRoute); + + send(false); + } + + @ParameterizedTest + @ValueSource(strings = { "pop3s", "imaps" }) + public void receiveSecured(String protocol) { + //start route + startRoute("pop3s".equals(protocol) ? CamelRoute.Routes.pop3sReceiveRoute : CamelRoute.Routes.imapsReceiveRoute); + + send(true); + } + + private void send(boolean secured) { RestAssured.given() - .get("/mock/{username}/{id}/subject", "james@localhost", 0) + .contentType(ContentType.TEXT) + .queryParam("subject", "Hello World") + .queryParam("from", "camel@localhost") + .queryParam("to", EMAIL_ADDRESS) + .queryParam("secured", secured) + .body("Hi how are you") + .post("/mail/send") .then() - .body(is("Hello World")); + .statusCode(204); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> { + //receive + return (List<Map<String, Object>>) RestAssured.get("/mail/getReceived/") + .then() + .statusCode(200) + .extract().as(List.class); + }, list -> list.size() == 1 + && "Hi how are you".equals(list.get(0).get("content")) + && "Hello World".equals(list.get(0).get("subject"))); } @Test @@ -82,13 +159,12 @@ public class MailTest { final String unmarshalMarshal = RestAssured.given() .contentType(ContentType.TEXT) .body(actual) - .post("/mail/route/mimeMultipartUnmarshalMarshal") + .post("/mail/mimeMultipartUnmarshalMarshal") .then() .statusCode(200) .extract().body().asString(); assertMultipart(EXPECTED_TEMPLATE, unmarshalMarshal); - } private void assertMultipart(final String expectedPattern, final String actual) { @@ -101,4 +177,181 @@ public class MailTest { Assertions.assertEquals(expected, actual); } + @Test + public void testAttachments() throws IOException, URISyntaxException { + startRoute(CamelRoute.Routes.pop3ReceiveRoute); + + String mailBodyContent = "Test mail content"; + String attachmentContent = "Attachment " + mailBodyContent; + java.nio.file.Path attachmentPath = Files.createTempFile("cq-attachment", ".txt"); + Files.write(attachmentPath, attachmentContent.getBytes(StandardCharsets.UTF_8)); + + try { + RestAssured.given() + .contentType(ContentType.TEXT) + .queryParam("subject", "Test attachment message") + .queryParam("from", "camel@localhost") + .queryParam("to", EMAIL_ADDRESS) + .body(mailBodyContent) + .post("/mail/send/attachment/{fileName}", attachmentPath.toAbsolutePath().toString()) + .then() + .statusCode(204); + + RestAssured.given() + .contentType(ContentType.TEXT) + .queryParam("subject", "Test attachment message") + .queryParam("from", "camel@localhost") + .queryParam("to", EMAIL_ADDRESS) + .body(mailBodyContent) + .post("/mail/send/attachment/{fileName}", "logo.jpeg") + .then() + .statusCode(204); + + Awaitility.await().atMost(20, TimeUnit.SECONDS).until(() -> { + return (List<Map<String, Object>>) RestAssured.get("/mail/getReceived/") + .then() + .statusCode(200) + .extract().as(List.class); + }, + list -> { + if (list.size() == 2) { + Map<String, Object> msg1 = list.get(0); + List<Map<String, Object>> attch1 = (List<Map<String, Object>>) msg1.get("attachments"); + Map<String, Object> msg2 = list.get(1); + List<Map<String, Object>> attch2 = (List<Map<String, Object>>) msg2.get("attachments"); + + return "Test mail content".equals(msg1.get("content")) + && "Test attachment message".equals(msg1.get("subject")) + && attachmentPath.getFileName().toString().equals(attch1.get(0).get("attachmentFilename")) + && attachmentContent.equals(attch1.get(0).get("attachmentContent")) + && (attch1.get(0).get("attachmentContentType").toString().startsWith("text/plain") + || attch1.get(0).get("attachmentContentType").toString() + .startsWith("application/octet-stream")) + + && "Test mail content".equals(msg2.get("content")) + && "Test attachment message".equals(msg2.get("subject")) + && "logo.jpeg".equals(attch2.get(0).get("attachmentFilename")) + && (attch2.get(0).get("attachmentContentType").toString().startsWith("image/jpeg") + || attch2.get(0).get("attachmentContentType").toString().toString() + .startsWith("application/octet-stream")); + } + return false; + }); + + } finally { + Files.deleteIfExists(attachmentPath); + } + } + + @Test + public void testBatchConsumer() { + //start route + startRoute(CamelRoute.Routes.batchReceiveRoute); + //send messages + IntStream.range(1, 5).boxed().forEach(i -> RestAssured.given() + .contentType(ContentType.JSON) + .contentType(ContentType.TEXT) + .queryParam("subject", "Test batch consumer") + .queryParam("from", "camel@localhost") + .queryParam("to", EMAIL_ADDRESS) + .body("message " + i) + .post("/mail/send") + .then() + .statusCode(204)); + + Awaitility.await().atMost(20, TimeUnit.SECONDS).until(() -> { + //receive + return (List<Map<String, Object>>) RestAssured.get("/mail/getReceived/") + .then() + .statusCode(200) + .extract().as(List.class); + }, list -> list.size() == 4 + + && "message 1".equals(list.get(0).get("content")) + && "Test batch consumer".equals(list.get(0).get("subject")) + && "0".equals(list.get(0).get(ExchangePropertyKey.BATCH_INDEX.getName()).toString()) + && "3".equals(list.get(0).get(ExchangePropertyKey.BATCH_SIZE.getName()).toString()) + && !((Boolean) list.get(0).get(ExchangePropertyKey.BATCH_COMPLETE.getName())) + + && "message 2".equals(list.get(1).get("content")) + && "Test batch consumer".equals(list.get(1).get("subject")) + && "1".equals(list.get(1).get(ExchangePropertyKey.BATCH_INDEX.getName()).toString()) + && !((Boolean) list.get(1).get(ExchangePropertyKey.BATCH_COMPLETE.getName())) + + && "message 3".equals(list.get(2).get("content")) + && "Test batch consumer".equals(list.get(2).get("subject")) + && "2".equals(list.get(2).get(ExchangePropertyKey.BATCH_INDEX.getName()).toString()) + && ((Boolean) list.get(2).get(ExchangePropertyKey.BATCH_COMPLETE.getName())) + + && "message 4".equals(list.get(3).get("content")) + && "Test batch consumer".equals(list.get(3).get("subject")) + && "0".equals(list.get(3).get(ExchangePropertyKey.BATCH_INDEX.getName()).toString())); + + } + + @Test + public void testConverters() throws Exception { + startRoute(CamelRoute.Routes.convertersRoute); + + RestAssured.given() + .contentType(ContentType.JSON) + .contentType(ContentType.TEXT) + .queryParam("subject", "Camel Rocks") + .queryParam("from", "camel@localhost") + .queryParam("to", EMAIL_ADDRESS) + .body("Hello World ") + .post("/mail/send") + .then() + .statusCode(204); + + Awaitility.await().atMost(20, TimeUnit.SECONDS).until(() -> { + //receive + return (List<Map<String, Object>>) RestAssured.get("/mail/getReceivedAsString/") + .then() + .statusCode(200) + .extract().as(List.class); + }, list -> list.size() == 1 + && ((String) list.get(0).get("body")).matches("Hello World\\s*")); + } + + @Test + public void testSort() { + List<String> msgs = IntStream.range(1, 5).boxed().map(i -> ("message " + i)).collect(Collectors.toList()); + //messages will be sent in reverse order + Collections.reverse(msgs); + + List<String> sorted = RestAssured.given() + .contentType(ContentType.JSON) + .body(msgs) + .post("/mail/sort") + .then() + .statusCode(200) + .extract().as(List.class); + + Assertions.assertEquals(4, sorted.size()); + Assertions.assertTrue(sorted.get(0).contains("message 1")); + Assertions.assertTrue(sorted.get(1).contains("message 2")); + Assertions.assertTrue(sorted.get(2).contains("message 3")); + Assertions.assertTrue(sorted.get(3).contains("message 4")); + } + + // helper methods + + private void startRoute(CamelRoute.Routes route) { + RestAssured.given() + .get("/mail/route/" + route.name() + "/start") + .then().statusCode(204); + + //wait for finish + Awaitility.await().atMost(5, TimeUnit.SECONDS).until( + () -> { + String status = RestAssured + .get("/mail/route/" + route.name() + "/status") + .then() + .statusCode(200) + .extract().asString(); + + return status.equals(ServiceStatus.Started.name()); + }); + } } diff --git a/integration-tests/mail/src/test/java/org/apache/camel/quarkus/component/mail/MailTestResource.java b/integration-tests/mail/src/test/java/org/apache/camel/quarkus/component/mail/MailTestResource.java new file mode 100644 index 0000000000..f7e6abb497 --- /dev/null +++ b/integration-tests/mail/src/test/java/org/apache/camel/quarkus/component/mail/MailTestResource.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.quarkus.component.mail; + +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.utility.DockerImageName; + +public class MailTestResource implements QuarkusTestResourceLifecycleManager { + + private static final String GREENMAIL_IMAGE_NAME = "greenmail/standalone:1.6.7"; + private GenericContainer<?> container; + + @Override + public Map<String, String> start() { + container = new GenericContainer<>(DockerImageName.parse(GREENMAIL_IMAGE_NAME)) + .withExposedPorts(MailProtocol.allPorts()) + .waitingFor(new HttpWaitStrategy() + .forPort(MailProtocol.API.getPort()) + .forPath("/api/service/readiness") + .forStatusCode(200)); + + container.start(); + + Map<String, String> options = new HashMap<>(); + for (MailProtocol protocol : MailProtocol.values()) { + String optionName = String.format("mail.%s.port", protocol.name().toLowerCase()); + Integer mappedPort = container.getMappedPort(protocol.getPort()); + options.put(optionName, mappedPort.toString()); + } + + return options; + } + + @Override + public void stop() { + if (container != null) { + container.stop(); + } + } + + enum MailProtocol { + SMTP(3025), + POP3(3110), + IMAP(3143), + SMTPS(3465), + IMAPS(3993), + POP3s(3995), + API(8080); + + private final int port; + + MailProtocol(int port) { + this.port = port; + } + + public int getPort() { + return port; + } + + public static Integer[] allPorts() { + MailProtocol[] values = values(); + Integer[] ports = new Integer[values.length]; + for (int i = 0; i < values.length; i++) { + ports[i] = values[i].getPort(); + } + return ports; + } + } +} diff --git a/pom.xml b/pom.xml index 8834903967..4fbdf0e0ff 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,7 @@ <commons-logging.version>1.2</commons-logging.version><!-- Mess in the transitive dependencies of hbase-testing-util --> <consul-client.version>${consul-client-version}</consul-client.version> <ftpserver.version>1.1.1</ftpserver.version> + <greenmail.version>1.6.7</greenmail.version> <istack-commons-runtime.version>3.0.10</istack-commons-runtime.version> <jakarta.mail.version>${jakarta-mail-version}</jakarta.mail.version> <htmlunit-driver.version>2.47.1</htmlunit-driver.version> @@ -477,6 +478,7 @@ <exclude>**/*.graphql</exclude> <exclude>**/*.ics</exclude> <exclude>**/*.jks</exclude> + <exclude>**/*.jpeg</exclude> <exclude>**/*.key</exclude> <exclude>**/*.kts</exclude> <exclude>**/*.lock</exclude> diff --git a/poms/bom-test/pom.xml b/poms/bom-test/pom.xml index a75f432b65..068869fc24 100644 --- a/poms/bom-test/pom.xml +++ b/poms/bom-test/pom.xml @@ -327,6 +327,12 @@ <artifactId>aws-java-sdk-core</artifactId> <version>${aws-java-sdk.version}</version> </dependency> + <dependency> + <groupId>com.icegreen</groupId> + <artifactId>greenmail</artifactId> + <version>${greenmail.version}</version> + <scope>test</scope> + </dependency> </dependencies> </dependencyManagement> diff --git a/poms/bom/src/main/generated/flattened-full-pom.xml b/poms/bom/src/main/generated/flattened-full-pom.xml index fb52bb0eeb..5bf60a7abf 100644 --- a/poms/bom/src/main/generated/flattened-full-pom.xml +++ b/poms/bom/src/main/generated/flattened-full-pom.xml @@ -17,24 +17,24 @@ </licenses> <developers> <developer> - <name>The Apache Camel Team</name><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 229 --> - <email>[email protected]</email><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 230 --> - <url>http://camel.apache.org</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 231 --> - <organization>Apache Software Foundation</organization><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 232 --> - <organizationUrl>http://apache.org/</organizationUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 233 --> + <name>The Apache Camel Team</name><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 230 --> + <email>[email protected]</email><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 231 --> + <url>http://camel.apache.org</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 232 --> + <organization>Apache Software Foundation</organization><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 233 --> + <organizationUrl>http://apache.org/</organizationUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 234 --> <properties> - <picUrl>http://camel.apache.org/banner.data/apache-camel-7.png</picUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 235 --> + <picUrl>http://camel.apache.org/banner.data/apache-camel-7.png</picUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 236 --> </properties> </developer> </developers> <scm> - <connection>scm:git:http://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</connection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 262 --> - <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</developerConnection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 263 --> - <url>https://gitbox.apache.org/repos/asf?p=camel-quarkus.git;a=summary/camel-quarkus-poms/camel-quarkus-bom</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 264 --> + <connection>scm:git:http://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</connection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 263 --> + <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</developerConnection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 264 --> + <url>https://gitbox.apache.org/repos/asf?p=camel-quarkus.git;a=summary/camel-quarkus-poms/camel-quarkus-bom</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 265 --> </scm> <issueManagement> - <system>GitHub</system><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 269 --> - <url>https://github.com/apache/camel-quarkus/issues</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 270 --> + <system>GitHub</system><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 270 --> + <url>https://github.com/apache/camel-quarkus/issues</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 271 --> </issueManagement> <distributionManagement> <repository> diff --git a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml index 9f7e2d6a48..6efbf96143 100644 --- a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml +++ b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml @@ -17,24 +17,24 @@ </licenses> <developers> <developer> - <name>The Apache Camel Team</name><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 229 --> - <email>[email protected]</email><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 230 --> - <url>http://camel.apache.org</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 231 --> - <organization>Apache Software Foundation</organization><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 232 --> - <organizationUrl>http://apache.org/</organizationUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 233 --> + <name>The Apache Camel Team</name><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 230 --> + <email>[email protected]</email><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 231 --> + <url>http://camel.apache.org</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 232 --> + <organization>Apache Software Foundation</organization><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 233 --> + <organizationUrl>http://apache.org/</organizationUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 234 --> <properties> - <picUrl>http://camel.apache.org/banner.data/apache-camel-7.png</picUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 235 --> + <picUrl>http://camel.apache.org/banner.data/apache-camel-7.png</picUrl><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 236 --> </properties> </developer> </developers> <scm> - <connection>scm:git:http://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</connection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 262 --> - <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</developerConnection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 263 --> - <url>https://gitbox.apache.org/repos/asf?p=camel-quarkus.git;a=summary/camel-quarkus-poms/camel-quarkus-bom</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 264 --> + <connection>scm:git:http://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</connection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 263 --> + <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/camel-quarkus.git/camel-quarkus-poms/camel-quarkus-bom</developerConnection><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 264 --> + <url>https://gitbox.apache.org/repos/asf?p=camel-quarkus.git;a=summary/camel-quarkus-poms/camel-quarkus-bom</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 265 --> </scm> <issueManagement> - <system>GitHub</system><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 269 --> - <url>https://github.com/apache/camel-quarkus/issues</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 270 --> + <system>GitHub</system><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 270 --> + <url>https://github.com/apache/camel-quarkus/issues</url><!-- org.apache.camel.quarkus:camel-quarkus:2.7.2-SNAPSHOT, line 271 --> </issueManagement> <distributionManagement> <repository>
