//! Mock email transport for integration tests. //! //! Records all sent emails so tests can assert on recipients, subjects, and bodies //! without hitting any external service. use makenotwork::email::EmailTransport; use makenotwork::error::Result; use std::sync::Mutex; /// A sent email record. #[derive(Debug, Clone)] #[allow(dead_code)] pub struct SentEmail { pub to: String, pub subject: String, pub body: String, pub unsub_url: Option, pub stream: Option, } /// In-memory email transport that records all sent emails. pub struct MockEmailTransport { sent: Mutex>, } #[allow(dead_code)] impl MockEmailTransport { pub fn new() -> Self { MockEmailTransport { sent: Mutex::new(Vec::new()), } } /// Return all emails sent so far. pub fn sent(&self) -> Vec { self.sent.lock().unwrap().clone() } /// Return emails sent to a specific address. pub fn sent_to(&self, address: &str) -> Vec { self.sent.lock().unwrap() .iter() .filter(|e| e.to == address) .cloned() .collect() } /// Clear the sent email log. pub fn clear(&self) { self.sent.lock().unwrap().clear(); } /// Return the number of emails sent. pub fn count(&self) -> usize { self.sent.lock().unwrap().len() } } #[async_trait::async_trait] impl EmailTransport for MockEmailTransport { async fn send_email(&self, to: &str, subject: &str, body: &str) -> Result<()> { self.sent.lock().unwrap().push(SentEmail { to: to.to_string(), subject: subject.to_string(), body: body.to_string(), unsub_url: None, stream: None, }); Ok(()) } async fn send_email_with_unsub( &self, to: &str, subject: &str, body: &str, unsub_url: Option<&str>, ) -> Result<()> { self.sent.lock().unwrap().push(SentEmail { to: to.to_string(), subject: subject.to_string(), body: body.to_string(), unsub_url: unsub_url.map(|s| s.to_string()), stream: None, }); Ok(()) } async fn send_email_with_headers_and_unsub( &self, to: &str, subject: &str, body: &str, _extra_headers: &[(&str, String)], unsub_url: Option<&str>, ) -> Result<()> { self.sent.lock().unwrap().push(SentEmail { to: to.to_string(), subject: subject.to_string(), body: body.to_string(), unsub_url: unsub_url.map(|s| s.to_string()), stream: None, }); Ok(()) } async fn send_email_broadcast_with_unsub( &self, to: &str, subject: &str, body: &str, unsub_url: Option<&str>, ) -> Result<()> { self.sent.lock().unwrap().push(SentEmail { to: to.to_string(), subject: subject.to_string(), body: body.to_string(), unsub_url: unsub_url.map(|s| s.to_string()), stream: Some("broadcast".to_string()), }); Ok(()) } }