maik 0.2.0

A mock SMTP server library
Documentation
use crate::mail::Mail;
use regex::bytes::Regex;
use std::collections::HashSet;

/// An assertion object for testing the mails sent on a [MockServer](crate::server::MockServer).
/// See [MockServer::assert](crate::server::MockServer::assert) for calling the MailAssertion.
///
/// # Examples
///
/// Assert `user@domain.tld` sent something.
///
/// ```
/// let ma = maik::MailAssertion::new().sender_is("user@domain.tld");
/// ```
///
/// Assert `user@domain.tld` sent `Hi,\r\n\r\nBye!` to
/// at least `user1@domain.tld` and `user2@domain.tld`.
///
/// ```
/// let ma = maik::MailAssertion::new()
///     .sender_is("user@domain.tld")
///     .recipients_contain(["user1@domain.tld", "user2@domain.tld"])
///     .body_is("Hi,\r\n\r\nBye!");
/// ```
///
/// Assert `user@domain.tld` received an email ends with "verification code: \d".
///
/// ```
/// use regex::bytes::Regex;
///
/// let ma = maik::MailAssertion::new()
///     .recipients_are(["user@domain.tld"])
///     .body_matches(Regex::new(r"verification code: \d$").unwrap());
/// ```
#[derive(Default)]
pub struct MailAssertion {
    sender: Option<Vec<u8>>,
    recipients: Option<RecipientAssertion>,
    body: Option<BodyAssertion>,
}

enum RecipientAssertion {
    Exact(HashSet<Vec<u8>>),
    Contains(HashSet<Vec<u8>>),
}

enum BodyAssertion {
    Exact(Vec<u8>),
    Regex(Regex),
}

impl MailAssertion {
    /// Creates a MailAssertion object.
    pub fn new() -> Self {
        Default::default()
    }

    /// Asserts that the sender equals to the given user.
    pub fn sender_is<V: AsRef<[u8]>>(mut self, user: V) -> Self {
        self.sender = Some(user.as_ref().to_vec());
        self
    }

    /// Asserts that the recipients equal to the given users.
    pub fn recipients_are<I, S>(mut self, users: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: AsRef<[u8]>,
    {
        self.recipients = Some(RecipientAssertion::Exact(Self::convert_recipients(users)));
        self
    }

    /// Asserts that the recipients contain the given users.
    pub fn recipients_contain<I, S>(mut self, users: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: AsRef<[u8]>,
    {
        self.recipients = Some(RecipientAssertion::Contains(Self::convert_recipients(
            users,
        )));
        self
    }

    fn convert_recipients<I, S>(users: I) -> HashSet<Vec<u8>>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<[u8]>,
    {
        users.into_iter().map(|r| r.as_ref().to_vec()).collect()
    }

    /// Asserts that the body equals the given text.
    pub fn body_is<V: AsRef<[u8]>>(mut self, text: V) -> Self {
        self.body = Some(BodyAssertion::Exact(text.as_ref().to_vec()));
        self
    }

    /// Asserts that the body matches the given regular expression.
    pub fn body_matches(mut self, regex: Regex) -> Self {
        self.body = Some(BodyAssertion::Regex(regex));
        self
    }

    pub(crate) fn assert(&self, mails: &[Mail]) -> bool {
        mails.iter().any(|mail| {
            self.sender.as_ref().is_none_or(|s| &mail.sender == s)
                && self.recipients.as_ref().is_none_or(|rec| match rec {
                    RecipientAssertion::Exact(expected) => &mail.recipients == expected,
                    RecipientAssertion::Contains(needle) => mail.recipients.is_superset(needle),
                })
                && self.body.as_ref().is_none_or(|body| match body {
                    BodyAssertion::Exact(expected) => &mail.body == expected,
                    BodyAssertion::Regex(re) => re.is_match(&mail.body),
                })
        })
    }
}