use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::email::{Email, EmailSender};
use crate::error::Result;
#[derive(Debug, Clone, Default)]
pub struct MockEmailSender {
sent: Arc<Mutex<Vec<SentEmail>>>,
}
#[derive(Debug, Clone)]
pub struct SentEmail {
pub to: Vec<String>,
pub subject: String,
pub text: Option<String>,
pub html: Option<String>,
}
impl MockEmailSender {
pub fn new() -> Self {
Self::default()
}
pub async fn sent(&self) -> Vec<SentEmail> {
self.sent.lock().await.clone()
}
pub async fn assert_sent_to(&self, address: &str) {
let sent = self.sent.lock().await;
let matching: Vec<_> = sent
.iter()
.filter(|e| e.to.contains(&address.to_string()))
.collect();
assert!(
matching.len() == 1,
"Expected 1 email to {address}, found {}",
matching.len()
);
}
pub async fn assert_none_sent(&self) {
let sent = self.sent.lock().await;
assert!(sent.is_empty(), "Expected no emails, found {}", sent.len());
}
}
impl EmailSender for MockEmailSender {
fn send<'a>(
&'a self,
email: &'a Email,
) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
Box::pin(async move {
self.sent.lock().await.push(SentEmail {
to: email.to.clone(),
subject: email.subject.clone(),
text: email.text.clone(),
html: email.html.clone(),
});
Ok(format!("mock-{}", uuid::Uuid::new_v4()))
})
}
}