Merge branch release/0.2.0 into master

4d01349 Add tests with improved conversion of MIME Multipart messages to string
d72ca46 Apply Checkstyle formatting (modified sun_checks) rules and add javadoc

Signed-off-by: Paul Campbell <paulcampbell@fife.ac.uk>
This commit is contained in:
Paul Campbell 2015-10-22 09:40:44 +01:00
commit 7b9f291be1
9 changed files with 798 additions and 117 deletions

3
.travis.yml Normal file
View file

@ -0,0 +1,3 @@
language: java
jdk:
- oraclejdk8

19
CHANGELOG Normal file
View file

@ -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

View file

@ -1,6 +1,10 @@
# wiser-assertions # wiser-assertions
Assertions for Wiser SMTP test server from subethamail Assertions for Wiser SMTP test server from subethamail
## Origin
Taken from Rafal Browiec [WiserAssertions] class.
## Usage ## Usage
@Before @Before
@ -33,3 +37,5 @@ Assertions for Wiser SMTP test server from subethamail
.withContentContains(message_element_2) .withContentContains(message_element_2)
.withContentContains(message_element_3); .withContentContains(message_element_3);
} }
[WiserAssertions]:http://blog.codeleak.pl/2014/09/testing-mail-code-in-spring-boot.html

188
checkstyle.xml Normal file
View file

@ -0,0 +1,188 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the sun coding conventions from:
- the Java Language Specification at
http://java.sun.com/docs/books/jls/second_edition/html/index.html
- the Sun Code Conventions at http://java.sun.com/docs/codeconv/
- the Javadoc guidelines at
http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
- the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
- some best practices
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
http://checkstyle.sourceforge.net/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
<module name="JavadocPackage"/>
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="FileLength"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- Checks for Headers -->
<!-- See http://checkstyle.sf.net/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Support @SuppressWarnings annotation -->
<!-- See http://checkstyle.sourceforge.net/config.html -->
<module name="SuppressWarningsHolder"/>
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod"/>
<module name="JavadocType"/>
<module name="JavadocVariable"/>
<module name="JavadocStyle"/>
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sf.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sf.net/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="LineLength">
<!-- Allow for long imports for Spring Batch -->
<!-- EnableBatchProcessing, JobBuilderFactory -->
<!-- and StepBuilderFactory -->
<property name="ignorePattern" value="^import"/>
</module>
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See http://checkstyle.sf.net/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sf.net/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sf.net/config_coding.html -->
<module name="AvoidInlineConditionals"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="HiddenField"/>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MagicNumber"/>
<module name="MissingSwitchDefault"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See http://checkstyle.sf.net/config_design.html -->
<!--<module name="DesignForExtension"/>-->
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="FinalParameters"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
<module name="SuppressWarningsFilter"/>
</module>

39
nb-configuration.xml Normal file
View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<netbeans.checkstyle.format>true</netbeans.checkstyle.format>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.importGroupsOrder>*;static *;java;javax;org.springframework;uk.ac.fife</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.importGroupsOrder>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.separateStaticImports>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.separateStaticImports>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterFields>1</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterFields>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLineAfterJavadocReturnTag>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLineAfterJavadocReturnTag>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignJavadocExceptionDescriptions>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignJavadocExceptionDescriptions>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.enableBlockCommentFormatting>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.enableBlockCommentFormatting>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignJavadocReturnDescription>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignJavadocReturnDescription>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLineAfterJavadocParameterDescriptions>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLineAfterJavadocParameterDescriptions>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignJavadocParameterDescriptions>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignJavadocParameterDescriptions>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesBeforeFields>1</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesBeforeFields>
<org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>none</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
<org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>4</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
<org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>4</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
<org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>8</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
<org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>80</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
<org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>true</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.sortMembersInGroups>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.sortMembersInGroups>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classMemberInsertionPoint>ORDERED_IN_CATEGORY</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classMemberInsertionPoint>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>
</properties>
</project-shared-configuration>

96
pom.xml
View file

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>net.kemitix</groupId> <groupId>net.kemitix</groupId>
<artifactId>wiser-assertions</artifactId> <artifactId>wiser-assertions</artifactId>
<version>0.1.1</version> <version>0.2.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>wiser-assertions</name> <name>wiser-assertions</name>
@ -11,28 +11,8 @@
<url>https://github.com/kemitix/wiser-assertions</url> <url>https://github.com/kemitix/wiser-assertions</url>
<developers>
<developer>
<name>Paul Campbell</name>
<email>pcampbell@kemitix.net</email>
<organization>Kemitix</organization>
<organizationUrl>https://github.com/kemitix/</organizationUrl>
</developer>
</developers>
<licenses>
<license>
<name>MIT License</name>
<url>http://www.opensource.org/licenses/mit-license.php</url>
</license>
</licenses>
<inceptionYear>2015</inceptionYear> <inceptionYear>2015</inceptionYear>
<prerequisites>
<maven>3.0.4</maven>
</prerequisites>
<issueManagement> <issueManagement>
<url>https://github.com/kemitix/wiser-assertions/issues</url> <url>https://github.com/kemitix/wiser-assertions/issues</url>
<system>GitHub Issues</system> <system>GitHub Issues</system>
@ -46,14 +26,12 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties> </properties>
<parent> <parent>
<groupId>org.sonatype.oss</groupId> <groupId>net.kemitix</groupId>
<artifactId>oss-parent</artifactId> <artifactId>kemitix-spring-parent</artifactId>
<version>9</version> <version>1.2.1</version>
</parent> </parent>
<dependencies> <dependencies>
@ -67,59 +45,17 @@
<artifactId>subethasmtp</artifactId> <artifactId>subethasmtp</artifactId>
<version>3.1.7</version> <version>3.1.7</version>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
<type>jar</type>
</dependency>
</dependencies> </dependencies>
<profiles> </project>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<configuration>
<passphrase>${gpg.passphrase}</passphrase>
</configuration>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,61 +1,144 @@
package net.kemitix.wiser.assertions; 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.IOException;
import java.io.OutputStream;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeMultipart;
import org.subethamail.wiser.Wiser;
import org.subethamail.wiser.WiserMessage;
/** /**
* Taken from the WiserAssetions class from Rafal Browiec at * Provides a set of assertions for checking the status of any messages received
* http://blog.codeleak.pl/2014/09/testing-mail-code-in-spring-boot.html * by subethamail's Wiser.
*
* <pre>
* {@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); }
* }
* </pre>
*/ */
public class WiserAssertions { public final class WiserAssertions {
/**
* The messages received by Wiser.
*/
private final List<WiserMessage> messages; private final List<WiserMessage> 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()); return new WiserAssertions(wiser.getMessages());
} }
private WiserAssertions(List<WiserMessage> messages) { /**
this.messages = messages; * Private constructor.
*
* @param wiserMessages the messages to be tested by the assertions
*/
private WiserAssertions(final List<WiserMessage> wiserMessages) {
this.messages = wiserMessages;
} }
public WiserAssertions from(String from) { /**
findFirstOrElseThrow(m -> m.getEnvelopeSender().equals(from), * Checks that there was at least one email received that was sent from the
assertionError("No message from [{0}] found!", from)); * {@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; return this;
} }
public WiserAssertions to(String to) { /**
findFirstOrElseThrow(m -> m.getEnvelopeReceiver().equals(to), * Checks that there was at least one email received that was sent to the
assertionError("No message to [{0}] found!", to)); * {@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; return this;
} }
public WiserAssertions withSubject(String subject) { /**
Predicate<WiserMessage> 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<WiserMessage> predicate
= m -> subject.equals(unchecked(getMimeMessage(m)::getSubject));
findFirstOrElseThrow(predicate, findFirstOrElseThrow(predicate,
assertionError("No message with subject [{0}] found!", subject)); assertionError("No message with subject [{0}] found!",
subject));
return this; return this;
} }
public WiserAssertions withSubjectContains(String subject) { /**
Predicate<WiserMessage> 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<WiserMessage> predicate
= m -> unchecked(getMimeMessage(m)::getSubject)
.contains(subject);
findFirstOrElseThrow(predicate, findFirstOrElseThrow(predicate,
assertionError("No message with subject [{0}] found!", subject)); assertionError("No message with subject [{0}] found!",
subject));
return this; 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 -> { findFirstOrElseThrow(m -> {
ThrowingSupplier<String> contentAsString ThrowingSupplier<String> contentAsString
= () -> getMimeMessageBody(m).trim(); = () -> getMimeMessageBody(m).trim();
@ -64,21 +147,42 @@ public class WiserAssertions {
return this; 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(); StringBuilder messageContent = new StringBuilder();
findFirstOrElseThrow((WiserMessage m) -> { findFirstOrElseThrow((WiserMessage m) -> {
ThrowingSupplier<String> contentAsString ThrowingSupplier<String> contentAsString
= () -> getMimeMessageBody(m).trim(); = () -> getMimeMessageBody(m).trim();
messageContent.append(unchecked(contentAsString)); messageContent.append(unchecked(contentAsString));
return unchecked(contentAsString).contains(content); 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; 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) { if (content instanceof MimeMessage) {
return (String) content; return content.toString();
} }
if (content instanceof MimeMultipart) { if (content instanceof MimeMultipart) {
return getMimeMultipartAsString((MimeMultipart) content); return getMimeMultipartAsString((MimeMultipart) content);
@ -86,20 +190,63 @@ public class WiserAssertions {
throw new RuntimeException("Unexpected MimeMessage content"); throw new RuntimeException("Unexpected MimeMessage content");
} }
private void findFirstOrElseThrow(Predicate<WiserMessage> predicate, Supplier<AssertionError> 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<WiserMessage> predicate,
final Supplier<AssertionError> exceptionSupplier
) {
messages.stream().filter(predicate) messages.stream().filter(predicate)
.findFirst().orElseThrow(exceptionSupplier); .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); return unchecked(wiserMessage::getMimeMessage);
} }
private static Supplier<AssertionError> 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> assertionError(
final String errorMessage,
final Object... args
) {
return ()
-> new AssertionError(MessageFormat.format(errorMessage, args));
} }
public static <T> T unchecked(ThrowingSupplier<T> supplier) { /**
* Convert any checked Exceptions into unchecked Exceptions.
*
* @param <T> 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> T unchecked(final ThrowingSupplier<T> supplier) {
try { try {
return supplier.get(); return supplier.get();
} catch (Throwable e) { } catch (Throwable e) {
@ -107,14 +254,41 @@ public class WiserAssertions {
} }
} }
private String getMimeMultipartAsString(MimeMultipart mimeMultipart) throws MessagingException, IOException { /**
OutputStream os = new ByteArrayOutputStream(); * Converts a {@link MimeMultipart} into a {@link String} stripping out the
mimeMultipart.writeTo(os); * mime part boundary and headers..
return os.toString(); *
* @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 <T> the type of value to be supplied
*/
public interface ThrowingSupplier<T> { public interface ThrowingSupplier<T> {
/**
* Returns the value.
*
* @return the value
*
* @throws Throwable on error
*/
T get() throws Throwable; T get() throws Throwable;
} }
} }

View file

@ -0,0 +1,28 @@
/**
* Provides {@link WiserAssertions} to check for messages received by the Wiser
* SMTP test server from subethamail.
*
* <p>
* The MIT License.
* <p>
* Copyright 2015 pcampbell.
* <p>
* 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:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* 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;

View file

@ -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");
}
}