Skip to main content

max / makenotwork

3.3 KB · 126 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(crate) 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(crate) struct MockEmailTransport {
23 sent: Mutex<Vec<SentEmail>>,
24 }
25
26 #[allow(dead_code)]
27 impl MockEmailTransport {
28 pub(crate) fn new() -> Self {
29 MockEmailTransport {
30 sent: Mutex::new(Vec::new()),
31 }
32 }
33
34 /// Return all emails sent so far.
35 pub(crate) fn sent(&self) -> Vec<SentEmail> {
36 self.sent.lock().unwrap().clone()
37 }
38
39 /// Return emails sent to a specific address.
40 pub(crate) fn sent_to(&self, address: &str) -> Vec<SentEmail> {
41 self.sent
42 .lock()
43 .unwrap()
44 .iter()
45 .filter(|e| e.to == address)
46 .cloned()
47 .collect()
48 }
49
50 /// Clear the sent email log.
51 pub(crate) fn clear(&self) {
52 self.sent.lock().unwrap().clear();
53 }
54
55 /// Return the number of emails sent.
56 pub(crate) fn count(&self) -> usize {
57 self.sent.lock().unwrap().len()
58 }
59 }
60
61 #[async_trait::async_trait]
62 impl EmailTransport for MockEmailTransport {
63 async fn send_email(&self, to: &str, subject: &str, body: &str) -> Result<()> {
64 self.sent.lock().unwrap().push(SentEmail {
65 to: to.to_string(),
66 subject: subject.to_string(),
67 body: body.to_string(),
68 unsub_url: None,
69 stream: None,
70 });
71 Ok(())
72 }
73
74 async fn send_email_with_unsub(
75 &self,
76 to: &str,
77 subject: &str,
78 body: &str,
79 unsub_url: Option<&str>,
80 ) -> Result<()> {
81 self.sent.lock().unwrap().push(SentEmail {
82 to: to.to_string(),
83 subject: subject.to_string(),
84 body: body.to_string(),
85 unsub_url: unsub_url.map(std::string::ToString::to_string),
86 stream: None,
87 });
88 Ok(())
89 }
90
91 async fn send_email_with_headers_and_unsub(
92 &self,
93 to: &str,
94 subject: &str,
95 body: &str,
96 _extra_headers: &[(&str, String)],
97 unsub_url: Option<&str>,
98 ) -> Result<()> {
99 self.sent.lock().unwrap().push(SentEmail {
100 to: to.to_string(),
101 subject: subject.to_string(),
102 body: body.to_string(),
103 unsub_url: unsub_url.map(std::string::ToString::to_string),
104 stream: None,
105 });
106 Ok(())
107 }
108
109 async fn send_email_broadcast_with_unsub(
110 &self,
111 to: &str,
112 subject: &str,
113 body: &str,
114 unsub_url: Option<&str>,
115 ) -> Result<()> {
116 self.sent.lock().unwrap().push(SentEmail {
117 to: to.to_string(),
118 subject: subject.to_string(),
119 body: body.to_string(),
120 unsub_url: unsub_url.map(std::string::ToString::to_string),
121 stream: Some("broadcast".to_string()),
122 });
123 Ok(())
124 }
125 }
126