diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9bcf999 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: java +jdk: + - oraclejdk8 diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..684456c --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,19 @@ +CHANGELOG +========= + +0.2.0 +------ + +* 4d01349 Add tests with improved conversion of MIME Multipart messages to string +* d72ca46 Apply Checkstyle formatting (modified sun_checks) rules and add javadoc + +0.1.1 +----- + +* Upgrade javax.mail to 1.4.7 + +0.1.0 +----- + +* Initial release + diff --git a/README.md b/README.md index 0a167a8..caf9cc3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # wiser-assertions Assertions for Wiser SMTP test server from subethamail +## Origin + +Taken from Rafal Browiec [WiserAssertions] class. + ## Usage @Before @@ -33,3 +37,5 @@ Assertions for Wiser SMTP test server from subethamail .withContentContains(message_element_2) .withContentContains(message_element_3); } + +[WiserAssertions]:http://blog.codeleak.pl/2014/09/testing-mail-code-in-spring-boot.html \ No newline at end of file diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..9706c65 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nb-configuration.xml b/nb-configuration.xml new file mode 100644 index 0000000..6e1225c --- /dev/null +++ b/nb-configuration.xml @@ -0,0 +1,39 @@ + + + + + + true + *;static *;java;javax;org.springframework;uk.ac.fife + true + 1 + true + true + true + true + true + true + 1 + none + 4 + 4 + 8 + 80 + true + project + true + ORDERED_IN_CATEGORY + false + false + + diff --git a/pom.xml b/pom.xml index b9b84d0..d6d8f01 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 net.kemitix wiser-assertions - 0.1.1 + 0.2.0 jar wiser-assertions @@ -11,28 +11,8 @@ https://github.com/kemitix/wiser-assertions - - - Paul Campbell - pcampbell@kemitix.net - Kemitix - https://github.com/kemitix/ - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - 2015 - - 3.0.4 - - https://github.com/kemitix/wiser-assertions/issues GitHub Issues @@ -46,14 +26,12 @@ UTF-8 - 1.8 - 1.8 - org.sonatype.oss - oss-parent - 9 + net.kemitix + kemitix-spring-parent + 1.2.1 @@ -67,59 +45,17 @@ subethasmtp 3.1.7 + + junit + junit + test + + + org.mockito + mockito-core + test + jar + - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - ${gpg.passphrase} - - - - sign-artifacts - verify - - sign - - - - - - - - - - - - - org.apache.maven.plugins - maven-source-plugin - 2.4 - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.2 - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - - - \ No newline at end of file + diff --git a/src/main/java/net/kemitix/wiser/assertions/WiserAssertions.java b/src/main/java/net/kemitix/wiser/assertions/WiserAssertions.java index 5d93ae9..a2fc2b6 100644 --- a/src/main/java/net/kemitix/wiser/assertions/WiserAssertions.java +++ b/src/main/java/net/kemitix/wiser/assertions/WiserAssertions.java @@ -1,61 +1,144 @@ package net.kemitix.wiser.assertions; -import java.io.ByteArrayOutputStream; +import org.subethamail.wiser.Wiser; +import org.subethamail.wiser.WiserMessage; + import java.io.IOException; -import java.io.OutputStream; import java.text.MessageFormat; import java.util.List; import java.util.function.Predicate; import java.util.function.Supplier; + import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import org.subethamail.wiser.Wiser; -import org.subethamail.wiser.WiserMessage; /** - * Taken from the WiserAssetions class from Rafal Browiec at - * http://blog.codeleak.pl/2014/09/testing-mail-code-in-spring-boot.html + * Provides a set of assertions for checking the status of any messages received + * by subethamail's Wiser. + * + *
+ * {@code
+ * {@literal @}Before
+ * public void setUp() throws IOException {
+ *     wiser = new Wiser(PORT);
+ *     wiser.start();
+ * }
+ *
+ * {@literal @}After public void tearDown() { wiser.stop(); }
+ *
+ * {@literal @}Test public void testMail() { //given ...
+ *
+ * //when ...
+ *
+ * //then WiserAssertions.assertReceivedMessage(wiser) .from(sender)
+ * .to(recipient_alpha) .to(recipient_beta) .withSubjectContains(subject_prefix)
+ * .withSubjectContains(subject_suffix) .withContentContains(message_element_1)
+ * .withContentContains(message_element_2)
+ * .withContentContains(message_element_3); }
+ * }
+ * 
*/ -public class WiserAssertions { +public final class WiserAssertions { + /** + * The messages received by Wiser. + */ private final List messages; - public static WiserAssertions assertReceivedMessage(Wiser wiser) { + /** + * Creates an instance of {@code} WiserAssertions} ready to make assertions + * on any messages received by the {@link Wiser} server. + * + * @param wiser the SMTP server instance + * + * @return an instance of {@code WiserAssertions} + */ + public static WiserAssertions assertReceivedMessage(final Wiser wiser) { return new WiserAssertions(wiser.getMessages()); } - private WiserAssertions(List messages) { - this.messages = messages; + /** + * Private constructor. + * + * @param wiserMessages the messages to be tested by the assertions + */ + private WiserAssertions(final List wiserMessages) { + this.messages = wiserMessages; } - public WiserAssertions from(String from) { - findFirstOrElseThrow(m -> m.getEnvelopeSender().equals(from), - assertionError("No message from [{0}] found!", from)); + /** + * Checks that there was at least one email received that was sent from the + * {@code sender}. + * + * @param sender email address to search for + * + * @return the {@code WiserAssertions} instance + */ + public WiserAssertions from(final String sender) { + findFirstOrElseThrow(m -> m.getEnvelopeSender().equals(sender), + assertionError("No message from [{0}] found!", sender)); return this; } - public WiserAssertions to(String to) { - findFirstOrElseThrow(m -> m.getEnvelopeReceiver().equals(to), - assertionError("No message to [{0}] found!", to)); + /** + * Checks that there was at least one email received that was sent to the + * {@code recipient}. + * + * @param recipient email address to search for + * + * @return the {@code WiserAssertions} instance + */ + public WiserAssertions to(final String recipient) { + findFirstOrElseThrow(m -> m.getEnvelopeReceiver().equals(recipient), + assertionError("No message to [{0}] found!", recipient)); return this; } - public WiserAssertions withSubject(String subject) { - Predicate predicate = m -> subject.equals(unchecked(getMimeMessage(m)::getSubject)); + /** + * Checks that there was at least one email received that has the required + * subject. + * + * @param subject the subject line to search for + * + * @return the {@code WiserAssertions} instance + */ + public WiserAssertions withSubject(final String subject) { + Predicate predicate + = m -> subject.equals(unchecked(getMimeMessage(m)::getSubject)); findFirstOrElseThrow(predicate, - assertionError("No message with subject [{0}] found!", subject)); + assertionError("No message with subject [{0}] found!", + subject)); return this; } - public WiserAssertions withSubjectContains(String subject) { - Predicate predicate = m -> unchecked(getMimeMessage(m)::getSubject).contains(subject); + /** + * Checks that there was at least one email received that has a subject that + * contains the search text. + * + * @param subject the text to search for in the subject + * + * @return the {@code WiserAssertions} instance + */ + public WiserAssertions withSubjectContains(final String subject) { + Predicate predicate + = m -> unchecked(getMimeMessage(m)::getSubject) + .contains(subject); findFirstOrElseThrow(predicate, - assertionError("No message with subject [{0}] found!", subject)); + assertionError("No message with subject [{0}] found!", + subject)); return this; } - public WiserAssertions withContent(String content) { + /** + * Check that there was at least one email received that has a body that + * matches the content. + * + * @param content the body of the email to search for + * + * @return the {@code WiserAssertions} instance + */ + public WiserAssertions withContent(final String content) { findFirstOrElseThrow(m -> { ThrowingSupplier contentAsString = () -> getMimeMessageBody(m).trim(); @@ -64,21 +147,42 @@ public class WiserAssertions { return this; } - public WiserAssertions withContentContains(String content) { + /** + * Check that there was at least one email received that contains the search + * text. + * + * @param content the text to search for in the body of the email + * + * @return the {@code WiserAssertions} instance + */ + public WiserAssertions withContentContains(final String content) { StringBuilder messageContent = new StringBuilder(); findFirstOrElseThrow((WiserMessage m) -> { ThrowingSupplier contentAsString = () -> getMimeMessageBody(m).trim(); messageContent.append(unchecked(contentAsString)); return unchecked(contentAsString).contains(content); - }, assertionError("No message with content containing [{0}] found! Was {1}", content, messageContent)); + }, assertionError( + "No message with content containing [{0}] found! Was {1}", + content, messageContent)); return this; } - private String getMimeMessageBody(WiserMessage m) throws IOException, MessagingException { - Object content = getMimeMessage(m).getContent(); + /** + * Returns the body of the message. + * + * @param message the message + * + * @return the body of the message + * + * @throws IOException if error extracting the mime message + * @throws MessagingException if the message type is not known + */ + private String getMimeMessageBody(final WiserMessage message) + throws IOException, MessagingException { + Object content = getMimeMessage(message).getContent(); if (content instanceof MimeMessage) { - return (String) content; + return content.toString(); } if (content instanceof MimeMultipart) { return getMimeMultipartAsString((MimeMultipart) content); @@ -86,20 +190,63 @@ public class WiserAssertions { throw new RuntimeException("Unexpected MimeMessage content"); } - private void findFirstOrElseThrow(Predicate predicate, Supplier exceptionSupplier) { + /** + * Checks that at least on message matches the predicate or the supplied + * exception will be thrown. + * + * @param predicate the condition a message must match + * @param exceptionSupplier the supplier of the exception + */ + private void findFirstOrElseThrow( + final Predicate predicate, + final Supplier exceptionSupplier + ) { messages.stream().filter(predicate) .findFirst().orElseThrow(exceptionSupplier); } - private MimeMessage getMimeMessage(WiserMessage wiserMessage) { + /** + * Returns the mime message within the {@link WiserMessage} converting any + * {@link MessagingException}s into {@link RuntimeException}s. + * + * @param wiserMessage the message + * + * @return the mime message + */ + private MimeMessage getMimeMessage(final WiserMessage wiserMessage) { return unchecked(wiserMessage::getMimeMessage); } - private static Supplier assertionError(String errorMessage, Object... args) { - return () -> new AssertionError(MessageFormat.format(errorMessage, args)); + /** + * Returns a {@link Supplier} for an {@link AssertionError}. + * + * @param errorMessage the message for the exception + * @param args the parameters to insert into the message using + * {@link MessageFormat} + * + * @return a supplier of an {@link AssertionError} + */ + @SuppressWarnings( + {"ThrowableInstanceNotThrown", "ThrowableInstanceNeverThrown"}) + private static Supplier assertionError( + final String errorMessage, + final Object... args + ) { + return () + -> new AssertionError(MessageFormat.format(errorMessage, args)); } - public static T unchecked(ThrowingSupplier supplier) { + /** + * Convert any checked Exceptions into unchecked Exceptions. + * + * @param the item type to be returned after suppressing any + * checked exceptions + * @param supplier the source of the return value that could cause a checked + * exception + * + * @return the product of the supplier + */ + public static T unchecked(final ThrowingSupplier supplier) { try { return supplier.get(); } catch (Throwable e) { @@ -107,14 +254,41 @@ public class WiserAssertions { } } - private String getMimeMultipartAsString(MimeMultipart mimeMultipart) throws MessagingException, IOException { - OutputStream os = new ByteArrayOutputStream(); - mimeMultipart.writeTo(os); - return os.toString(); + /** + * Converts a {@link MimeMultipart} into a {@link String} stripping out the + * mime part boundary and headers.. + * + * @param mimeMultipart the message part to convert + * + * @return the message part as a string + * + * @throws MessagingException if the part is empty + * @throws IOException if there is another error + */ + private String getMimeMultipartAsString(final MimeMultipart mimeMultipart) + throws MessagingException, IOException { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mimeMultipart.getCount(); i++) { + sb.append(mimeMultipart.getBodyPart(i).getContent()); + } + return sb.toString(); } + /** + * Interface for providing a value that could thrown an exception when + * sought. + * + * @param the type of value to be supplied + */ public interface ThrowingSupplier { + /** + * Returns the value. + * + * @return the value + * + * @throws Throwable on error + */ T get() throws Throwable; } } diff --git a/src/main/java/net/kemitix/wiser/assertions/package-info.java b/src/main/java/net/kemitix/wiser/assertions/package-info.java new file mode 100644 index 0000000..f459df7 --- /dev/null +++ b/src/main/java/net/kemitix/wiser/assertions/package-info.java @@ -0,0 +1,28 @@ +/** + * Provides {@link WiserAssertions} to check for messages received by the Wiser + * SMTP test server from subethamail. + * + *

+ * The MIT License. + *

+ * Copyright 2015 pcampbell. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kemitix.wiser.assertions; diff --git a/src/test/java/net/kemitix/wiser/assertions/WiserAssertionsTest.java b/src/test/java/net/kemitix/wiser/assertions/WiserAssertionsTest.java new file mode 100644 index 0000000..891f608 --- /dev/null +++ b/src/test/java/net/kemitix/wiser/assertions/WiserAssertionsTest.java @@ -0,0 +1,288 @@ +package net.kemitix.wiser.assertions; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.subethamail.wiser.Wiser; + +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +/** + * Tests for {@link WiserAssertions}. + * + * @author pcampbell + */ +public class WiserAssertionsTest { + + /** + * Logger. + */ + private static final Logger LOG + = Logger.getLogger(WiserAssertionsTest.class.getName()); + + /** + * Test mail server. + */ + private Wiser wiser; + + /** + * Prepare each test. + */ + @Before + @SuppressWarnings("magicnumber") + public void setUp() { + wiser = new Wiser(PORT); + wiser.start(); + } + + /** + * Test mail server port. + */ + private static final int PORT = 12345; + + /** + * Clean up after each test. + */ + @After + public void tearDown() { + wiser.stop(); + } + + /** + * Sends a mime multipart message to the Wiser server. + * + * @param from the sender + * @param to the recipient + * @param subject the subject of the email + * @param body the body of the email + */ + private void sendMimeMultipartMessage( + final String from, + final String to, + final String subject, + final String body) { + Properties properties = new Properties(); + properties.setProperty("mail.smtp.host", "localhost"); + properties.setProperty("mail.smtp.port", "" + PORT); + Session session = Session.getDefaultInstance(properties); + try { + MimeMessage message = new MimeMessage(session); + message.setFrom(new InternetAddress(from)); + message.setRecipients(Message.RecipientType.TO, to); + message.setSubject(subject, "UTF-8"); + final Multipart mimeMultipart = new MimeMultipart(); + final MimeBodyPart mimeBodyPart = new MimeBodyPart(); + mimeBodyPart.setText(body); + mimeMultipart.addBodyPart(mimeBodyPart); + message.setContent(mimeMultipart); + Transport.send(message); + } catch (MessagingException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + + /** + * Instantiates the WiserAssertions. + * + * @return the wiser assertions + */ + private WiserAssertions getAssertions() { + return WiserAssertions.assertReceivedMessage(wiser); + } + + /** + * Test {@link WiserAssertions#withContent(java.lang.String)} where the + * content of the email matches. + */ + @Test + public void testContentMatches() { + //given + final String body = "message body"; + //when + sendMimeMultipartMessage("from", "to", "subject", body); + //then + getAssertions().withContent(body); + } + + /** + * Test {@link WiserAssertions#withContent(java.lang.String)} where the + * content of the email does not match. + */ + @Test(expected = AssertionError.class) + public void testContentNotMatches() { + //given + final String body = "message body"; + //when + sendMimeMultipartMessage("from", "to", "subject", body); + //then + getAssertions().withContent("Other body"); + } + + /** + * Test {@link WiserAssertions#withContentContains(String)} where the + * content of the email matches. + */ + @Test + public void testContentContainsMatches() { + //given + final String body = "message body"; + //when + sendMimeMultipartMessage("from", "to", "subject", body); + //then + getAssertions().withContentContains("age bo"); + } + + /** + * Test {@link WiserAssertions#withContentContains(String)} where the + * content of the email does not match. + */ + @Test(expected = AssertionError.class) + public void testContentContainsNotMatches() { + //given + final String body = "message body"; + //when + sendMimeMultipartMessage("from", "to", "subject", body); + //then + getAssertions().withContentContains("agebo"); + } + + /** + * Test {@link WiserAssertions#from(java.lang.String)} can detect when mail + * is sent from a user. + * + * @throws java.io.IOException if error delivering test message + */ + @Test + public void testFromMatches() throws IOException { + //given + final String from = "bob@a.com"; + //when + sendMimeMultipartMessage(from, "to", "subject", "body"); + //then + getAssertions().from(from); + } + + /** + * Test {@link WiserAssertions#from(java.lang.String)} can detect when mail + * is not sent from a user. + */ + @Test(expected = AssertionError.class) + public void testFromNotMatches() { + //given + final String from = "bob@a.com"; + //when + sendMimeMultipartMessage(from, "to", "subject", "body"); + //then + getAssertions().from("lisa@c.com"); + } + + /** + * Test {@link WiserAssertions#assertReceivedMessage(Wiser)} creates and + * returns a WiserAssertions instance. + */ + @Test + public void testInstantiate() { + assertNotNull(getAssertions()); + } + + /** + * Test {@link WiserAssertions#withSubjectContains(java.lang.String)} where + * the subject contains the expected fragment. + */ + @Test + public void testSubjectContainsMatches() { + //given + final String fragment = "foo"; + //when + sendMimeMultipartMessage("from", "to", "subject " + fragment + " tail", "body"); + //then + getAssertions().withSubjectContains(fragment); + } + + /** + * Test {@link WiserAssertions#withSubjectContains(java.lang.String)} where + * the subject does not contain the expected fragment. + */ + @Test(expected = AssertionError.class) + public void testSubjectContainsNotMatches() { + //given + final String fragment = "foo"; + //when + sendMimeMultipartMessage("from", "to", "subject tail", "body"); + //then + getAssertions().withSubjectContains(fragment); + } + + /** + * Test {@link WiserAssertions#withSubject(java.lang.String)} where the + * message has the subject expected. + */ + @Test + public void testSubjectMatches() { + //given + final String subject = "message subject"; + //when + sendMimeMultipartMessage("from", "to", subject, "body"); + //then + getAssertions().withSubject(subject); + } + + /** + * Test {@link WiserAssertions#withSubject(java.lang.String)} where the + * message does not have the subject expected. + */ + @Test(expected = AssertionError.class) + public void testSubjectNotMatches() { + //given + final String subject = "message subject"; + //when + sendMimeMultipartMessage("from", "to", subject, "body"); + //then + getAssertions().withSubject("other subject"); + } + + /** + * Test {@link WiserAssertions#from(java.lang.String)} can detect when mail + * is sent to a user. + */ + @Test + public void testToMatches() { + //given + final String to = "carl@b.com"; + //when + sendMimeMultipartMessage("from", to, "subject", "body"); + //then + getAssertions().to(to); + } + + /** + * Test {@link WiserAssertions#from(java.lang.String)} can detect when mail + * is not sent from a user. + * + */ + @Test(expected = AssertionError.class) + public void testToNotMatches() { + //given + final String to = "carl@b.com"; + //when + sendMimeMultipartMessage("from", to, "subject", "body"); + //then + getAssertions().to("bob@a.com"); + } + +}