package fr.sii.ogham.assertion.email;
import static fr.sii.ogham.assertion.AssertionHelper.assertThat;
import static fr.sii.ogham.assertion.OghamAssertions.usingContext;
import static fr.sii.ogham.helper.email.EmailUtils.getAlternativePart;
import static fr.sii.ogham.helper.email.EmailUtils.getAttachments;
import static fr.sii.ogham.helper.email.EmailUtils.getBodyPart;
import static java.util.Arrays.asList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage.RecipientType;
import org.hamcrest.Matcher;
import org.junit.Assert;
import com.google.common.base.Predicate;
import fr.sii.ogham.assertion.HasParent;
import fr.sii.ogham.assertion.context.SingleMessageContext;
import fr.sii.ogham.helper.email.AnyPredicate;
import fr.sii.ogham.helper.email.FileNamePredicate;
public class EmailAssert<P> extends HasParent<P> {
/**
* The list of messages that will be used for assertions
*/
private final List<? extends Message> actual;
public EmailAssert(Message actual, P parent) {
this(Arrays.asList(actual), parent);
}
public EmailAssert(List<? extends Message> actual, P parent) {
super(parent);
this.actual = actual;
}
/**
* Make assertions on the subject of the message(s).
*
* <pre>
* .receivedMessages().message(0)
* .subject(is("foobar"))
* </pre>
*
* Will check if the subject of the first message is exactly "foobar".
*
* <pre>
* .receivedMessages().forEach()
* .subject(is("foobar"))
* </pre>
*
* Will check that the subject of every message is exactly "foobar".
*
* @param matcher
* the assertion to apply on subject
* @return the fluent API for chaining assertions on received message(s)
*/
public EmailAssert<P> subject(Matcher<String> matcher) {
try {
String desc = "subject of message ${messageIndex}";
int index = 0;
for (Message message : actual) {
assertThat(message.getSubject(), usingContext(desc, new SingleMessageContext(index++), matcher));
}
return this;
} catch (MessagingException e) {
throw new AssertionError("Failed to get subject of messsage", e);
}
}
/**
* Make assertions on the body of the message(s) using fluent API. The body
* of the message is either:
* <ul>
* <li>The part of the message when it contains only one part</li>
* <li>The part with text or HTML mimetype if only one part with one of that
* mimetype</li>
* <li>The second part with text or HTML mimetype if there are two text or
* HTML parts</li>
* </ul>
*
* <pre>
* .receivedMessages().message(0).body()
* .contentAsString(is("email body"))
* .contentType(is("text/plain"))
* </pre>
*
* Will check if the body content of the first message is "email body" and
* content-type of the first message is "text/plain".
*
* <pre>
* .receivedMessages().forEach().body()
* .contentAsString(is("email body"))
* .contentType(is("text/plain"))
* </pre>
*
* Will check that the body content of every message is "email body" and
* content-type of every message is "text/plain".
*
* @return the fluent API for chaining assertions on received message(s)
*/
public PartAssert<EmailAssert<P>> body() {
try {
int index = 0;
List<PartWithContext> bodies = new ArrayList<>();
for (Message message : actual) {
bodies.add(new PartWithContext(getBodyPart(message), "body", new SingleMessageContext(index++)));
}
return new PartAssert<>(bodies, this);
} catch (MessagingException e) {
throw new AssertionError("Failed to get body of messsage", e);
}
}
/**
* Make assertions on the alternative part of the message(s) using fluent
* API. The alternative is useful when sending HTML email that may be
* unreadable on some email clients. For example, a smartphone will display
* the 2 or 3 first lines as a summary. Many smartphones will take the HTML
* message as-is and will display HTML tags instead of content of email.
* Alternative is used to provide a textual visualization of the message
* that will be readable by any system.
*
* <p>
* The alternative of the message is either:
* <ul>
* <li>null if there is only one part</li>
* <li>null if there is only one text or HTML part</li>
* <li>the first part if there are more than one text or HTML part</li>
* </ul>
*
* <pre>
* .receivedMessages().message(0).alternative()
* .contentAsString(is("email alternative"))
* .contentType(is("text/plain"))
* </pre>
*
* Will check if the body content of the first message is "email
* alternative" and content-type of the first message is "text/plain".
*
* <pre>
* .receivedMessages().forEach().alternative()
* .contentAsString(is("email alternative"))
* .contentType(is("text/plain"))
* </pre>
*
* Will check that the body content of every message is "email alternative"
* and content-type of every message is "text/plain".
*
* @return the fluent API for chaining assertions on received message(s)
*/
public PartAssert<EmailAssert<P>> alternative() {
try {
int index = 0;
List<PartWithContext> bodies = new ArrayList<>();
for (Message message : actual) {
bodies.add(new PartWithContext(getAlternativePart(message), "alternative", new SingleMessageContext(index++)));
}
return new PartAssert<>(bodies, this);
} catch (MessagingException e) {
throw new AssertionError("Failed to get body of messsage", e);
}
}
/**
* Make assertions on the body of the message(s). The body of the message is
* either:
* <ul>
* <li>The part of the message when it contains only one part</li>
* <li>The part with text or HTML mimetype if only one part with one of that
* mimetype</li>
* <li>The second part with text or HTML mimetype if there are two text or
* HTML parts</li>
* </ul>
*
* <pre>
* .receivedMessages().message(0)
* .body(allOf(notNullValue(), instanceOf(MimeBodyPart.class))
* </pre>
*
* Will check if the body of the first message is not null and is a
* MimeBodyPart instance.
*
* <pre>
* .receivedMessages().forEach()
* .body(allOf(notNullValue(), instanceOf(MimeBodyPart.class))
* </pre>
*
* Will check that the body of every message is not null and is a
* MimeBodyPart instance.
*
* <p>
* You can use the {@link #body()} variant to make more powerful assertions.
*
* @param matcher
* the assertion to apply on body
* @param <T>
* the type used for the matcher
* @return the fluent API for chaining assertions on received message(s)
*/
public <T extends Part> EmailAssert<P> body(Matcher<? super Part> matcher) {
try {
String desc = "body of message ${messageIndex}";
int index = 0;
for (Message message : actual) {
assertThat(getBodyPart(message), usingContext(desc, new SingleMessageContext(index++), matcher));
}
return this;
} catch (MessagingException e) {
throw new AssertionError("Failed to access attachments of messsage", e);
}
}
/**
* Make assertions on the alternative part of the message(s). The
* alternative is useful when sending HTML email that may be unreadable on
* some email clients. For example, a smartphone will display the 2 or 3
* first lines as a summary. Many smartphones will take the HTML message
* as-is and will display HTML tags instead of content of email. Alternative
* is used to provide a textual visualization of the message that will be
* readable by any system.
*
* <p>
* The alternative of the message is either:
* <ul>
* <li>null if there is only one part</li>
* <li>null if there is only one text or HTML part</li>
* <li>the first part if there are more than one text or HTML part</li>
* </ul>
*
* <pre>
* .receivedMessages().message(0)
* .alternative(allOf(notNullValue(), instanceOf(MimeBodyPart.class))
* </pre>
*
* Will check if the alternative of the first message is not null and is a
* MimeBodyPart instance.
*
* <pre>
* .receivedMessages().forEach()
* .alternative(allOf(notNullValue(), instanceOf(MimeBodyPart.class))
* </pre>
*
* Will check that the alternative of every message is not null and is a
* MimeBodyPart instance.
*
* <p>
* You can use the {@link #alternative()} variant to make more powerful
* assertions.
*
* @param matcher
* the assertion to apply on alternative
* @param <T>
* the type used for the matcher
* @return the fluent API for chaining assertions on received message(s)
*/
public <T extends Part> EmailAssert<P> alternative(Matcher<? super Part> matcher) {
try {
String desc = "alternative of message ${messageIndex}";
int index = 0;
for (Message message : actual) {
assertThat(getAlternativePart(message), usingContext(desc, new SingleMessageContext(index++), matcher));
}
return this;
} catch (MessagingException e) {
throw new AssertionError("Failed to access attachments of messsage", e);
}
}
/**
* Make assertions on the sender of the message(s) using fluent API.
*
* <pre>
* .receivedMessages().message(0).from()
* .address(hasItems("noreply@sii.fr"))
* .personal(hasItems("Groupe SII"))
* </pre>
*
* Will check if the sender email address of the first message is exactly
* "noreply@sii.fr" and sender displayed address of the first message is
* exactly "Groupe SII".
*
* <pre>
* .receivedMessages().forEach().from()
* .address(hasItems("noreply@sii.fr"))
* .personal(hasItems("Groupe SII"))
* </pre>
*
* Will check if the sender email address of every message is exactly
* "noreply@sii.fr" and sender displayed address of every message is exactly
* "Groupe SII".
*
* @return the fluent API for chaining assertions on received message(s)
*/
public AddressListAssert<EmailAssert<P>> from() {
try {
int index = 0;
List<AddressesWithContext> addresses = new ArrayList<>();
for (Message message : actual) {
addresses.add(new AddressesWithContext(asList((InternetAddress[]) message.getFrom()), "from", new SingleMessageContext(index++)));
}
return new AddressListAssert<>(addresses, this);
} catch (MessagingException e) {
throw new AssertionError("Failed to get from field of messsage", e);
}
}
/**
* Make assertions on the sender of the message(s) using fluent API.
*
* <pre>
* .receivedMessages().message(0).to()
* .address(hasItems("recipient1@sii.fr", "recipient2@sii.fr"))
* .personal(hasItems("Foo", "Bar"))
* </pre>
*
* Will check if the list of email addresses of direct recipients (TO) of
* the first message are exactly "recipient1@sii.fr", "recipient2@sii.fr"
* and the list of displayed address of direct recipients (TO) of the first
* message are exactly "Foo", "Bar".
*
* <pre>
* .receivedMessages().forEach().to()
* .address(hasItems("recipient1@sii.fr", "recipient2@sii.fr"))
* .personal(hasItems("Foo", "Bar"))
* </pre>
*
* Will check if the list of email addresses of direct recipients (TO) of
* every message are exactly "recipient1@sii.fr", "recipient2@sii.fr" and
* the list of displayed address of direct recipients (TO) of every message
* are exactly "Foo", "Bar".
*
* @return the fluent API for chaining assertions on received message(s)
*/
public AddressListAssert<EmailAssert<P>> to() {
try {
int index = 0;
List<AddressesWithContext> addresses = new ArrayList<>();
for (Message message : actual) {
addresses.add(new AddressesWithContext(asList((InternetAddress[]) message.getRecipients(RecipientType.TO)), "to", new SingleMessageContext(index++)));
}
return new AddressListAssert<>(addresses, this);
} catch (MessagingException e) {
throw new AssertionError("Failed to get to field of messsage", e);
}
}
/**
* Make assertions on the sender of the message(s) using fluent API.
*
* <pre>
* .receivedMessages().message(0).cc()
* .address(hasItems("recipient1@sii.fr", "recipient2@sii.fr"))
* .personal(hasItems("Foo", "Bar"))
* </pre>
*
* Will check if the list of email addresses of copy recipients (CC) of the
* first message are exactly "recipient1@sii.fr", "recipient2@sii.fr" and
* the list of displayed address of copy recipients (CC) of the first
* message are exactly "Foo", "Bar".
*
* <pre>
* .receivedMessages().forEach().cc()
* .address(hasItems("recipient1@sii.fr", "recipient2@sii.fr"))
* .personal(hasItems("Foo", "Bar"))
* </pre>
*
* Will check if the list of email addresses of copy recipients (CC) of
* every message are exactly "recipient1@sii.fr", "recipient2@sii.fr" and
* the list of displayed address of copy recipients (CC) of every message
* are exactly "Foo", "Bar".
*
* @return the fluent API for chaining assertions on received message(s)
*/
public AddressListAssert<EmailAssert<P>> cc() {
try {
int index = 0;
List<AddressesWithContext> addresses = new ArrayList<>();
for (Message message : actual) {
addresses.add(new AddressesWithContext(asList((InternetAddress[]) message.getRecipients(RecipientType.CC)), "cc", new SingleMessageContext(index++)));
}
return new AddressListAssert<>(addresses, this);
} catch (MessagingException e) {
throw new AssertionError("Failed to get cc field of messsage", e);
}
}
/**
* Make assertions on the list of attachments of the message(s).
*
* <pre>
* .receivedMessages().message(0)
* .attachments(hasSize(1))
* </pre>
*
* Will check if the number of attachments of the first message is exactly
* 1.
*
* <pre>
* .receivedMessages().forEach()
* .attachments(hasSize(1))
* </pre>
*
* Will check that the number of attachments of every message is exactly 1.
*
* <p>
* You can use the {@link #attachment(String)} or
* {@link #attachments(Predicate)} variants to make more powerful assertions
* on a particular attachment.
*
* @param matcher
* the assertion to apply on list of attachments
* @param <T>
* the type used for the matcher
* @return the fluent API for chaining assertions on received message(s)
*/
public <T extends Collection<? extends BodyPart>> EmailAssert<P> attachments(Matcher<? super Collection<? extends BodyPart>> matcher) {
try {
String desc = "attachments of message ${messageIndex}";
int index = 0;
for (Message message : actual) {
Object content = message.getContent();
Assert.assertTrue("should be multipart message", content instanceof Multipart);
assertThat(getAttachments((Multipart) content), usingContext(desc, new SingleMessageContext(index++), matcher));
}
return this;
} catch (MessagingException | IOException e) {
throw new AssertionError("Failed to access attachments of messsage", e);
}
}
/**
* Make assertions on a particular attachment of the message(s) using fluent
* API. The attachment is identified by its filename.
*
* <pre>
* .receivedMessages().message(0).attachment("foo.pdf")
* .contentType(is("application/pdf"))
* </pre>
*
* Will check if the content-type of the attachment named "foo.pdf" of the
* first message is exactly "application/pdf".
*
* <pre>
* .receivedMessages().forEach().attachment("foo.pdf")
* .contentType(is("application/pdf"))
* </pre>
*
* Will check that the content-type of attachment named "foo.pdf" of every
* message is exactly "application/pdf".
*
* <p>
* This is a shortcut to {@link #attachments(Predicate)} with
* {@link FileNamePredicate};
*
* @param filename
* the name of the attachment to make assertions on it
* @return the fluent API for chaining assertions on received message(s)
*/
public PartAssert<EmailAssert<P>> attachment(String filename) {
return attachments(new FileNamePredicate(filename));
}
/**
* Make assertions on a particular attachment of the message(s).
*
* <pre>
* .receivedMessages().message(0).attachment(0)
* .contentType(is("application/pdf"))
* </pre>
*
* Will check if the content-type of the first attachment of the first
* message is exactly "application/pdf".
*
* <pre>
* .receivedMessages().forEach().attachment(0)
* .contentType(is("application/pdf"))
* </pre>
*
* Will check if the content-type of the first attachment of every message
* is exactly "application/pdf".
*
* @param index
* the index of the attachment
* @return the fluent API for chaining assertions on received message(s)
*/
public PartAssert<EmailAssert<P>> attachment(int index) {
try {
int msgIndex = 0;
List<PartWithContext> attachments = new ArrayList<>();
for (Message message : actual) {
Object content = message.getContent();
Assert.assertTrue("should be multipart message", content instanceof Multipart);
List<BodyPart> found = getAttachments((Multipart) content, new AnyPredicate<BodyPart>());
BodyPart attachment = index >= found.size() ? null : found.get(index);
attachments.add(new PartWithContext(attachment, "attachment with index " + index, new SingleMessageContext(msgIndex++)));
}
return new PartAssert<>(attachments, this);
} catch (MessagingException | IOException e) {
throw new AssertionError("Failed to get attachment with index " + index + " of messsage", e);
}
}
/**
* Make assertions on a one or several attachments of the message(s) using
* fluent API. The attachments are identified using provided predicate.
*
* <pre>
* .receivedMessages().message(0)
* .attachments(new PdfFilter()).filename(endsWith(".pdf"))
* </pre>
*
* Will check if the name of every PDF attachments of the first message are
* named "foo.pdf".
*
* <pre>
* .receivedMessages().forEach()
* .attachments(new PdfFilter()).filename(endsWith(".pdf"))
* </pre>
*
* Will check if the name of every PDF attachments of every message are
* named "foo.pdf".
*
*
* @param filter
* the filter used to find attachments
* @return the fluent API for chaining assertions on received message(s)
*/
public PartAssert<EmailAssert<P>> attachments(Predicate<BodyPart> filter) {
try {
int index = 0;
List<PartWithContext> attachments = new ArrayList<>();
for (Message message : actual) {
Object content = message.getContent();
Assert.assertTrue("should be multipart message", content instanceof Multipart);
int attachmentIdx = 0;
for (BodyPart attachment : getAttachments((Multipart) content, filter)) {
attachments.add(new PartWithContext(attachment, "attachment " + filter + "(" + attachmentIdx + ")", new SingleMessageContext(index)));
attachmentIdx++;
}
index++;
}
return new PartAssert<>(attachments, this);
} catch (MessagingException | IOException e) {
throw new AssertionError("Failed to get attachment " + filter + " of messsage", e);
}
}
}