Skip to main content

max / makenotwork

3.1 KB · 112 lines History Blame Raw
1 //! Mock email transport for integration tests.
2 //!
3 //! Records all sent emails so tests can assert on recipients, subjects, and bodies
4 //! without hitting any external service.
5
6 use makenotwork::email::EmailTransport;
7 use makenotwork::error::Result;
8 use std::sync::Mutex;
9
10 /// A sent email record.
11 #[derive(Debug, Clone)]
12 #[allow(dead_code)]
13 pub struct SentEmail {
14 pub to: String,
15 pub subject: String,
16 pub body: String,
17 pub unsub_url: Option<String>,
18 pub stream: Option<String>,
19 }
20
21 /// In-memory email transport that records all sent emails.
22 pub struct MockEmailTransport {
23 sent: Mutex<Vec<SentEmail>>,
24 }
25
26 #[allow(dead_code)]
27 impl MockEmailTransport {
28 pub fn new() -> Self {
29 MockEmailTransport {
30 sent: Mutex::new(Vec::new()),
31 }
32 }
33
34 /// Return all emails sent so far.
35 pub fn sent(&self) -> Vec<SentEmail> {
36 self.sent.lock().unwrap().clone()
37 }
38
39 /// Return emails sent to a specific address.
40 pub fn sent_to(&self, address: &str) -> Vec<SentEmail> {
41 self.sent.lock().unwrap()
42 .iter()
43 .filter(|e| e.to == address)
44 .cloned()
45 .collect()
46 }
47
48 /// Clear the sent email log.
49 pub fn clear(&self) {
50 self.sent.lock().unwrap().clear();
51 }
52
53 /// Return the number of emails sent.
54 pub fn count(&self) -> usize {
55 self.sent.lock().unwrap().len()
56 }
57 }
58
59 #[async_trait::async_trait]
60 impl EmailTransport for MockEmailTransport {
61 async fn send_email(&self, to: &str, subject: &str, body: &str) -> Result<()> {
62 self.sent.lock().unwrap().push(SentEmail {
63 to: to.to_string(),
64 subject: subject.to_string(),
65 body: body.to_string(),
66 unsub_url: None,
67 stream: None,
68 });
69 Ok(())
70 }
71
72 async fn send_email_with_unsub(
73 &self, to: &str, subject: &str, body: &str, unsub_url: Option<&str>,
74 ) -> Result<()> {
75 self.sent.lock().unwrap().push(SentEmail {
76 to: to.to_string(),
77 subject: subject.to_string(),
78 body: body.to_string(),
79 unsub_url: unsub_url.map(|s| s.to_string()),
80 stream: None,
81 });
82 Ok(())
83 }
84
85 async fn send_email_with_headers_and_unsub(
86 &self, to: &str, subject: &str, body: &str,
87 _extra_headers: &[(&str, String)], unsub_url: Option<&str>,
88 ) -> Result<()> {
89 self.sent.lock().unwrap().push(SentEmail {
90 to: to.to_string(),
91 subject: subject.to_string(),
92 body: body.to_string(),
93 unsub_url: unsub_url.map(|s| s.to_string()),
94 stream: None,
95 });
96 Ok(())
97 }
98
99 async fn send_email_broadcast_with_unsub(
100 &self, to: &str, subject: &str, body: &str, unsub_url: Option<&str>,
101 ) -> Result<()> {
102 self.sent.lock().unwrap().push(SentEmail {
103 to: to.to_string(),
104 subject: subject.to_string(),
105 body: body.to_string(),
106 unsub_url: unsub_url.map(|s| s.to_string()),
107 stream: Some("broadcast".to_string()),
108 });
109 Ok(())
110 }
111 }
112