better_auth_core/
email.rs1use async_trait::async_trait;
2
3use crate::error::AuthResult;
4
5#[async_trait]
8pub trait EmailProvider: Send + Sync {
9 async fn send(&self, to: &str, subject: &str, html: &str, text: &str) -> AuthResult<()>;
16}
17
18pub struct ConsoleEmailProvider;
22
23#[async_trait]
24impl EmailProvider for ConsoleEmailProvider {
25 async fn send(&self, to: &str, subject: &str, _html: &str, text: &str) -> AuthResult<()> {
26 eprintln!("[EMAIL] To: {to} | Subject: {subject} | Body: {text}");
27 Ok(())
28 }
29}
30
31#[cfg(test)]
32#[allow(clippy::type_complexity)]
33mod tests {
34 use super::*;
35 use std::sync::{Arc, Mutex};
36
37 struct MockEmailProvider {
39 sent: Arc<Mutex<Vec<(String, String, String, String)>>>,
40 }
41
42 impl MockEmailProvider {
43 fn new() -> (Self, Arc<Mutex<Vec<(String, String, String, String)>>>) {
44 let sent = Arc::new(Mutex::new(Vec::new()));
45 (Self { sent: sent.clone() }, sent)
46 }
47 }
48
49 #[async_trait]
50 impl EmailProvider for MockEmailProvider {
51 async fn send(&self, to: &str, subject: &str, html: &str, text: &str) -> AuthResult<()> {
52 self.sent.lock().unwrap().push((
53 to.to_string(),
54 subject.to_string(),
55 html.to_string(),
56 text.to_string(),
57 ));
58 Ok(())
59 }
60 }
61
62 #[tokio::test]
63 async fn test_console_email_provider_send() {
64 let provider = ConsoleEmailProvider;
65 let result = provider
66 .send("user@example.com", "Test Subject", "<h1>Hi</h1>", "Hi")
67 .await;
68 assert!(result.is_ok());
69 }
70
71 #[tokio::test]
72 async fn test_mock_email_provider_records_sends() {
73 let (provider, sent) = MockEmailProvider::new();
74 provider
75 .send("a@b.com", "Sub", "<p>html</p>", "text")
76 .await
77 .unwrap();
78 provider.send("c@d.com", "Sub2", "", "text2").await.unwrap();
79
80 let messages = sent.lock().unwrap();
81 assert_eq!(messages.len(), 2);
82 assert_eq!(messages[0].0, "a@b.com");
83 assert_eq!(messages[1].0, "c@d.com");
84 }
85
86 #[tokio::test]
87 async fn test_trait_object_works() {
88 let provider: Box<dyn EmailProvider> = Box::new(ConsoleEmailProvider);
89 let result = provider.send("user@example.com", "Test", "", "body").await;
90 assert!(result.is_ok());
91 }
92
93 #[tokio::test]
94 async fn test_missing_provider_returns_error() {
95 use crate::adapters::MemoryDatabaseAdapter;
96 use crate::config::AuthConfig;
97 use crate::plugin::AuthContext;
98
99 let config = Arc::new(AuthConfig::new("test-secret-key-at-least-32-chars-long"));
100 let database = Arc::new(MemoryDatabaseAdapter::new());
101 let ctx = AuthContext::new(config, database);
102
103 let result = ctx.email_provider();
104 assert!(result.is_err());
105 }
106}