AssertEmail.java

package fr.sii.ogham.testing.assertion.email;

import static fr.sii.ogham.testing.assertion.util.EmailUtils.getBodyParts;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;

import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;

import org.junit.Assert;
import org.junit.ComparisonFailure;

import fr.sii.ogham.testing.assertion.html.AssertHtml;
import fr.sii.ogham.testing.assertion.util.AssertionRegistry;
import fr.sii.ogham.testing.assertion.util.EmailUtils;
import fr.sii.ogham.testing.assertion.util.Executable;
import fr.sii.ogham.testing.assertion.util.FailAtEndRegistry;

/**
 * Utility class for checking if the received email content is as expected.
 * 
 * @author Aurélien Baudet
 *
 */
@SuppressWarnings("squid:S1192")
public final class AssertEmail {
	private static final Pattern HTML_PATTERN = Pattern.compile("<html", Pattern.CASE_INSENSITIVE);
	private static final Pattern NEW_LINES = Pattern.compile("\r|\n");

	/**
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains several parts (several contents). The
	 * received email should also have the equivalent parts. It will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The received message is a {@link Multipart} email</li>
	 * <li>The number of parts are equal</li>
	 * <li>Each received part body equals the expected one (order is important).
	 * See {@link #assertBody(String, String, boolean)}</li>
	 * <li>Each received part Mime Type equals the expected one (order is
	 * important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body is done strictly (totally equal).
	 * </p>
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmail
	 *            the received email
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the content of the email fails
	 */
	public static void assertEquals(ExpectedMultiPartEmail expectedEmail, Message actualEmail) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertEquals(expectedEmail, actualEmail, true, assertions);
		assertions.execute();
	}

	/**
	 * <p>
	 * Shortcut to simplify unit testing with GreenMail. See
	 * {@link #assertEquals(ExpectedMultiPartEmail[], Message[])}.
	 * </p>
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains several parts (several contents). The
	 * received email should also have the equivalent parts. It will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The received message is a {@link Multipart} email</li>
	 * <li>The number of parts are equal</li>
	 * <li>Each received part body equals the expected one (order is important).
	 * See {@link #assertBody(String, String, boolean)}</li>
	 * <li>Each received part Mime Type equals the expected one (order is
	 * important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body is done strictly (totally equal).
	 * </p>
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmails
	 *            the received email
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the content of the email fails
	 */
	public static void assertEquals(ExpectedMultiPartEmail expectedEmail, Message[] actualEmails) throws MessagingException, IOException {
		assertEquals(new ExpectedMultiPartEmail[] { expectedEmail }, actualEmails);
	}

	/**
	 * Assert that each received email content respects the expected one. It
	 * ensures that the number of received emails equals to the expected number.
	 * Then for each email it calls
	 * {@link #assertEquals(ExpectedMultiPartEmail, Message)} .
	 * 
	 * @param expectedEmails
	 *            the list of expected emails
	 * @param actualEmails
	 *            the received emails
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the content of the email fails
	 */
	public static void assertEquals(ExpectedMultiPartEmail[] expectedEmails, Message[] actualEmails) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertions.register(() -> Assert.assertEquals("should have received " + expectedEmails.length + " email", expectedEmails.length, actualEmails.length));
		for (int i = 0; i < expectedEmails.length; i++) {
			assertEquals(expectedEmails[i], i < actualEmails.length ? actualEmails[i] : null, true, assertions);
		}
		assertions.execute();
	}

	/**
	 * <p>
	 * Shortcut to simplify unit testing with GreenMail. See
	 * {@link #assertEquals(ExpectedEmail[], Message[])}.
	 * </p>
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains only one part (only one content). It
	 * will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The body equals the expected one (order is important). See
	 * {@link #assertBody(String, String, boolean)}</li>
	 * <li>The Mime Type equals the expected one (order is important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body is done strictly (totally equal).
	 * </p>
	 * 
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmails
	 *            the received emails
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the email content fails
	 */
	public static void assertEquals(ExpectedEmail expectedEmail, Message[] actualEmails) throws MessagingException, IOException {
		assertEquals(new ExpectedEmail[] { expectedEmail }, actualEmails);
	}

	/**
	 * Assert that each received email content respects the expected one. It
	 * ensures that the number of received emails equals to the expected number.
	 * Then for each email it calls
	 * {@link #assertEquals(ExpectedEmail, Message)} .
	 * 
	 * @param expectedEmail
	 *            the expected email
	 * @param actualEmails
	 *            the received emails
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the email content fails
	 */
	public static void assertEquals(ExpectedEmail[] expectedEmail, Message[] actualEmails) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertions.register(() -> Assert.assertEquals("should have received " + expectedEmail.length + " email", expectedEmail.length, actualEmails.length));
		for (int i = 0; i < expectedEmail.length; i++) {
			assertEquals(expectedEmail[i], i < actualEmails.length ? actualEmails[i] : null, true, assertions);
		}
		assertions.execute();
	}

	/**
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains only one part (only one content). It
	 * will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The body equals the expected one (order is important). See
	 * {@link #assertBody(String, String, boolean)}</li>
	 * <li>The Mime Type equals the expected one (order is important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body is done strictly (totally equal).
	 * </p>
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmail
	 *            the received email
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the email content fails
	 */
	public static void assertEquals(ExpectedEmail expectedEmail, Message actualEmail) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertEquals(expectedEmail, actualEmail, true, assertions);
		assertions.execute();
	}

	/**
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains several parts (several contents). The
	 * received email should also have the equivalent parts. It will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The received message is a {@link Multipart} email</li>
	 * <li>The number of parts are equal</li>
	 * <li>Each received part body equals the expected one (order is important).
	 * See {@link #assertBody(String, String, boolean)}</li>
	 * <li>Each received part Mime Type equals the expected one (order is
	 * important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body ignores the new line characters.
	 * </p>
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmail
	 *            the received email
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the content of the email fails
	 */
	public static void assertSimilar(ExpectedMultiPartEmail expectedEmail, Message actualEmail) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertEquals(expectedEmail, actualEmail, false, assertions);
		assertions.execute();
	}

	/**
	 * <p>
	 * Shortcut to simplify unit testing with GreenMail. See
	 * {@link #assertSimilar(ExpectedMultiPartEmail[], Message[])}.
	 * </p>
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains several parts (several contents). The
	 * received email should also have the equivalent parts. It will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The received message is a {@link Multipart} email</li>
	 * <li>The number of parts are equal</li>
	 * <li>Each received part body equals the expected one (order is important).
	 * See {@link #assertBody(String, String, boolean)}</li>
	 * <li>Each received part Mime Type equals the expected one (order is
	 * important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body ignores the new line characters.
	 * </p>
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmails
	 *            the received email
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the content of the email fails
	 */
	public static void assertSimilar(ExpectedMultiPartEmail expectedEmail, Message[] actualEmails) throws MessagingException, IOException {
		assertSimilar(new ExpectedMultiPartEmail[] { expectedEmail }, actualEmails);
	}

	/**
	 * Assert that each received email content respects the expected one. It
	 * ensures that the number of received emails equals to the expected number.
	 * Then for each email it calls
	 * {@link #assertSimilar(ExpectedMultiPartEmail, Message)} .
	 * 
	 * @param expectedEmails
	 *            the list of expected emails
	 * @param actualEmails
	 *            the received emails
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the content of the email fails
	 */
	public static void assertSimilar(ExpectedMultiPartEmail[] expectedEmails, Message[] actualEmails) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertions.register(() -> Assert.assertEquals("should have received " + expectedEmails.length + " email", expectedEmails.length, actualEmails.length));
		for (int i = 0; i < expectedEmails.length; i++) {
			assertEquals(expectedEmails[i], i < actualEmails.length ? actualEmails[i] : null, false, assertions);
		}
		assertions.execute();
	}

	/**
	 * <p>
	 * Shortcut to simplify unit testing with GreenMail. See
	 * {@link #assertSimilar(ExpectedEmail[], Message[])}.
	 * </p>
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains only one part (only one content). It
	 * will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The body equals the expected one (order is important). See
	 * {@link #assertBody(String, String, boolean)}</li>
	 * <li>The Mime Type equals the expected one (order is important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body ignores the new line characters.
	 * </p>
	 * 
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmails
	 *            the received emails
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the email content fails
	 */
	public static void assertSimilar(ExpectedEmail expectedEmail, Message[] actualEmails) throws MessagingException, IOException {
		assertSimilar(new ExpectedEmail[] { expectedEmail }, actualEmails);
	}

	/**
	 * Assert that each received email content respects the expected one. It
	 * ensures that the number of received emails equals to the expected number.
	 * Then for each email it calls
	 * {@link #assertSimilar(ExpectedEmail, Message)} .
	 * 
	 * @param expectedEmail
	 *            the expected email
	 * @param actualEmails
	 *            the received emails
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the email content fails
	 */
	public static void assertSimilar(ExpectedEmail[] expectedEmail, Message[] actualEmails) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertions.register(() -> Assert.assertEquals("should have received " + expectedEmail.length + " email", expectedEmail.length, actualEmails.length));
		for (int i = 0; i < expectedEmail.length; i++) {
			assertEquals(expectedEmail[i], i < actualEmails.length ? actualEmails[i] : null, false, assertions);
		}
		assertions.execute();
	}

	/**
	 * Assert that the fields of the received email are equal to the expected
	 * values. The expected email contains only one part (only one content). It
	 * will check that:
	 * <ul>
	 * <li>The received headers are respected (see
	 * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li>
	 * <li>The body equals the expected one (order is important). See
	 * {@link #assertBody(String, String, boolean)}</li>
	 * <li>The Mime Type equals the expected one (order is important).</li>
	 * </ul>
	 * <p>
	 * The checking of the body ignores the new line characters.
	 * </p>
	 * 
	 * @param expectedEmail
	 *            all the fields with their expected values
	 * @param actualEmail
	 *            the received email
	 * @throws MessagingException
	 *             when accessing the received email fails
	 * @throws IOException
	 *             when reading the email content fails
	 */
	public static void assertSimilar(ExpectedEmail expectedEmail, Message actualEmail) throws MessagingException, IOException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertEquals(expectedEmail, actualEmail, false, assertions);
		assertions.execute();
	}

	/**
	 * Checks if the received body equals the expected body. It handles HTML
	 * content and pure text content.
	 * <p>
	 * For text, the check can be done either strictly (totally equal) or not
	 * (ignore new lines).
	 * </p>
	 * <p>
	 * For HTML, the string content is parsed and DOM trees are compared. The
	 * comparison can be done either strictly (DOM trees are totally equals,
	 * attributes are in the same order) or not (attributes can be in any
	 * order).
	 * </p>
	 * 
	 * @param expectedBody
	 *            the expected content as string
	 * @param actualBody
	 *            the received content as string
	 * @param strict
	 *            true for strict checking (totally equals) or false to ignore
	 *            new line characters
	 */
	public static void assertBody(String expectedBody, String actualBody, boolean strict) {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertBody("body", expectedBody, actualBody, strict, assertions);
		assertions.execute();
	}

	/**
	 * Checks if the received headers corresponds to the expected header values.
	 * It checks if:
	 * <ul>
	 * <li>The received subject equals the expected subject</li>
	 * <li>The email sender address equals the expected sender address</li>
	 * <li>The total number of recipients equals the total number of expected
	 * recipients</li>
	 * <li>The number of "to" recipients equals the number of expected "to"
	 * recipients</li>
	 * <li>The number of "cc" recipients equals the number of expected "cc"
	 * recipients</li>
	 * <li>The number of "bcc" recipients equals the number of expected "bcc"
	 * recipients</li>
	 * <li>Each "to" recipient equals the expected "to" recipient value</li>
	 * <li>Each "cc" recipient equals the expected "cc" recipient value</li>
	 * <li>Each "bcc" recipient equals the expected "bcc" recipient value</li>
	 * </ul>
	 * 
	 * @param expectedEmail
	 *            the expected header values
	 * @param actualEmail
	 *            the received header values
	 * @throws MessagingException
	 *             when accessing the received email fails
	 */
	public static void assertHeaders(ExpectedEmailHeader expectedEmail, Message actualEmail) throws MessagingException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertHeaders(expectedEmail, actualEmail, assertions);
		assertions.execute();
	}

	/**
	 * Checks if the received headers corresponds to the expected header values.
	 * It checks if:
	 * <ul>
	 * <li>The number of recipients of the provided type equals the number of
	 * expected recipients of the provided type</li>
	 * <li>Each recipient of the provided type equals the expected recipient
	 * value of the provided type</li>
	 * </ul>
	 * 
	 * @param expectedRecipients
	 *            the list of recipient string values
	 * @param actualEmail
	 *            the received header values
	 * @param recipientType
	 *            the type of the recipient to compare
	 * @throws MessagingException
	 *             when accessing the received email fails
	 */
	public static void assertRecipients(List<String> expectedRecipients, Message actualEmail, RecipientType recipientType) throws MessagingException {
		AssertionRegistry assertions = new FailAtEndRegistry();
		assertRecipients(expectedRecipients, actualEmail, recipientType, assertions);
		assertions.execute();
	}

	private static void assertEquals(ExpectedEmail expectedEmail, Message actualEmail, boolean strict, AssertionRegistry assertions) throws MessagingException, IOException {
		assertHeaders(expectedEmail, actualEmail, assertions);
		assertions.register(() -> Assert.assertNotNull("Expected at least one body part but none found", getBodyOrNull(actualEmail, assertions)));
		assertBody("body", expectedEmail.getExpectedContent().getBody(), getBodyContentOrNull(actualEmail, assertions), strict, assertions);
		assertMimetype(expectedEmail.getExpectedContent(), getBodyMimetypeOrNull(actualEmail, assertions), assertions);
	}

	private static void assertEquals(ExpectedMultiPartEmail expectedEmail, Message actualEmail, boolean strict, AssertionRegistry assertions) throws MessagingException, IOException {
		assertHeaders(expectedEmail, actualEmail, assertions);
		Object content = actualEmail == null ? null : actualEmail.getContent();
		assertions.register(() -> Assert.assertTrue("should be multipart message", content instanceof Multipart));
		List<Part> bodyParts = actualEmail == null ? emptyList() : getBodyParts(actualEmail);
		assertions.register(() -> Assert.assertEquals("should have " + expectedEmail.getExpectedContents().size() + " parts", expectedEmail.getExpectedContents().size(), bodyParts.size()));
		for (int i = 0; i < expectedEmail.getExpectedContents().size(); i++) {
			Part part = i < bodyParts.size() ? bodyParts.get(i) : null;
			assertBody("body[" + i + "]", expectedEmail.getExpectedContents().get(i).getBody(), part == null || part.getContent() == null ? null : part.getContent().toString(), strict, assertions);
			assertMimetype(expectedEmail.getExpectedContents().get(i), part == null ? null : part.getContentType(), assertions);
		}
	}

	private static void assertMimetype(ExpectedContent expectedContent, String contentType, AssertionRegistry assertions) {
		assertions.register(() -> Assert.assertTrue("mimetype should match " + expectedContent.getMimetype() + " instead of " + contentType,
				contentType != null && expectedContent.getMimetype().matcher(contentType).matches()));
	}

	private static void assertBody(String name, String expectedBody, String actualBody, boolean strict, AssertionRegistry assertions) {
		if (isHtml(expectedBody)) {
			if (strict) {
				assertions.register(() -> AssertHtml.assertEquals(expectedBody, actualBody));
			} else {
				assertions.register(() -> AssertHtml.assertSimilar(expectedBody, actualBody));
			}
		} else {
			assertions.register(() -> compareText(name, expectedBody, actualBody, strict));
		}
	}

	private static void compareText(String name, String expectedBody, String actualBody, boolean strict) throws ComparisonFailure {
		if (expectedBody == null && actualBody != null) {
			throw new ComparisonFailure(name + " should be null", expectedBody, actualBody);
		}
		if (expectedBody == null) {
			return;
		}
		if (strict ? !expectedBody.equals(actualBody) : !sanitize(expectedBody).equals(sanitize(actualBody))) {
			throw new ComparisonFailure(name + " should be '" + expectedBody + "'", expectedBody, actualBody);
		}
	}

	private static void assertHeaders(ExpectedEmailHeader expectedEmail, Message actualEmail, AssertionRegistry assertions) throws MessagingException {
		Address[] from = actualEmail == null || actualEmail.getFrom() == null ? null : actualEmail.getFrom();
		assertions.register(() -> Assert.assertEquals("subject should be '" + expectedEmail.getSubject() + "'", expectedEmail.getSubject(), actualEmail == null ? null : actualEmail.getSubject()));
		assertions.register(() -> Assert.assertEquals("should have only one from", (Integer) 1, from == null ? null : from.length));
		assertions.register(() -> Assert.assertEquals("from should be '" + expectedEmail.getFrom() + "'", expectedEmail.getFrom(), from == null ? null : from[0].toString()));
		int recipients = expectedEmail.getTo().size() + expectedEmail.getBcc().size() + expectedEmail.getCc().size();
		assertions.register(() -> Assert.assertEquals("should be received by " + recipients + " recipients", (Integer) recipients,
				actualEmail == null || actualEmail.getAllRecipients() == null ? null : actualEmail.getAllRecipients().length));
		assertRecipients(expectedEmail.getTo(), actualEmail, RecipientType.TO, assertions);
		assertRecipients(expectedEmail.getCc(), actualEmail, RecipientType.CC, assertions);
		assertRecipients(expectedEmail.getBcc(), actualEmail, RecipientType.BCC, assertions);
	}

	private static void assertRecipients(List<String> expectedRecipients, Message actualEmail, RecipientType recipientType, AssertionRegistry assertions) throws MessagingException {
		Address[] actualRecipients = actualEmail == null ? null : actualEmail.getRecipients(recipientType);
		if (expectedRecipients.isEmpty()) {
			assertions.register(() -> Assert.assertTrue("should be received by no recipients (of type RecipientType." + recipientType + ")", actualRecipients == null || actualRecipients.length == 0));
		} else {
			assertions.register(() -> Assert.assertEquals("should be received by " + expectedRecipients.size() + " recipients (of type RecipientType." + recipientType + ")",
					(Integer) expectedRecipients.size(), actualRecipients == null ? null : actualRecipients.length));
			for (int i = 0; i < expectedRecipients.size(); i++) {
				final int idx = i;
				assertions.register(() -> Assert.assertEquals("recipient " + recipientType + "[" + idx + "] should be '" + expectedRecipients.get(idx) + "'", expectedRecipients.get(idx),
						actualRecipients != null && idx < actualRecipients.length ? actualRecipients[idx].toString() : null));
			}
		}
	}

	/**
	 * Remove new lines from the string.
	 * 
	 * @param str
	 *            the string to sanitize
	 * @return the sanitized string
	 */
	private static String sanitize(String str) {
		if (str == null) {
			return null;
		}
		return NEW_LINES.matcher(str).replaceAll("");
	}

	@SuppressWarnings("squid:S2147") // false positive: merging exception
										// doesn't compile in that case or we
										// are force to throw Exception instead
										// of MessagingException
	private static Part getBodyOrNull(Part actualEmail, AssertionRegistry registry) throws MessagingException {
		try {
			if (actualEmail == null) {
				return null;
			}
			return EmailUtils.getBodyPart(actualEmail);
		} catch (MessagingException e) {
			registry.register(failure(e));
			return null;
		} catch (IllegalStateException e) {
			registry.register(failure(e));
			return null;
		}
	}

	@SuppressWarnings("squid:S2147") // false positive: merging exception
										// doesn't compile in that case or we
										// are force to throw Exception instead
										// of MessagingException
	private static String getBodyContentOrNull(Part actualEmail, AssertionRegistry registry) throws MessagingException, IOException {
		try {
			Part bodyPart = getBodyOrNull(actualEmail, registry);
			if (bodyPart == null) {
				return null;
			}
			return EmailUtils.getContent(bodyPart, UTF_8);
		} catch (MessagingException e) {
			registry.register(failure(e));
			return null;
		} catch (IllegalStateException e) {
			registry.register(failure(e));
			return null;
		} catch (IOException e) {
			registry.register(failure(e));
			return null;
		}
	}

	private static <E extends Exception> Executable<E> failure(E exception) {
		return () -> {
			throw exception;
		};
	}

	@SuppressWarnings("squid:S2147") // false positive: merging exception
										// doesn't compile in that case or we
										// are force to throw Exception instead
										// of MessagingException
	private static String getBodyMimetypeOrNull(Part actualEmail, AssertionRegistry registry) throws MessagingException {
		try {
			Part bodyPart = getBodyOrNull(actualEmail, registry);
			if (bodyPart == null) {
				return null;
			}
			return bodyPart.getContentType();
		} catch (MessagingException e) {
			registry.register(failure(e));
			return null;
		} catch (IllegalStateException e) {
			registry.register(failure(e));
			return null;
		}
	}

	private static boolean isHtml(String expectedBody) {
		return HTML_PATTERN.matcher(expectedBody).find();
	}

	private AssertEmail() {
		super();
	}
}