You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:17:17 UTC
[sling-org-apache-sling-testing-email] 01/05: SLING-6949 - Create
testing utilities for email-enabled applications
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to annotated tag org.apache.sling.testing.email-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-email.git
commit 2710b1fba47e799aff7d52ed15ea6c5a7973dd0d
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Jun 9 14:36:36 2017 +0000
SLING-6949 - Create testing utilities for email-enabled applications
Initial commit of a bundle which starts an SMTP server inside a Sling
application and then exposes the received messages via HTTP.
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/email@1798227 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 123 ++++++++++++++++++
.../sling/testing/email/impl/EMailServlet.java | 109 ++++++++++++++++
.../testing/email/impl/SmtpServerWrapper.java | 114 +++++++++++++++++
.../sling/testing/email/impl/EmailServletTest.java | 142 +++++++++++++++++++++
.../testing/email/impl/SmtpServerWrapperTest.java | 61 +++++++++
5 files changed, 549 insertions(+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..68e43b8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>30</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.sling.testing.email</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Testing Email Support</name>
+ <description>
+ Contains utilities that assist in validating email-enabled OSGi applications.
+ </description>
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/email</connection>
+ <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/email</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/testing/email</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <!--
+ subetha-smpt and subetha-wiser are not OSGi bundles
+ Since the potential for reuse inside the runtime is quite small,
+ let bnd embed the needed packages inside this bundle
+
+ Also embed the felix-utils JSON Writer
+ -->
+ <Conditional-Package>org.subethamail.*,org.apache.felix.utils.json</Conditional-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.subethamail</groupId>
+ <artifactId>subethasmtp-wiser</artifactId>
+ <version>1.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.http.whiteboard</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <version>1.9.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.clients</artifactId>
+ <version>1.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+ <version>2.2.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.jsonpath</groupId>
+ <artifactId>json-path</artifactId>
+ <version>2.2.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/testing/email/impl/EMailServlet.java b/src/main/java/org/apache/sling/testing/email/impl/EMailServlet.java
new file mode 100644
index 0000000..25ff6ac
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/email/impl/EMailServlet.java
@@ -0,0 +1,109 @@
+/*
+ * 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.sling.testing.email.impl;
+
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.utils.json.JSONWriter;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Exposes emails stored by the {@link SmtpServerWrapper} through an HTTP API
+ *
+ * <p>The entry points for the servlet are:
+ *
+ * <ol>
+ * <li><tt>GET /system/sling/testing/email/config</tt>, which returns
+ * a JSON object containing the configuration properties of the {@link SmtpServerWrapper}</li>
+ * <li><tt>GET /system/sling/testing/email/messages</tt>, which returns the messages
+ * currently held by the {@link SmtpServerWrapper}</li>
+ * </ol>
+ */
+@Component(service = Servlet.class,
+ property = {
+ HTTP_WHITEBOARD_SERVLET_PATTERN + "=/system/sling/testing/email",
+ HTTP_WHITEBOARD_CONTEXT_SELECT + "=(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=org.osgi.service.http)"
+ })
+public class EMailServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Reference
+ private SmtpServerWrapper wiser;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+
+ resp.setContentType("application/json");
+ JSONWriter w = new JSONWriter(resp.getWriter());
+
+ String action = ( req.getPathInfo() == null || req.getPathInfo().isEmpty() ) ? "messages" : req.getPathInfo().substring(1);
+
+ switch (action) {
+ case "messages":
+ w.object();
+ w.key("messages");
+ w.array();
+ for ( MimeMessage msg : wiser.getMessages() ) {
+ w.object();
+ try {
+ Enumeration<?> headers = msg.getAllHeaders();
+ while ( headers.hasMoreElements()) {
+ Header header = (Header) headers.nextElement();
+ w.key(header.getName()).value(header.getValue());
+ }
+
+ w.key("-Content-").value(msg.getContent());
+
+ } catch (MessagingException e) {
+ throw new ServletException("Failed retrieving message data", e);
+ }
+ w.endObject();
+ }
+ w.endArray();
+ w.endObject();
+ break;
+
+ case "config":
+ w.object();
+ w.key("bindPort").value(wiser.getEffectiveBindPort());
+ w.endObject();
+ break;
+
+ default:
+ resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ break;
+ }
+
+
+ }
+}
diff --git a/src/main/java/org/apache/sling/testing/email/impl/SmtpServerWrapper.java b/src/main/java/org/apache/sling/testing/email/impl/SmtpServerWrapper.java
new file mode 100644
index 0000000..046b626
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/email/impl/SmtpServerWrapper.java
@@ -0,0 +1,114 @@
+/*
+ * 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.sling.testing.email.impl;
+
+import java.lang.reflect.Field;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.subethamail.smtp.server.SMTPServer;
+import org.subethamail.wiser.Wiser;
+import org.subethamail.wiser.WiserMessage;
+
+// we want the component to be immediate since it is usable outside the OSGi service registry
+// via SMTP connections
+@Component(service = SmtpServerWrapper.class, immediate = true)
+@Designate(ocd = SmtpServerWrapper.Config.class)
+public class SmtpServerWrapper {
+
+ @ObjectClassDefinition(name="Apache Sling Testing SMTP Server Wrapper")
+ public @interface Config {
+
+ @AttributeDefinition(name="Bind port", description="The port on which the server will bind. A value of 0 requests binding on any available port ")
+ int bind_port() default 0;
+ }
+
+ // wiser is not thread-safe so guard access to the instance
+ private final Object sync = new Object();
+ private Wiser wiser;
+ private int effectiveBindPort;
+
+ @Activate
+ public void activate(Config cfg) throws ReflectiveOperationException {
+
+ int bindPort;
+
+ synchronized (sync) {
+ wiser = new Wiser();
+ wiser.setPort(cfg.bind_port());
+ wiser.start();
+ bindPort = cfg.bind_port() == 0 ? reflectiveGetEffectiveBindPort(wiser.getServer()) : cfg.bind_port();
+ }
+
+ effectiveBindPort = bindPort;
+ }
+
+ private int reflectiveGetEffectiveBindPort(SMTPServer server) throws ReflectiveOperationException {
+
+ // we control the version of Wiser used so there is no risk of an exception here
+ Field field = SMTPServer.class.getDeclaredField("serverSocket");
+ field.setAccessible(true);
+ ServerSocket socket = (ServerSocket) field.get(server);
+
+ return socket.getLocalPort();
+ }
+
+ @Deactivate
+ public void deactivate() {
+
+ synchronized (sync) {
+ wiser.stop();
+ }
+ }
+
+ public int getEffectiveBindPort() {
+
+ return effectiveBindPort;
+ }
+
+ public void clearMessages() {
+
+ synchronized (sync) {
+ wiser.getMessages().clear();
+ }
+ }
+
+ public List<MimeMessage> getMessages() {
+
+ try {
+ List<MimeMessage> messages = new ArrayList<>();
+ synchronized (sync) {
+ for ( WiserMessage message : wiser.getMessages()) {
+ messages.add(message.getMimeMessage());
+ }
+ }
+ return messages;
+ } catch (MessagingException e) {
+ throw new RuntimeException("Failed converting to " + MimeMessage.class.getName(), e);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/sling/testing/email/impl/EmailServletTest.java b/src/test/java/org/apache/sling/testing/email/impl/EmailServletTest.java
new file mode 100644
index 0000000..f07d2d4
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/email/impl/EmailServletTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sling.testing.email.impl;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import javax.mail.Message.RecipientType;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.servlethelpers.MockSlingHttpServletResponse;
+import org.apache.sling.testing.clients.util.PortAllocator;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.jayway.jsonpath.JsonPath;
+
+public class EmailServletTest {
+
+ @Rule
+ public SlingContext ctx = new SlingContext();
+ private int bindPort;
+ private EMailServlet servlet;
+
+ @Before
+ public void prepare() {
+
+ bindPort = new PortAllocator().allocatePort();
+
+ ctx.registerInjectActivateService(new SmtpServerWrapper(), Collections.singletonMap("bind.port", bindPort));
+
+ servlet = ctx.registerInjectActivateService(new EMailServlet());
+ }
+
+ @Test
+ public void getBindPort() throws ServletException, IOException {
+
+ // SLING-6947
+ MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(ctx.resourceResolver()) {
+ @Override
+ public String getPathInfo() {
+ return "/config";
+ }
+ };
+
+ MockSlingHttpServletResponse response = new MockSlingHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals("response.status", HttpServletResponse.SC_OK, response.getStatus());
+
+ // SLING-6948
+ byte[] out = response.getOutputAsString().getBytes();
+ int configuredPort = JsonPath.read(new ByteArrayInputStream(out), "$.bindPort");
+
+ assertThat("bindPort", configuredPort, equalTo(bindPort));
+ }
+
+ @Test
+ public void getMessages() throws ServletException, IOException, MessagingException {
+
+ String subject1 = "Test email";
+ String body1 = "A long message \r\nbody";
+ sendEmail(subject1, body1);
+
+ String subject2 = "Verification email";
+ String body2 = "A shorter message body";
+ sendEmail(subject2, body2);
+
+ // SLING-6947
+ MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(ctx.resourceResolver()) {
+ @Override
+ public String getPathInfo() {
+ return "/messages";
+ }
+ };
+
+ MockSlingHttpServletResponse response = new MockSlingHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals("response.status", HttpServletResponse.SC_OK, response.getStatus());
+
+ // SLING-6948
+ byte[] out = response.getOutputAsString().getBytes();
+ List<String> subjects = JsonPath.read(new ByteArrayInputStream(out), "$.messages[*].Subject");
+
+ assertThat("subjects.size", subjects, hasSize(2));
+ assertThat("subjects", subjects, Matchers.hasItems(subject1, subject2));
+
+ String readBody = JsonPath.read(new ByteArrayInputStream(out), "$.messages[0].['-Content-']");
+ assertThat("body", readBody, equalTo(body1));
+ }
+
+ private void sendEmail(String subject, String body) throws MessagingException, AddressException {
+
+ Properties mailProps = new Properties();
+ mailProps.put("mail.smtp.host", "localhost");
+ mailProps.put("mail.smtp.port", String.valueOf(bindPort));
+
+ Session mailSession = Session.getInstance(mailProps);
+
+ MimeMessage msg = new MimeMessage(mailSession);
+ msg.setFrom(new InternetAddress("sender@localhost"));
+ msg.addRecipient(RecipientType.TO, new InternetAddress("receiver@localhost"));
+ msg.setSubject(subject);
+ msg.setText(body);
+
+ Transport.send(msg);
+ }
+}
diff --git a/src/test/java/org/apache/sling/testing/email/impl/SmtpServerWrapperTest.java b/src/test/java/org/apache/sling/testing/email/impl/SmtpServerWrapperTest.java
new file mode 100644
index 0000000..14e28a8
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/email/impl/SmtpServerWrapperTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.sling.testing.email.impl;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+import java.util.Collections;
+
+import org.apache.sling.testing.clients.util.PortAllocator;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class SmtpServerWrapperTest {
+
+ @Rule public OsgiContext ctx = new OsgiContext();
+
+ private SmtpServerWrapper wrapper;
+
+ @After
+ public void cleanUp() {
+ if ( wrapper != null ) {
+ wrapper.deactivate();
+ }
+ }
+
+ @Test
+ public void startupWithPreconfiguredPort() throws ReflectiveOperationException {
+
+ final int configuredPort = new PortAllocator().allocatePort();
+
+ wrapper = ctx.registerInjectActivateService(new SmtpServerWrapper(), Collections.singletonMap("bind.port", configuredPort));
+
+ assertThat("bindPort", wrapper.getEffectiveBindPort(), equalTo(configuredPort));
+ }
+
+ @Test
+ public void startupWithRandomPort() throws ReflectiveOperationException {
+
+ wrapper = ctx.registerInjectActivateService(new SmtpServerWrapper(), Collections.singletonMap("bind.port", 0));
+
+ assertThat("bindPort", wrapper.getEffectiveBindPort(), greaterThan(0));
+ }
+}
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.