Skip to main content

br_email/
lib.rs

1use crate::analyze::AnalyzeEmails;
2#[cfg(feature = "imap")]
3use crate::imap::Imap;
4#[cfg(feature = "pop3")]
5use crate::pop3::Pop3;
6#[cfg(feature = "smtp")]
7use crate::smtp::Smtp;
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10use std::io::Error;
11use std::path::PathBuf;
12use std::{fs, io};
13
14#[cfg(feature = "imap")]
15mod imap;
16#[cfg(feature = "pop3")]
17mod pop3;
18#[cfg(feature = "smtp")]
19mod smtp;
20
21pub mod analyze;
22pub mod pools;
23
24#[derive(Clone, Debug)]
25pub struct Mail {
26    /// 协议
27    protocol: Protocol,
28    /// 邮件地址
29    host: String,
30    /// 邮件端口
31    port: u16,
32    /// 邮件名称
33    name: String,
34    /// 邮件账号
35    mail: String,
36    /// 邮件密码
37    pass: String,
38    /// 邮件类型
39    email_type: EmailType,
40}
41
42impl Mail {
43    pub fn new_new(config: Config) -> Result<Self, String> {
44        let default = config.default.clone();
45        let connection = config
46            .connections
47            .get(&default)
48            .ok_or_else(|| format!("默认配置不存在: {default}"))?
49            .clone();
50        Ok(Self {
51            protocol: connection.protocol,
52            host: connection.host,
53            port: connection.port,
54            name: connection.name,
55            mail: connection.mail,
56            pass: connection.pass,
57            email_type: connection.email_type,
58        })
59    }
60    pub fn new(protocol: &str, host: &str, port: u16, name: &str, mail: &str, pass: &str) -> Self {
61        Self {
62            protocol: Protocol::from(protocol),
63            host: host.to_string(),
64            port,
65            name: name.to_string(),
66            mail: mail.to_string(),
67            pass: pass.to_string(),
68            email_type: EmailType::from(mail),
69        }
70    }
71    /// 解析邮件
72    pub fn analyze(data: Vec<u8>, debug: bool) -> io::Result<AnalyzeEmails> {
73        AnalyzeEmails::new(data, debug)
74    }
75    /// 接收
76    pub fn receive(&mut self) -> Box<dyn Receive> {
77        match self.protocol {
78            #[cfg(feature = "pop3")]
79            Protocol::POP3 => Box::new(Pop3::new(
80                self.host.clone(),
81                self.port,
82                self.name.clone(),
83                self.mail.clone(),
84                self.pass.clone(),
85                self.email_type.clone(),
86            )),
87            #[cfg(feature = "smtp")]
88            Protocol::Smtp => Box::new(Smtp::new(
89                self.host.clone(),
90                self.port,
91                self.name.clone(),
92                self.mail.clone(),
93                self.pass.clone(),
94                self.email_type.clone(),
95            )),
96            #[cfg(feature = "imap")]
97            Protocol::Imap => Box::new(Imap::new(
98                self.host.clone(),
99                self.port,
100                self.name.clone(),
101                self.mail.clone(),
102                self.pass.clone(),
103                self.email_type.clone(),
104            )),
105            _ => Box::new(UnsupportedReceive {
106                protocol: self.protocol.clone(),
107            }),
108        }
109    }
110    /// 发送
111    pub fn sender(&mut self) -> Box<dyn Sender> {
112        match self.protocol {
113            #[cfg(feature = "pop3")]
114            Protocol::POP3 => Box::new(Pop3::new(
115                self.host.clone(),
116                self.port,
117                self.name.clone(),
118                self.mail.clone(),
119                self.pass.clone(),
120                self.email_type.clone(),
121            )),
122            #[cfg(feature = "smtp")]
123            Protocol::Smtp => Box::new(Smtp::new(
124                self.host.clone(),
125                self.port,
126                self.name.clone(),
127                self.mail.clone(),
128                self.pass.clone(),
129                self.email_type.clone(),
130            )),
131            #[cfg(feature = "imap")]
132            Protocol::Imap => Box::new(Imap::new(
133                self.host.clone(),
134                self.port,
135                self.name.clone(),
136                self.mail.clone(),
137                self.pass.clone(),
138                self.email_type.clone(),
139            )),
140            _ => Box::new(UnsupportedSender {
141                protocol: self.protocol.clone(),
142            }),
143        }
144    }
145}
146
147pub trait Receive {
148    /// 获取邮件总数
149    fn get_total(&mut self) -> Result<usize, Error>;
150    /// 获取指定量ID列表
151    fn get_id_list(&mut self) -> Result<Vec<String>, Error>;
152    /// 获取指定ID邮件
153    /// 返回 (邮件内容,邮件尺寸,邮件指纹)
154    fn get_id_info(&mut self, id: &str) -> Result<(Vec<u8>, usize, String), Error>;
155    /// 删除指定邮件
156    fn del_id(&mut self, id: &str) -> Result<bool, Error>;
157
158    /// 获取唯一标识符列表
159    /// IMAP: 返回 UID 列表
160    /// POP3: 返回 (序号, UIDL) 列表中的 UIDL
161    fn get_uid_list(&mut self) -> Result<Vec<String>, Error> {
162        self.get_id_list()
163    }
164
165    /// 获取指定 UID 之后的新邮件列表 (增量查询)
166    /// IMAP: 使用 UID SEARCH
167    /// POP3: 需要配合本地存储比对
168    fn get_new_since(&mut self, _last_uid: &str) -> Result<Vec<String>, Error> {
169        Err(Error::other("此协议不支持增量查询"))
170    }
171
172    /// 获取邮箱有效性标识
173    /// IMAP: 返回 UIDVALIDITY
174    /// POP3: 返回 None (POP3 无此概念)
175    fn get_validity(&mut self) -> Result<Option<u64>, Error> {
176        Ok(None)
177    }
178
179    /// 获取最大 UID (用于记录同步点)
180    fn get_max_uid(&mut self) -> Result<Option<u64>, Error> {
181        Ok(None)
182    }
183
184    /// 获取 UIDL 映射表 (仅 POP3)
185    /// 返回 Vec<(序号, UIDL)>
186    fn get_uidl_map(&mut self) -> Result<Vec<(String, String)>, Error> {
187        Err(Error::other("此协议不支持 UIDL"))
188    }
189}
190
191pub trait Sender {
192    /// 发送邮件
193    /// 返回 (邮件内容,邮件尺寸,邮件指纹)
194    fn send(&mut self) -> Result<(Vec<u8>, usize, String), Error>;
195
196    /// 设置发件人信息
197    /// * name 发件人名称
198    /// * mail 发件箱
199    fn set_from(&mut self, name: &str) -> &mut dyn Sender;
200
201    /// 收件人
202    /// * name 收件人名称
203    /// * mail 收件箱
204    fn set_to(&mut self, name: &str, mail: &str) -> &mut dyn Sender;
205    /// 抄送人
206    /// * name 抄送人名称
207    /// * mail 收件箱
208    fn set_cc(&mut self, name: &str, mail: &str) -> &mut dyn Sender;
209    /// 设置主题
210    fn set_subject(&mut self, subject: &str) -> &mut dyn Sender;
211    /// 设置内容
212    fn set_body(&mut self, text: &str, html: &str) -> &mut dyn Sender;
213    /// 设置文件
214    fn set_file(&mut self, name: &str, data: Vec<u8>, content_type: &str) -> &mut dyn Sender;
215}
216
217struct UnsupportedReceive {
218    protocol: Protocol,
219}
220
221impl UnsupportedReceive {
222    fn err(&self, action: &str) -> Error {
223        Error::other(format!("{action} 不支持协议: {:?}", self.protocol))
224    }
225}
226
227impl Receive for UnsupportedReceive {
228    fn get_total(&mut self) -> Result<usize, Error> {
229        Err(self.err("获取邮件总数"))
230    }
231
232    fn get_id_list(&mut self) -> Result<Vec<String>, Error> {
233        Err(self.err("获取邮件ID列表"))
234    }
235
236    fn get_id_info(&mut self, _id: &str) -> Result<(Vec<u8>, usize, String), Error> {
237        Err(self.err("获取邮件内容"))
238    }
239
240    fn del_id(&mut self, _id: &str) -> Result<bool, Error> {
241        Err(self.err("删除邮件"))
242    }
243}
244
245struct UnsupportedSender {
246    protocol: Protocol,
247}
248
249impl UnsupportedSender {
250    fn err(&self, action: &str) -> Error {
251        Error::other(format!("{action} 不支持协议: {:?}", self.protocol))
252    }
253}
254
255impl Sender for UnsupportedSender {
256    fn send(&mut self) -> Result<(Vec<u8>, usize, String), Error> {
257        Err(self.err("发送邮件"))
258    }
259
260    fn set_from(&mut self, _name: &str) -> &mut dyn Sender {
261        self
262    }
263
264    fn set_to(&mut self, _name: &str, _mail: &str) -> &mut dyn Sender {
265        self
266    }
267
268    fn set_cc(&mut self, _name: &str, _mail: &str) -> &mut dyn Sender {
269        self
270    }
271
272    fn set_subject(&mut self, _subject: &str) -> &mut dyn Sender {
273        self
274    }
275
276    fn set_body(&mut self, _text: &str, _html: &str) -> &mut dyn Sender {
277        self
278    }
279
280    fn set_file(&mut self, _name: &str, _data: Vec<u8>, _content_type: &str) -> &mut dyn Sender {
281        self
282    }
283}
284
285#[derive(Clone, Debug, Deserialize, Serialize)]
286enum Protocol {
287    POP3,
288    Smtp,
289    Imap,
290    None,
291}
292
293impl From<&str> for Protocol {
294    fn from(name: &str) -> Self {
295        let name = name.to_lowercase();
296        match name.as_str() {
297            "pop3" => Protocol::POP3,
298            "imap" => Protocol::Imap,
299            "smtp" => Protocol::Smtp,
300            _ => Protocol::None,
301        }
302    }
303}
304
305#[derive(Clone, Debug, Deserialize, Serialize)]
306enum EmailType {
307    Google,
308    Qq,
309    None,
310}
311impl From<&str> for EmailType {
312    fn from(email: &str) -> Self {
313        match email {
314            x if x.contains("@gmail.com") => EmailType::Google,
315            x if x.contains("@qq.com") => EmailType::Qq,
316            _ => EmailType::None,
317        }
318    }
319}
320
321#[derive(Clone, Debug, Deserialize, Serialize)]
322pub struct Config {
323    default: String,
324    connections: BTreeMap<String, Connection>,
325}
326impl Default for Config {
327    fn default() -> Self {
328        Self::new()
329    }
330}
331
332impl Config {
333    pub fn create(config_file: PathBuf, pkg_name: bool) -> Config {
334        #[derive(Clone, Debug, Deserialize, Serialize)]
335        pub struct ConfigNew {
336            pub br_email: Config,
337        }
338        impl ConfigNew {
339            pub fn new() -> ConfigNew {
340                let mut connections = BTreeMap::new();
341                connections.insert("my_name".to_string(), Connection::default());
342                Self {
343                    br_email: Config {
344                        default: "my_name".to_string(),
345                        connections,
346                    },
347                }
348            }
349        }
350        match fs::read_to_string(config_file.clone()) {
351            Ok(e) => {
352                if pkg_name {
353                    let data = ConfigNew::new();
354                    toml::from_str::<ConfigNew>(&e)
355                        .unwrap_or_else(|_| {
356                            if let Ok(toml) = toml::to_string(&data) {
357                                let toml = format!("{e}\r\n{toml}");
358                                if let Some(path) = config_file.to_str() {
359                                    let _ = fs::write(path, toml);
360                                }
361                            }
362                            data
363                        })
364                        .br_email
365                } else {
366                    let data = Config::new();
367                    toml::from_str::<Config>(&e).unwrap_or_else(|_| {
368                        if let Ok(toml) = toml::to_string(&data) {
369                            let toml = format!("{e}\r\n{toml}");
370                            if let Some(path) = config_file.to_str() {
371                                let _ = fs::write(path, toml);
372                            }
373                        }
374                        data
375                    })
376                }
377            }
378            Err(_) => {
379                if pkg_name {
380                    let data = ConfigNew::new();
381                    if let Some(parent) = config_file.parent() {
382                        let _ = fs::create_dir_all(parent);
383                    }
384                    if let Ok(toml) = toml::to_string(&data) {
385                        if let Some(path) = config_file.to_str() {
386                            let _ = fs::write(path, toml);
387                        }
388                    }
389                    data.br_email
390                } else {
391                    let data = Config::new();
392                    if let Some(parent) = config_file.parent() {
393                        let _ = fs::create_dir_all(parent);
394                    }
395                    if let Ok(toml) = toml::to_string(&data) {
396                        if let Some(path) = config_file.to_str() {
397                            let _ = fs::write(path, toml);
398                        }
399                    }
400                    data
401                }
402            }
403        }
404    }
405    pub fn new() -> Self {
406        let mut connections = BTreeMap::new();
407        connections.insert("my_name".to_string(), Connection::default());
408        Self {
409            default: "my_name".to_string(),
410            connections,
411        }
412    }
413}
414#[derive(Clone, Debug, Deserialize, Serialize)]
415struct Connection {
416    /// 协议
417    protocol: Protocol,
418    /// 邮件地址
419    host: String,
420    /// 邮件端口
421    port: u16,
422    /// 邮件名称
423    name: String,
424    /// 邮件账号
425    mail: String,
426    /// 邮件密码
427    pass: String,
428    /// 邮件类型
429    email_type: EmailType,
430}
431impl Default for Connection {
432    fn default() -> Self {
433        Self {
434            protocol: Protocol::Smtp,
435            host: "smtp.exmail.qq.com".to_string(),
436            port: 25,
437            name: "发件人名".to_string(),
438            mail: "xxx@qq.com".to_string(),
439            pass: "*******".to_string(),
440            email_type: EmailType::Qq,
441        }
442    }
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use std::time::{SystemTime, UNIX_EPOCH};
449
450    fn temp_config_path(test_name: &str) -> PathBuf {
451        let nanos = SystemTime::now()
452            .duration_since(UNIX_EPOCH)
453            .unwrap()
454            .as_nanos();
455        std::env::temp_dir().join(format!(
456            "br_email_{test_name}_{}_{}.toml",
457            std::process::id(),
458            nanos
459        ))
460    }
461
462    #[test]
463    fn test_protocol_from() {
464        assert!(matches!(Protocol::from("pop3"), Protocol::POP3));
465        assert!(matches!(Protocol::from("POP3"), Protocol::POP3));
466        assert!(matches!(Protocol::from("imap"), Protocol::Imap));
467        assert!(matches!(Protocol::from("IMAP"), Protocol::Imap));
468        assert!(matches!(Protocol::from("smtp"), Protocol::Smtp));
469        assert!(matches!(Protocol::from("SMTP"), Protocol::Smtp));
470        assert!(matches!(Protocol::from("unknown"), Protocol::None));
471    }
472
473    #[test]
474    fn test_email_type_from() {
475        assert!(matches!(
476            EmailType::from("user@gmail.com"),
477            EmailType::Google
478        ));
479        assert!(matches!(EmailType::from("user@qq.com"), EmailType::Qq));
480        assert!(matches!(
481            EmailType::from("user@outlook.com"),
482            EmailType::None
483        ));
484    }
485
486    #[test]
487    fn test_mail_new() {
488        let mail = Mail::new(
489            "imap",
490            "imap.gmail.com",
491            993,
492            "test",
493            "user@gmail.com",
494            "pass",
495        );
496        assert!(matches!(mail.protocol, Protocol::Imap));
497        assert_eq!(mail.host, "imap.gmail.com");
498        assert_eq!(mail.port, 993);
499        assert_eq!(mail.name, "test");
500        assert_eq!(mail.mail, "user@gmail.com");
501        assert_eq!(mail.pass, "pass");
502    }
503
504    #[test]
505    fn test_config_new() {
506        let config = Config::new();
507        assert_eq!(config.default, "my_name");
508        assert!(config.connections.contains_key("my_name"));
509    }
510
511    #[test]
512    fn test_config_default() {
513        let config = Config::default();
514        assert_eq!(config.default, "my_name");
515    }
516
517    #[test]
518    fn test_connection_default() {
519        let conn = Connection::default();
520        assert!(matches!(conn.protocol, Protocol::Smtp));
521        assert_eq!(conn.host, "smtp.exmail.qq.com");
522        assert_eq!(conn.port, 25);
523    }
524
525    #[test]
526    fn test_receive_dispatch_for_supported_protocols() {
527        let mut pop3_mail = Mail::new("pop3", "localhost", 995, "n", "m@m.com", "p");
528        let _pop3_receiver = pop3_mail.receive();
529
530        let mut smtp_mail = Mail::new("smtp", "localhost", 587, "n", "m@m.com", "p");
531        let _smtp_receiver = smtp_mail.receive();
532
533        let mut imap_mail = Mail::new("imap", "localhost", 993, "n", "m@m.com", "p");
534        let _imap_receiver = imap_mail.receive();
535    }
536
537    #[test]
538    fn test_sender_dispatch_for_supported_protocols() {
539        let mut pop3_mail = Mail::new("pop3", "localhost", 995, "n", "m@m.com", "p");
540        let mut pop3_sender = pop3_mail.sender();
541        pop3_sender.set_from("sender");
542        pop3_sender.set_to("to", "to@test.com");
543        pop3_sender.set_cc("cc", "cc@test.com");
544        pop3_sender.set_subject("subject");
545        pop3_sender.set_body("text", "<p>html</p>");
546        pop3_sender.set_file("attachment.txt", vec![1, 2, 3], "text/plain");
547
548        let mut smtp_mail = Mail::new("smtp", "localhost", 587, "n", "m@m.com", "p");
549        let mut smtp_sender = smtp_mail.sender();
550        smtp_sender.set_from("sender");
551        smtp_sender.set_to("to", "to@test.com");
552        smtp_sender.set_cc("cc", "cc@test.com");
553        smtp_sender.set_subject("subject");
554        smtp_sender.set_body("text", "<p>html</p>");
555        smtp_sender.set_file("attachment.txt", vec![1, 2, 3], "text/plain");
556
557        let mut imap_mail = Mail::new("imap", "localhost", 993, "n", "m@m.com", "p");
558        let mut imap_sender = imap_mail.sender();
559        imap_sender.set_from("sender");
560        imap_sender.set_to("to", "to@test.com");
561        imap_sender.set_cc("cc", "cc@test.com");
562        imap_sender.set_subject("subject");
563        imap_sender.set_body("text", "<p>html</p>");
564        imap_sender.set_file("attachment.txt", vec![1, 2, 3], "text/plain");
565    }
566
567    #[test]
568    fn test_config_create_file_not_exists_without_pkg_name() {
569        let config_path = temp_config_path("create_without_pkg_missing");
570        std::fs::remove_file(&config_path).ok();
571
572        let config = Config::create(config_path.clone(), false);
573        assert_eq!(config.default, "my_name");
574        assert!(config.connections.contains_key("my_name"));
575        assert!(config_path.exists());
576
577        std::fs::remove_file(&config_path).ok();
578    }
579
580    #[test]
581    fn test_config_create_file_not_exists_with_pkg_name() {
582        let config_path = temp_config_path("create_with_pkg_missing");
583        std::fs::remove_file(&config_path).ok();
584
585        let config = Config::create(config_path.clone(), true);
586        assert_eq!(config.default, "my_name");
587        assert!(config.connections.contains_key("my_name"));
588        let file_content = std::fs::read_to_string(&config_path).unwrap();
589        assert!(file_content.contains("[br_email]"));
590
591        std::fs::remove_file(&config_path).ok();
592    }
593
594    #[test]
595    fn test_config_create_existing_valid_toml_without_pkg_name() {
596        let config_path = temp_config_path("create_without_pkg_valid");
597        std::fs::remove_file(&config_path).ok();
598
599        let config_content = r#"default = "my_name"
600
601[connections.my_name]
602protocol = "Smtp"
603host = "smtp.test.com"
604port = 25
605name = "test"
606mail = "test@test.com"
607pass = "pass"
608email_type = "None"
609"#;
610        std::fs::write(&config_path, config_content).unwrap();
611
612        let config = Config::create(config_path.clone(), false);
613        assert_eq!(config.default, "my_name");
614        assert_eq!(
615            config.connections.get("my_name").unwrap().host,
616            "smtp.test.com"
617        );
618
619        std::fs::remove_file(&config_path).ok();
620    }
621
622    #[test]
623    fn test_config_create_existing_valid_toml_with_pkg_name() {
624        let config_path = temp_config_path("create_with_pkg_valid");
625        std::fs::remove_file(&config_path).ok();
626
627        let config_content = r#"[br_email]
628default = "my_name"
629
630[br_email.connections.my_name]
631protocol = "Smtp"
632host = "smtp.test.com"
633port = 25
634name = "test"
635mail = "test@test.com"
636pass = "pass"
637email_type = "None"
638"#;
639        std::fs::write(&config_path, config_content).unwrap();
640
641        let config = Config::create(config_path.clone(), true);
642        assert_eq!(config.default, "my_name");
643        assert_eq!(
644            config.connections.get("my_name").unwrap().host,
645            "smtp.test.com"
646        );
647
648        std::fs::remove_file(&config_path).ok();
649    }
650
651    #[test]
652    fn test_config_create_existing_invalid_toml_with_pkg_name_fallback() {
653        let config_path = temp_config_path("create_with_pkg_invalid");
654        std::fs::remove_file(&config_path).ok();
655
656        std::fs::write(&config_path, "invalid toml content!!!").unwrap();
657        let config = Config::create(config_path.clone(), true);
658
659        assert_eq!(config.default, "my_name");
660        assert!(config.connections.contains_key("my_name"));
661        let file_content = std::fs::read_to_string(&config_path).unwrap();
662        assert!(file_content.contains("invalid toml content!!!"));
663        assert!(file_content.contains("[br_email]"));
664
665        std::fs::remove_file(&config_path).ok();
666    }
667
668    #[test]
669    fn test_config_create_existing_invalid_toml_without_pkg_name_fallback() {
670        let config_path = temp_config_path("create_without_pkg_invalid");
671        std::fs::remove_file(&config_path).ok();
672
673        std::fs::write(&config_path, "invalid toml content!!!").unwrap();
674        let config = Config::create(config_path.clone(), false);
675
676        assert_eq!(config.default, "my_name");
677        assert!(config.connections.contains_key("my_name"));
678        let file_content = std::fs::read_to_string(&config_path).unwrap();
679        assert!(file_content.contains("invalid toml content!!!"));
680        assert!(file_content.contains("[connections.my_name]"));
681
682        std::fs::remove_file(&config_path).ok();
683    }
684
685    #[test]
686    fn test_mail_new_new_success() {
687        let mut config = Config::new();
688        config.default = "test_conn".to_string();
689        config.connections.insert(
690            "test_conn".to_string(),
691            Connection {
692                protocol: Protocol::Imap,
693                host: "imap.test.com".to_string(),
694                port: 993,
695                name: "Test".to_string(),
696                mail: "test@test.com".to_string(),
697                pass: "password".to_string(),
698                email_type: EmailType::None,
699            },
700        );
701
702        let mail = Mail::new_new(config).unwrap();
703        assert_eq!(mail.host, "imap.test.com");
704        assert_eq!(mail.port, 993);
705    }
706
707    #[test]
708    fn test_mail_new_new_missing_default() {
709        let mut config = Config::new();
710        config.default = "nonexistent".to_string();
711
712        let result = Mail::new_new(config);
713        assert!(result.is_err());
714    }
715
716    #[test]
717    fn test_unsupported_receive() {
718        let mut mail = Mail::new("unknown", "host", 123, "name", "mail", "pass");
719        let mut receiver = mail.receive();
720
721        assert!(receiver.get_total().is_err());
722        assert!(receiver.get_id_list().is_err());
723        assert!(receiver.get_id_info("1").is_err());
724        assert!(receiver.del_id("1").is_err());
725    }
726
727    #[test]
728    fn test_unsupported_sender() {
729        let mut mail = Mail::new("unknown", "host", 123, "name", "mail", "pass");
730        let mut sender = mail.sender();
731
732        assert!(sender.send().is_err());
733        sender.set_from("test");
734        sender.set_to("name", "mail");
735        sender.set_cc("name", "mail");
736        sender.set_subject("subject");
737        sender.set_body("text", "html");
738        sender.set_file("name", vec![], "type");
739    }
740
741    #[test]
742    fn test_receive_trait_default_methods() {
743        let mut mail = Mail::new("unknown", "host", 123, "name", "mail", "pass");
744        let mut receiver = mail.receive();
745
746        assert!(receiver.get_uid_list().is_err());
747        assert!(receiver.get_new_since("100").is_err());
748        assert!(receiver.get_validity().unwrap().is_none());
749        assert!(receiver.get_max_uid().unwrap().is_none());
750        assert!(receiver.get_uidl_map().is_err());
751    }
752
753    #[test]
754    fn test_mail_analyze_valid() {
755        let email_data = b"From: sender@example.com\r\n\
756To: receiver@example.com\r\n\
757Subject: Test\r\n\
758Content-Type: text/plain\r\n\
759Date: 01 Jan 2024 12:00:00 +0000\r\n\
760\r\n\
761Body"
762            .to_vec();
763
764        let result = Mail::analyze(email_data, false);
765        assert!(result.is_ok());
766        let email = result.unwrap();
767        assert_eq!(email.subject, "Test");
768    }
769
770    #[test]
771    fn test_mail_analyze_invalid() {
772        let invalid_data = b"invalid email".to_vec();
773        let result = Mail::analyze(invalid_data, false);
774        assert!(result.is_err());
775    }
776
777    #[test]
778    fn test_mail_with_different_protocols() {
779        let pop3_mail = Mail::new("pop3", "pop.test.com", 995, "name", "user@test.com", "pass");
780        assert!(matches!(pop3_mail.protocol, Protocol::POP3));
781
782        let smtp_mail = Mail::new(
783            "smtp",
784            "smtp.test.com",
785            587,
786            "name",
787            "user@test.com",
788            "pass",
789        );
790        assert!(matches!(smtp_mail.protocol, Protocol::Smtp));
791
792        let imap_mail = Mail::new(
793            "imap",
794            "imap.test.com",
795            993,
796            "name",
797            "user@test.com",
798            "pass",
799        );
800        assert!(matches!(imap_mail.protocol, Protocol::Imap));
801    }
802
803    #[test]
804    fn test_email_type_detection() {
805        let gmail = Mail::new(
806            "imap",
807            "imap.gmail.com",
808            993,
809            "name",
810            "user@gmail.com",
811            "pass",
812        );
813        assert!(matches!(gmail.email_type, EmailType::Google));
814
815        let qq = Mail::new("imap", "imap.qq.com", 993, "name", "user@qq.com", "pass");
816        assert!(matches!(qq.email_type, EmailType::Qq));
817
818        let other = Mail::new(
819            "imap",
820            "imap.other.com",
821            993,
822            "name",
823            "user@other.com",
824            "pass",
825        );
826        assert!(matches!(other.email_type, EmailType::None));
827    }
828}