FluentPartAssert.java

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

import static fr.sii.ogham.testing.assertion.util.AssertionHelper.assertThat;
import static fr.sii.ogham.testing.assertion.util.AssertionHelper.usingContext;
import static fr.sii.ogham.testing.assertion.util.EmailUtils.getContent;
import static java.util.Arrays.asList;
import static java.util.Collections.list;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.Part;

import org.hamcrest.Matcher;

import fr.sii.ogham.testing.assertion.util.AssertionRegistry;
import fr.sii.ogham.testing.util.HasParent;

public class FluentPartAssert<P> extends HasParent<P> {
	/**
	 * The list of messages that will be used for assertions
	 */
	private final List<PartWithContext> actual;
	/**
	 * Registry to register assertions
	 */
	private final AssertionRegistry registry;

	public FluentPartAssert(PartWithContext actual, P parent, AssertionRegistry registry) {
		this(Arrays.asList(actual), parent, registry);
	}

	public FluentPartAssert(List<PartWithContext> actual, P parent, AssertionRegistry registry) {
		super(parent);
		this.actual = actual;
		this.registry = registry;
	}

	/**
	 * Make assertions on the string content of a part (body, alternative or
	 * attachment) of the message(s). UTF-8 charset is used to decode body
	 * content.
	 * 
	 * <pre>
	 * .receivedMessages().message(0).body()
	 *    .contentAsString(is("foobar"))
	 * </pre>
	 * 
	 * Will check if the content of the body of the first message is exactly
	 * "foobar".
	 * 
	 * <pre>
	 * .receivedMessages().every().body()
	 *    .contentAsString(is("foobar"))
	 * </pre>
	 * 
	 * Will check if the content of the body of every message is exactly
	 * "foobar".
	 * 
	 * @param matcher
	 *            the assertion to apply on string content
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> contentAsString(Matcher<? super String> matcher) {
		return contentAsString(matcher, StandardCharsets.UTF_8);
	}

	/**
	 * Make assertions on the string content of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).body()
	 *    .contentAsString(is("foobar"), Charset.forName("UTF-8"))
	 * </pre>
	 * 
	 * Will check if the content of the body of the first message is exactly
	 * "foobar".
	 * 
	 * <pre>
	 * .receivedMessages().every().body()
	 *    .contentAsString(is("foobar"), Charset.forName("UTF-8"))
	 * </pre>
	 * 
	 * Will check if the content of the body of every message is exactly
	 * "foobar".
	 * 
	 * @param matcher
	 *            the assertion to apply on string content
	 * @param charset
	 *            the charset used to decode the content
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> contentAsString(Matcher<? super String> matcher, Charset charset) {
		try {
			String message = charset.name() + " content of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(part == null ? null : getContent(part, charset), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (Exception e) {
			throw new AssertionError("Failed to get string content for part", e);
		}
	}

	/**
	 * Make assertions on the raw content of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).body()
	 *    .content(is(resource("path/to/expected/file"))
	 * </pre>
	 * 
	 * Will check if the content of the body of the first message is exactly the
	 * same as the file resource available in the classpath.
	 * 
	 * <pre>
	 * .receivedMessages().every().body()
	 *    .content(is(resource("path/to/expected/file"))
	 * </pre>
	 * 
	 * Will check if the content of the body of every message is exactly the
	 * same as the file resource available in the classpath.
	 * 
	 * @param matcher
	 *            the assertion to apply on raw content
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> content(Matcher<byte[]> matcher) {
		try {
			String message = "raw content of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(part == null ? null : getContent(part), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (Exception e) {
			throw new AssertionError("Failed to get content for part", e);
		}
	}

	/**
	 * Make assertions on the content-type of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).body()
	 *    .contentType(is("text/html"))
	 * </pre>
	 * 
	 * Will check if the content-type of the body of the first message is
	 * exactly "text/html".
	 * 
	 * <pre>
	 * .receivedMessages().every().body()
	 *    .contentType(is("text/html"))
	 * </pre>
	 * 
	 * Will check if the content-type of the body of every message is exactly
	 * "text/html".
	 * 
	 * @param matcher
	 *            the assertion to apply on content-type
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> contentType(Matcher<? super String> matcher) {
		try {
			String message = "content-type of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(part == null ? null : part.getContentType(), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (MessagingException e) {
			throw new AssertionError("Failed to get string content type for part", e);
		}
	}

	/**
	 * Make assertions on the description of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).body()
	 *    .description(is("foo bar"))
	 * </pre>
	 * 
	 * Will check if the description of the body of the first message is exactly
	 * "foo bar".
	 * 
	 * <pre>
	 * .receivedMessages().every().body()
	 *    .description(is("foo bar"))
	 * </pre>
	 * 
	 * Will check if the description of the body of every message is exactly
	 * "foo bar".
	 * 
	 * @param matcher
	 *            the assertion to apply on description
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> description(Matcher<? super String> matcher) {
		try {
			String message = "description of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(part == null ? null : part.getDescription(), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (MessagingException e) {
			throw new AssertionError("Failed to get description of part", e);
		}
	}

	/**
	 * Make assertions on the disposition of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).body()
	 *    .disposition(is(INLINE_DISPOSITION))
	 * </pre>
	 * 
	 * Will check if the disposition of the body of the first message is exactly
	 * "inline".
	 * 
	 * <pre>
	 * .receivedMessages().every().body()
	 *    .disposition(is(INLINE_DISPOSITION))
	 * </pre>
	 * 
	 * Will check if the disposition of the body of every message is exactly
	 * "inline".
	 * 
	 * @param matcher
	 *            the assertion to apply on disposition
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> disposition(Matcher<? super String> matcher) {
		try {
			String message = "disposition of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(part == null ? null : part.getDisposition(), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (MessagingException e) {
			throw new AssertionError("Failed to get disposition of part", e);
		}
	}

	/**
	 * Make assertions on the filename of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).attachment(0)
	 *    .filename(is("foo.pdf"))
	 * </pre>
	 * 
	 * Will check if the filename of the first attachment of the first message
	 * is exactly "foo.pdf".
	 * 
	 * <pre>
	 * .receivedMessages().every().attachment(0)
	 *    .filename(is("foo.pdf"))
	 * </pre>
	 * 
	 * Will check if the filename of the first attachment of every message is
	 * exactly "foo.pdf".
	 * 
	 * @param matcher
	 *            the assertion to apply on filename
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> filename(Matcher<? super String> matcher) {
		try {
			String message = "filename of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(part == null ? null : part.getFileName(), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (MessagingException e) {
			throw new AssertionError("Failed to get filename of part", e);
		}
	}

	/**
	 * Make assertions on the headers of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).attachment(0)
	 *    .headers(hasItem(new Header("name", "value")))
	 * </pre>
	 * 
	 * Will check if the headers of the first attachment of the first message
	 * contains a header with name "name" and value "value".
	 * 
	 * <pre>
	 * .receivedMessages().every().attachment(0)
	 *    .header(hasItem(new Header("name", "value")))
	 * </pre>
	 * 
	 * Will check if the headers of the first attachment of every message is
	 * contains a header with name "name" and value "value".
	 * 
	 * @param matcher
	 *            the assertion to apply on headers
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> headers(Matcher<? super Iterable<Header>> matcher) {
		try {
			String message = "headers of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(part == null ? null : list(part.getAllHeaders()), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (MessagingException e) {
			throw new AssertionError("Failed to get headers of part", e);
		}
	}

	/**
	 * Make assertions on a single header of a part (body, alternative or
	 * attachment) of the message(s).
	 * 
	 * <pre>
	 * .receivedMessages().message(0).attachment(0)
	 *    .header("Content-ID", contains("foo"))
	 * </pre>
	 * 
	 * Will check if the "Content-ID" header of the first attachment of the
	 * first message values "foo".
	 * 
	 * <pre>
	 * .receivedMessages().every().attachment(0)
	 *    .header("Content-ID", contains("foo"))
	 * </pre>
	 * 
	 * Will check if the "Content-ID" header of the first attachment of every
	 * message values "foo".
	 * 
	 * @param headerName
	 *            the name of the header to check
	 * @param matcher
	 *            the assertion to apply on the header header
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPartAssert<P> header(String headerName, Matcher<? super Iterable<String>> matcher) {
		try {
			String message = "header " + headerName + " of ${partName} of message ${messageIndex}";
			for (PartWithContext partWithContext : actual) {
				Part part = partWithContext.getPart();
				registry.register(() -> assertThat(getHeaderValues(part, headerName), usingContext(message, partWithContext, matcher)));
			}
			return this;
		} catch (MessagingException e) {
			throw new AssertionError("Failed to get header" + headerName + " of part", e);
		}
	}
	
	@SuppressWarnings("squid:S1168")
	private static List<String> getHeaderValues(Part part, String headerName) throws MessagingException {
		if (part != null) {
			String[] vals = part.getHeader(headerName);
			if (vals != null) {
				return asList(vals);
			}
		}
		return null;
	}
}