All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.kemitix.wiser.assertions.WiserAssertions Maven / Gradle / Ivy

The newest version!
/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 Paul Campbell
 *
 * 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;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.kemitix.mon.maybe.Maybe;
import net.kemitix.mon.result.Result;
import org.subethamail.wiser.Wiser;
import org.subethamail.wiser.WiserMessage;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

/**
 * Provides a set of assertions for checking the status of any messages received
 * by subethamail's Wiser.
 * 
 * 
 * {@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);
 *  }
 * 
 * 
*/ @SuppressWarnings("methodcount") public final class WiserAssertions { private static final String ERROR_MESSAGE_SUBJECT = "No message with subject [{0}] found!"; private static final String ERROR_MESSAGE_CONTENT_CONTAINS = "No message with content containing [{0}] found!"; private static final String ERROR_MESSAGE_CONTENT = "No message with content [{0}] found!"; private static final String ERROR_MESSAGE_TO = "No message to [{0}] found!"; /** * The messages received by Wiser. */ private final List messages; /** * Private constructor. * * @param wiserMessages the messages to be tested by the assertions */ private WiserAssertions(final List wiserMessages) { this.messages = wiserMessages; } /** * 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()); } /** * 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) { messageMatches(m -> m.getEnvelopeSender().equals(sender)) .orElseThrow(assertionError("No message from [{0}] found!", sender)); return this; } private Optional messageMatches(final Predicate predicate) { return messages.stream() .filter(predicate) .findAny(); } /** * 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)); } /** * 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) { messageMatches(m -> m.getEnvelopeReceiver().equals(recipient)) .orElseThrow(assertionError(ERROR_MESSAGE_TO, recipient)); return this; } /** * 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) { messageMatches(m -> subject(m).equals(subject)) .orElseThrow(assertionError(ERROR_MESSAGE_SUBJECT, subject)); return this; } @SuppressFBWarnings("EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS") private String subject(final WiserMessage wiserMessage) { try { return wiserMessage.getMimeMessage().getSubject(); } catch (MessagingException e) { throw new IllegalArgumentException("Invalid email message", e); } } /** * 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) { messageMatches(m -> subject(m).contains(subject)) .orElseThrow(assertionError(ERROR_MESSAGE_SUBJECT, subject)); return this; } /** * 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) { messageMatches(m -> messageBody(m).trim().equals(content.trim())) .orElseThrow(assertionError(ERROR_MESSAGE_CONTENT, content)); return this; } @SuppressFBWarnings("EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS") private String messageBody(final WiserMessage m) { try { return messageBody(m.getMimeMessage().getContent()); } catch (IOException | MessagingException e) { throw new RuntimeException(e); } } private String messageBody(final Object content) { return contentAsString(content) .or(() -> contentAsMimeMessage(content)) .or(() -> contentAsMultiPartMime(content)) .orElseThrow(() -> new RuntimeException("Unexpected MimeMessage content")); } private Maybe contentAsString(final Object content) { if (content instanceof String) { return Maybe.just((String) content); } return Maybe.nothing(); } private Maybe contentAsMimeMessage(final Object content) { if (content instanceof MimeMessage) { return Maybe.just(content.toString()); } return Maybe.nothing(); } private Maybe contentAsMultiPartMime(final Object content) { if (content instanceof MimeMultipart) { return mimeMultipartAsString((MimeMultipart) content); } return Maybe.nothing(); } /** * 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) { messageMatches((WiserMessage m) -> messageBody(m).trim().contains(content)) .orElseThrow(assertionError(ERROR_MESSAGE_CONTENT_CONTAINS, content)); return this; } /** * 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 */ private Maybe mimeMultipartAsString(final MimeMultipart mimeMultipart) { return Result.toMaybe(Result.of(mimeMultipart::getCount) .map(count -> bodyPartsAsString(mimeMultipart, count))); } private String bodyPartsAsString(final MimeMultipart mimeMultipart, final int count) { return IntStream.range(0, count) .mapToObj(i -> bodyPart(mimeMultipart, i)) .map(this::bodyPartAsString) .map(Result::orElseThrowUnchecked) .collect(Collectors.joining()); } private Result bodyPartAsString(final Result bodyPart) { return bodyPartContent(bodyPart) .flatMap(this::contentObjectAsString); } private Result bodyPartContent(final Result bodyPart) { return bodyPart.flatMap(part -> Result.of(part::getContent)); } private Result bodyPart(final MimeMultipart mimeMultipart, final int i) { return Result.of(() -> mimeMultipart.getBodyPart(i)); } private Result contentObjectAsString(final Object content) { if (content instanceof MimeMultipart) { return Result.of(() -> mimeMultipartAsString((MimeMultipart) content).orElse("")); } else { return Result.ok((String) content); } } }