1use crate::dsn::{smtp_diagnostic_text, smtp_to_enhanced_code};
4use crate::mailet::{Mailet, MailetAction, MailetConfig};
5use async_trait::async_trait;
6use rusmes_proto::Mail;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum SmtpStatusCode {
12 TemporaryFailure(u16),
14 PermanentFailure(u16),
16}
17
18impl SmtpStatusCode {
19 pub fn from_code(code: u16) -> Option<Self> {
21 match code {
22 400..=499 => Some(SmtpStatusCode::TemporaryFailure(code)),
23 500..=599 => Some(SmtpStatusCode::PermanentFailure(code)),
24 _ => None,
25 }
26 }
27
28 pub fn code(&self) -> u16 {
30 match self {
31 SmtpStatusCode::TemporaryFailure(c) | SmtpStatusCode::PermanentFailure(c) => *c,
32 }
33 }
34
35 pub fn is_permanent(&self) -> bool {
37 matches!(self, SmtpStatusCode::PermanentFailure(_))
38 }
39
40 pub fn enhanced_code(&self) -> String {
42 smtp_to_enhanced_code(self.code()).to_string()
43 }
44
45 pub fn diagnostic_text(&self) -> &str {
47 smtp_diagnostic_text(self.code())
48 }
49}
50
51#[derive(Debug, Clone, PartialEq)]
53pub enum DsnAction {
54 Failed,
55 Delayed,
56 Delivered,
57 Relayed,
58 Expanded,
59}
60
61impl DsnAction {
62 pub fn as_str(&self) -> &str {
63 match self {
64 DsnAction::Failed => "failed",
65 DsnAction::Delayed => "delayed",
66 DsnAction::Delivered => "delivered",
67 DsnAction::Relayed => "relayed",
68 DsnAction::Expanded => "expanded",
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct DeliveryStatusNotification {
76 pub reporting_mta: String,
78 pub arrival_date: u64,
80 pub recipients: Vec<DsnRecipient>,
82}
83
84#[derive(Debug, Clone)]
86pub struct DsnRecipient {
87 pub final_recipient: String,
89 pub action: DsnAction,
91 pub status: String,
93 pub diagnostic_code: Option<String>,
95 pub remote_mta: Option<String>,
97 pub last_attempt_date: Option<u64>,
99}
100
101impl DeliveryStatusNotification {
102 pub fn new(reporting_mta: String) -> Self {
104 let arrival_date = SystemTime::now()
105 .duration_since(UNIX_EPOCH)
106 .unwrap_or_default()
107 .as_secs();
108
109 Self {
110 reporting_mta,
111 arrival_date,
112 recipients: Vec::new(),
113 }
114 }
115
116 pub fn add_recipient(&mut self, recipient: DsnRecipient) {
118 self.recipients.push(recipient);
119 }
120
121 pub fn generate_message_body(&self) -> String {
123 let mut body = String::new();
124
125 body.push_str(&format!("Reporting-MTA: dns; {}\n", self.reporting_mta));
127 body.push_str(&format!(
128 "Arrival-Date: {}\n",
129 self.format_date(self.arrival_date)
130 ));
131 body.push('\n');
132
133 for recipient in &self.recipients {
135 body.push_str(&format!(
136 "Final-Recipient: rfc822; {}\n",
137 recipient.final_recipient
138 ));
139 body.push_str(&format!("Action: {}\n", recipient.action.as_str()));
140 body.push_str(&format!("Status: {}\n", recipient.status));
141
142 if let Some(ref diagnostic) = recipient.diagnostic_code {
143 body.push_str(&format!("Diagnostic-Code: smtp; {}\n", diagnostic));
144 }
145
146 if let Some(ref remote_mta) = recipient.remote_mta {
147 body.push_str(&format!("Remote-MTA: dns; {}\n", remote_mta));
148 }
149
150 if let Some(date) = recipient.last_attempt_date {
151 body.push_str(&format!("Last-Attempt-Date: {}\n", self.format_date(date)));
152 }
153
154 body.push('\n');
155 }
156
157 body
158 }
159
160 pub fn generate_human_text(&self, error_code: SmtpStatusCode, error_message: &str) -> String {
162 let mut text = String::new();
163
164 text.push_str("This is an automatically generated Delivery Status Notification.\n\n");
165
166 if error_code.is_permanent() {
167 text.push_str("YOUR MESSAGE COULD NOT BE DELIVERED to the following recipients:\n\n");
168 } else {
169 text.push_str(
170 "DELIVERY OF YOUR MESSAGE HAS BEEN DELAYED to the following recipients:\n\n",
171 );
172 }
173
174 for recipient in &self.recipients {
175 text.push_str(&format!(" {}\n", recipient.final_recipient));
176 }
177
178 text.push('\n');
179 text.push_str(&format!(
180 "Reason: {} {}\n",
181 error_code.code(),
182 error_message
183 ));
184 text.push_str(&format!(
185 "Enhanced Status Code: {}\n",
186 error_code.enhanced_code()
187 ));
188 text.push_str(&format!("Diagnostic: {}\n", error_code.diagnostic_text()));
189 text.push('\n');
190
191 if error_code.is_permanent() {
192 text.push_str("No further delivery attempts will be made.\n");
193 } else {
194 text.push_str(
195 "Delivery will be retried. You will be notified if delivery continues to fail.\n",
196 );
197 }
198
199 text
200 }
201
202 fn format_date(&self, timestamp: u64) -> String {
203 use chrono::{DateTime, Utc};
204
205 if let Some(dt) = DateTime::<Utc>::from_timestamp(timestamp as i64, 0) {
206 dt.to_rfc2822()
207 } else {
208 format!("timestamp:{}", timestamp)
209 }
210 }
211}
212
213pub struct BounceMailet {
215 name: String,
216 reporting_mta: String,
218 postmaster: String,
220 include_headers: bool,
222 include_body: bool,
224 max_body_size: usize,
226}
227
228impl BounceMailet {
229 pub fn new() -> Self {
231 Self {
232 name: "Bounce".to_string(),
233 reporting_mta: "localhost".to_string(),
234 postmaster: "postmaster@localhost".to_string(),
235 include_headers: true,
236 include_body: false,
237 max_body_size: 1024,
238 }
239 }
240
241 pub fn generate_bounce(
243 &self,
244 mail: &Mail,
245 error_code: SmtpStatusCode,
246 error_message: &str,
247 ) -> String {
248 let mut dsn = DeliveryStatusNotification::new(self.reporting_mta.clone());
249
250 for recipient in mail.recipients() {
252 let action = if error_code.is_permanent() {
253 DsnAction::Failed
254 } else {
255 DsnAction::Delayed
256 };
257
258 let recipient_dsn = DsnRecipient {
259 final_recipient: recipient.to_string(),
260 action,
261 status: error_code.enhanced_code(),
262 diagnostic_code: Some(format!("{} {}", error_code.code(), error_message)),
263 remote_mta: mail
264 .get_attribute("smtp.remote_mta")
265 .and_then(|v| v.as_str())
266 .map(|s| s.to_string()),
267 last_attempt_date: Some(
268 SystemTime::now()
269 .duration_since(UNIX_EPOCH)
270 .unwrap_or_default()
271 .as_secs(),
272 ),
273 };
274
275 dsn.add_recipient(recipient_dsn);
276 }
277
278 self.generate_multipart_report(&dsn, mail, error_code, error_message)
280 }
281
282 fn generate_multipart_report(
284 &self,
285 dsn: &DeliveryStatusNotification,
286 mail: &Mail,
287 error_code: SmtpStatusCode,
288 error_message: &str,
289 ) -> String {
290 let mut message = String::new();
291 let boundary = "rusmes-dsn-boundary-7b3a9f1e";
292
293 message.push_str("MIME-Version: 1.0\n");
295 message.push_str(&format!(
296 "Content-Type: multipart/report; report-type=delivery-status; boundary=\"{}\"\n",
297 boundary
298 ));
299 message.push('\n');
300 message.push_str("This is a MIME-encapsulated message.\n");
301 message.push('\n');
302
303 message.push_str(&format!("--{}\n", boundary));
305 message.push_str("Content-Type: text/plain; charset=utf-8\n");
306 message.push_str("Content-Description: Notification\n");
307 message.push('\n');
308 message.push_str(&dsn.generate_human_text(error_code, error_message));
309 message.push('\n');
310
311 let msg_id = mail.message_id();
312 message.push_str(&format!("Original Message-ID: {}\n", msg_id));
313 message.push('\n');
314
315 message.push_str(&format!("--{}\n", boundary));
317 message.push_str("Content-Type: message/delivery-status\n");
318 message.push_str("Content-Description: Delivery Report\n");
319 message.push('\n');
320 message.push_str(&dsn.generate_message_body());
321
322 message.push_str(&format!("--{}\n", boundary));
324
325 if self.include_body {
326 message.push_str("Content-Type: message/rfc822\n");
328 message.push_str("Content-Description: Undelivered Message\n");
329 message.push('\n');
330
331 self.append_original_headers(&mut message, mail);
333
334 if let Some(body) = mail.get_attribute("message.body").and_then(|v| v.as_str()) {
336 message.push('\n');
337 let truncated_body = if body.len() > self.max_body_size {
338 format!("{}... (truncated)", &body[..self.max_body_size])
339 } else {
340 body.to_string()
341 };
342 message.push_str(&truncated_body);
343 message.push('\n');
344 }
345 } else if self.include_headers {
346 message.push_str("Content-Type: text/rfc822-headers\n");
348 message.push_str("Content-Description: Undelivered Message Headers\n");
349 message.push('\n');
350 self.append_original_headers(&mut message, mail);
351 }
352
353 message.push_str(&format!("--{}--\n", boundary));
355
356 message
357 }
358
359 fn append_original_headers(&self, message: &mut String, mail: &Mail) {
361 if let Some(subject) = mail
362 .get_attribute("header.Subject")
363 .and_then(|v| v.as_str())
364 {
365 message.push_str(&format!("Subject: {}\n", subject));
366 }
367 if let Some(from) = mail.get_attribute("header.From").and_then(|v| v.as_str()) {
368 message.push_str(&format!("From: {}\n", from));
369 }
370 if let Some(to) = mail.get_attribute("header.To").and_then(|v| v.as_str()) {
371 message.push_str(&format!("To: {}\n", to));
372 }
373 if let Some(date) = mail.get_attribute("header.Date").and_then(|v| v.as_str()) {
374 message.push_str(&format!("Date: {}\n", date));
375 }
376 if let Some(cc) = mail.get_attribute("header.Cc").and_then(|v| v.as_str()) {
377 message.push_str(&format!("Cc: {}\n", cc));
378 }
379 if let Some(msg_id) = mail
380 .get_attribute("header.Message-ID")
381 .and_then(|v| v.as_str())
382 {
383 message.push_str(&format!("Message-ID: {}\n", msg_id));
384 }
385 }
386}
387
388impl Default for BounceMailet {
389 fn default() -> Self {
390 Self::new()
391 }
392}
393
394#[async_trait]
395impl Mailet for BounceMailet {
396 async fn init(&mut self, config: MailetConfig) -> anyhow::Result<()> {
397 if let Some(mta) = config.get_param("reporting_mta") {
398 self.reporting_mta = mta.to_string();
399 }
400
401 if let Some(postmaster) = config.get_param("postmaster") {
402 self.postmaster = postmaster.to_string();
403 }
404
405 if let Some(include) = config.get_param("include_headers") {
406 self.include_headers = include.parse().unwrap_or(true);
407 }
408
409 if let Some(include) = config.get_param("include_body") {
410 self.include_body = include.parse().unwrap_or(false);
411 }
412
413 if let Some(max_size) = config.get_param("max_body_size") {
414 self.max_body_size = max_size.parse().unwrap_or(1024);
415 }
416
417 tracing::info!("Initialized BounceMailet");
418 Ok(())
419 }
420
421 async fn service(&self, mail: &mut Mail) -> anyhow::Result<MailetAction> {
422 let should_bounce = mail
424 .get_attribute("bounce.required")
425 .and_then(|v| v.as_bool())
426 .unwrap_or(false);
427
428 if !should_bounce {
429 return Ok(MailetAction::Continue);
430 }
431
432 let error_code_num = mail
434 .get_attribute("bounce.error_code")
435 .and_then(|v| v.as_i64())
436 .unwrap_or(550) as u16;
437
438 let error_message = mail
439 .get_attribute("bounce.error_message")
440 .and_then(|v| v.as_str())
441 .unwrap_or("Delivery failed");
442
443 let error_code = SmtpStatusCode::from_code(error_code_num)
444 .unwrap_or(SmtpStatusCode::PermanentFailure(550));
445
446 tracing::info!(
447 "Generating bounce for mail {} with code {}",
448 mail.id(),
449 error_code.code()
450 );
451
452 let bounce_body = self.generate_bounce(mail, error_code, error_message);
454
455 mail.set_attribute("bounce.generated", true);
457 mail.set_attribute("bounce.body", bounce_body);
458
459 Ok(MailetAction::Continue)
460 }
461
462 fn name(&self) -> &str {
463 &self.name
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use bytes::Bytes;
471 use rusmes_proto::{HeaderMap, MailAddress, MessageBody, MimeMessage};
472 use std::str::FromStr;
473
474 #[test]
475 fn test_smtp_status_code_parsing() {
476 assert_eq!(
477 SmtpStatusCode::from_code(450),
478 Some(SmtpStatusCode::TemporaryFailure(450))
479 );
480 assert_eq!(
481 SmtpStatusCode::from_code(550),
482 Some(SmtpStatusCode::PermanentFailure(550))
483 );
484 assert_eq!(SmtpStatusCode::from_code(250), None);
485 }
486
487 #[test]
488 fn test_smtp_status_code_permanent() {
489 assert!(SmtpStatusCode::PermanentFailure(550).is_permanent());
490 assert!(!SmtpStatusCode::TemporaryFailure(450).is_permanent());
491 }
492
493 #[test]
494 fn test_smtp_enhanced_codes() {
495 assert_eq!(
496 SmtpStatusCode::PermanentFailure(550).enhanced_code(),
497 "5.1.1"
498 );
499 assert_eq!(
500 SmtpStatusCode::TemporaryFailure(450).enhanced_code(),
501 "4.2.1"
502 );
503 assert_eq!(
504 SmtpStatusCode::PermanentFailure(552).enhanced_code(),
505 "5.2.2"
506 );
507 }
508
509 #[test]
510 fn test_smtp_diagnostic_text() {
511 assert_eq!(
512 SmtpStatusCode::PermanentFailure(550).diagnostic_text(),
513 "Requested action not taken: mailbox unavailable"
514 );
515 }
516
517 #[test]
518 fn test_dsn_action_as_str() {
519 assert_eq!(DsnAction::Failed.as_str(), "failed");
520 assert_eq!(DsnAction::Delayed.as_str(), "delayed");
521 assert_eq!(DsnAction::Delivered.as_str(), "delivered");
522 }
523
524 #[test]
525 fn test_dsn_creation() {
526 let dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
527 assert_eq!(dsn.reporting_mta, "mail.example.com");
528 assert!(dsn.recipients.is_empty());
529 }
530
531 #[test]
532 fn test_dsn_add_recipient() {
533 let mut dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
534 let recipient = DsnRecipient {
535 final_recipient: "user@example.com".to_string(),
536 action: DsnAction::Failed,
537 status: "5.1.1".to_string(),
538 diagnostic_code: Some("550 User unknown".to_string()),
539 remote_mta: None,
540 last_attempt_date: None,
541 };
542
543 dsn.add_recipient(recipient);
544 assert_eq!(dsn.recipients.len(), 1);
545 }
546
547 #[test]
548 fn test_dsn_message_body_generation() {
549 let mut dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
550 let recipient = DsnRecipient {
551 final_recipient: "user@example.com".to_string(),
552 action: DsnAction::Failed,
553 status: "5.1.1".to_string(),
554 diagnostic_code: Some("550 User unknown".to_string()),
555 remote_mta: Some("remote.example.com".to_string()),
556 last_attempt_date: None,
557 };
558
559 dsn.add_recipient(recipient);
560 let body = dsn.generate_message_body();
561
562 assert!(body.contains("Reporting-MTA: dns; mail.example.com"));
563 assert!(body.contains("Final-Recipient: rfc822; user@example.com"));
564 assert!(body.contains("Action: failed"));
565 assert!(body.contains("Status: 5.1.1"));
566 assert!(body.contains("Diagnostic-Code: smtp; 550 User unknown"));
567 assert!(body.contains("Remote-MTA: dns; remote.example.com"));
568 }
569
570 #[tokio::test]
571 async fn test_bounce_mailet_init() {
572 let mut mailet = BounceMailet::new();
573 let config = MailetConfig::new("Bounce");
574
575 mailet.init(config).await.unwrap();
576 assert_eq!(mailet.name(), "Bounce");
577 }
578
579 #[tokio::test]
580 async fn test_bounce_mailet_no_bounce_required() {
581 let mailet = BounceMailet::new();
582 let mut mail = Mail::new(
583 Some(MailAddress::from_str("sender@test.com").unwrap()),
584 vec![MailAddress::from_str("rcpt@test.com").unwrap()],
585 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
586 None,
587 None,
588 );
589
590 let action = mailet.service(&mut mail).await.unwrap();
591 assert_eq!(action, MailetAction::Continue);
592 assert!(mail.get_attribute("bounce.generated").is_none());
593 }
594
595 #[tokio::test]
596 async fn test_bounce_mailet_generate_bounce() {
597 let mailet = BounceMailet::new();
598 let mut mail = Mail::new(
599 Some(MailAddress::from_str("sender@test.com").unwrap()),
600 vec![MailAddress::from_str("unknown@test.com").unwrap()],
601 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
602 None,
603 None,
604 );
605
606 mail.set_attribute("bounce.required", true);
607 mail.set_attribute("bounce.error_code", 550_i64);
608 mail.set_attribute("bounce.error_message", "User unknown");
609
610 let action = mailet.service(&mut mail).await.unwrap();
611 assert_eq!(action, MailetAction::Continue);
612 assert_eq!(
613 mail.get_attribute("bounce.generated")
614 .and_then(|v| v.as_bool()),
615 Some(true)
616 );
617 assert!(mail.get_attribute("bounce.body").is_some());
618 }
619
620 #[tokio::test]
621 async fn test_bounce_message_content() {
622 let mailet = BounceMailet::new();
623 let mut mail = Mail::new(
624 Some(MailAddress::from_str("sender@test.com").unwrap()),
625 vec![MailAddress::from_str("user@test.com").unwrap()],
626 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
627 None,
628 None,
629 );
630
631 mail.set_attribute("header.Subject", "Test message");
632 let error_code = SmtpStatusCode::PermanentFailure(550);
633 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
634
635 assert!(bounce.contains("Delivery Status Notification"));
636 assert!(bounce.contains("user@test.com"));
637 assert!(bounce.contains("550"));
638 assert!(bounce.contains("User unknown"));
639 assert!(bounce.contains("5.1.1")); }
641
642 #[tokio::test]
643 async fn test_bounce_config_reporting_mta() {
644 let mut mailet = BounceMailet::new();
645 let config = MailetConfig::new("Bounce").with_param("reporting_mta", "mail.example.com");
646
647 mailet.init(config).await.unwrap();
648 assert_eq!(mailet.reporting_mta, "mail.example.com");
649 }
650
651 #[tokio::test]
652 async fn test_bounce_config_postmaster() {
653 let mut mailet = BounceMailet::new();
654 let config = MailetConfig::new("Bounce").with_param("postmaster", "postmaster@example.com");
655
656 mailet.init(config).await.unwrap();
657 assert_eq!(mailet.postmaster, "postmaster@example.com");
658 }
659
660 #[tokio::test]
661 async fn test_bounce_config_include_headers() {
662 let mut mailet = BounceMailet::new();
663 let config = MailetConfig::new("Bounce").with_param("include_headers", "false");
664
665 mailet.init(config).await.unwrap();
666 assert!(!mailet.include_headers);
667 }
668
669 #[tokio::test]
670 async fn test_bounce_config_include_body() {
671 let mut mailet = BounceMailet::new();
672 let config = MailetConfig::new("Bounce").with_param("include_body", "true");
673
674 mailet.init(config).await.unwrap();
675 assert!(mailet.include_body);
676 }
677
678 #[tokio::test]
679 async fn test_bounce_config_max_body_size() {
680 let mut mailet = BounceMailet::new();
681 let config = MailetConfig::new("Bounce").with_param("max_body_size", "2048");
682
683 mailet.init(config).await.unwrap();
684 assert_eq!(mailet.max_body_size, 2048);
685 }
686
687 #[tokio::test]
688 async fn test_bounce_multipart_structure() {
689 let mailet = BounceMailet::new();
690 let mail = Mail::new(
691 Some(MailAddress::from_str("sender@test.com").unwrap()),
692 vec![MailAddress::from_str("user@test.com").unwrap()],
693 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
694 None,
695 None,
696 );
697
698 let error_code = SmtpStatusCode::PermanentFailure(550);
699 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
700
701 assert!(bounce.contains("Content-Type: text/plain"));
703 assert!(bounce.contains("Content-Type: message/delivery-status"));
704 }
705
706 #[tokio::test]
707 async fn test_bounce_temporary_failure() {
708 let mailet = BounceMailet::new();
709 let mail = Mail::new(
710 Some(MailAddress::from_str("sender@test.com").unwrap()),
711 vec![MailAddress::from_str("user@test.com").unwrap()],
712 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
713 None,
714 None,
715 );
716
717 let error_code = SmtpStatusCode::TemporaryFailure(450);
718 let bounce = mailet.generate_bounce(&mail, error_code, "Mailbox unavailable");
719
720 assert!(bounce.contains("450"));
721 assert!(bounce.contains("4.2.1")); assert!(bounce.contains("Mailbox unavailable"));
723 }
724
725 #[tokio::test]
726 async fn test_bounce_include_original_headers() {
727 let mut mailet = BounceMailet::new();
728 mailet.include_headers = true;
729
730 let mut mail = Mail::new(
731 Some(MailAddress::from_str("sender@test.com").unwrap()),
732 vec![MailAddress::from_str("user@test.com").unwrap()],
733 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
734 None,
735 None,
736 );
737 mail.set_attribute("header.Subject", "Test Subject");
738 mail.set_attribute("header.From", "sender@test.com");
739
740 let error_code = SmtpStatusCode::PermanentFailure(550);
741 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
742
743 assert!(bounce.contains("Subject: Test Subject"));
744 assert!(bounce.contains("From: sender@test.com"));
745 }
746
747 #[tokio::test]
748 async fn test_bounce_include_body_truncated() {
749 let mut mailet = BounceMailet::new();
750 mailet.include_body = true;
751 mailet.max_body_size = 10;
752
753 let mut mail = Mail::new(
754 Some(MailAddress::from_str("sender@test.com").unwrap()),
755 vec![MailAddress::from_str("user@test.com").unwrap()],
756 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
757 None,
758 None,
759 );
760 mail.set_attribute(
761 "message.body",
762 "This is a very long message body that should be truncated",
763 );
764
765 let error_code = SmtpStatusCode::PermanentFailure(550);
766 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
767
768 assert!(bounce.contains("(truncated)"));
769 }
770
771 #[tokio::test]
772 async fn test_bounce_multiple_recipients() {
773 let mailet = BounceMailet::new();
774 let mail = Mail::new(
775 Some(MailAddress::from_str("sender@test.com").unwrap()),
776 vec![
777 MailAddress::from_str("user1@test.com").unwrap(),
778 MailAddress::from_str("user2@test.com").unwrap(),
779 ],
780 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
781 None,
782 None,
783 );
784
785 let error_code = SmtpStatusCode::PermanentFailure(550);
786 let bounce = mailet.generate_bounce(&mail, error_code, "Users unknown");
787
788 assert!(bounce.contains("user1@test.com"));
789 assert!(bounce.contains("user2@test.com"));
790 }
791
792 #[tokio::test]
793 async fn test_bounce_with_remote_mta() {
794 let mailet = BounceMailet::new();
795 let mut mail = Mail::new(
796 Some(MailAddress::from_str("sender@test.com").unwrap()),
797 vec![MailAddress::from_str("user@test.com").unwrap()],
798 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
799 None,
800 None,
801 );
802 mail.set_attribute("smtp.remote_mta", "remote.example.com");
803
804 let error_code = SmtpStatusCode::PermanentFailure(550);
805 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
806
807 assert!(bounce.contains("Remote-MTA: dns; remote.example.com"));
808 }
809
810 #[test]
811 fn test_smtp_status_code_all_enhanced_codes() {
812 assert_eq!(
814 SmtpStatusCode::TemporaryFailure(421).enhanced_code(),
815 "4.4.2"
816 );
817 assert_eq!(
818 SmtpStatusCode::TemporaryFailure(450).enhanced_code(),
819 "4.2.1"
820 );
821 assert_eq!(
822 SmtpStatusCode::TemporaryFailure(451).enhanced_code(),
823 "4.3.0"
824 );
825 assert_eq!(
826 SmtpStatusCode::TemporaryFailure(452).enhanced_code(),
827 "4.2.2"
828 );
829 assert_eq!(
830 SmtpStatusCode::TemporaryFailure(454).enhanced_code(),
831 "4.7.0"
832 );
833 assert_eq!(
834 SmtpStatusCode::PermanentFailure(550).enhanced_code(),
835 "5.1.1"
836 );
837 assert_eq!(
838 SmtpStatusCode::PermanentFailure(551).enhanced_code(),
839 "5.1.6"
840 );
841 assert_eq!(
842 SmtpStatusCode::PermanentFailure(552).enhanced_code(),
843 "5.2.2"
844 );
845 assert_eq!(
846 SmtpStatusCode::PermanentFailure(553).enhanced_code(),
847 "5.1.3"
848 );
849 assert_eq!(
850 SmtpStatusCode::PermanentFailure(554).enhanced_code(),
851 "5.7.1"
852 );
853 }
854
855 #[test]
856 fn test_smtp_status_code_500_codes() {
857 assert_eq!(
858 SmtpStatusCode::PermanentFailure(500).enhanced_code(),
859 "5.5.2"
860 );
861 assert_eq!(
862 SmtpStatusCode::PermanentFailure(501).enhanced_code(),
863 "5.5.4"
864 );
865 assert_eq!(
866 SmtpStatusCode::PermanentFailure(502).enhanced_code(),
867 "5.5.1"
868 );
869 assert_eq!(
870 SmtpStatusCode::PermanentFailure(503).enhanced_code(),
871 "5.5.1"
872 );
873 assert_eq!(
874 SmtpStatusCode::PermanentFailure(504).enhanced_code(),
875 "5.5.4"
876 );
877 }
878
879 #[test]
880 fn test_dsn_human_text_permanent() {
881 let mut dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
882 dsn.add_recipient(DsnRecipient {
883 final_recipient: "user@test.com".to_string(),
884 action: DsnAction::Failed,
885 status: "5.1.1".to_string(),
886 diagnostic_code: None,
887 remote_mta: None,
888 last_attempt_date: None,
889 });
890
891 let error_code = SmtpStatusCode::PermanentFailure(550);
892 let text = dsn.generate_human_text(error_code, "User unknown");
893
894 assert!(text.contains("COULD NOT BE DELIVERED"));
895 assert!(text.contains("user@test.com"));
896 assert!(text.contains("550"));
897 assert!(text.contains("User unknown"));
898 assert!(text.contains("No further delivery attempts"));
899 }
900
901 #[test]
902 fn test_dsn_human_text_temporary() {
903 let mut dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
904 dsn.add_recipient(DsnRecipient {
905 final_recipient: "user@test.com".to_string(),
906 action: DsnAction::Delayed,
907 status: "4.2.1".to_string(),
908 diagnostic_code: None,
909 remote_mta: None,
910 last_attempt_date: None,
911 });
912
913 let error_code = SmtpStatusCode::TemporaryFailure(450);
914 let text = dsn.generate_human_text(error_code, "Mailbox unavailable");
915
916 assert!(text.contains("DELAYED"));
917 assert!(text.contains("user@test.com"));
918 assert!(text.contains("450"));
919 assert!(text.contains("Mailbox unavailable"));
920 assert!(text.contains("Delivery will be retried"));
921 }
922
923 #[tokio::test]
924 async fn test_multipart_report_structure() {
925 let mailet = BounceMailet::new();
926 let mail = Mail::new(
927 Some(MailAddress::from_str("sender@test.com").unwrap()),
928 vec![MailAddress::from_str("user@test.com").unwrap()],
929 MimeMessage::new(
930 HeaderMap::new(),
931 MessageBody::Small(Bytes::from("Test body")),
932 ),
933 None,
934 None,
935 );
936
937 let error_code = SmtpStatusCode::PermanentFailure(550);
938 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
939
940 assert!(bounce.contains("MIME-Version: 1.0"));
942 assert!(bounce.contains("multipart/report"));
943 assert!(bounce.contains("report-type=delivery-status"));
944 assert!(bounce.contains("Content-Type: text/plain"));
945 assert!(bounce.contains("Content-Type: message/delivery-status"));
946 }
947
948 #[tokio::test]
949 async fn test_multipart_with_full_message() {
950 let mut mailet = BounceMailet::new();
951 mailet.include_body = true;
952
953 let mut mail = Mail::new(
954 Some(MailAddress::from_str("sender@test.com").unwrap()),
955 vec![MailAddress::from_str("user@test.com").unwrap()],
956 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
957 None,
958 None,
959 );
960 mail.set_attribute("message.body", "This is the original message body");
961
962 let error_code = SmtpStatusCode::PermanentFailure(550);
963 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
964
965 assert!(bounce.contains("Content-Type: message/rfc822"));
966 assert!(bounce.contains("Undelivered Message"));
967 assert!(bounce.contains("original message body"));
968 }
969
970 #[tokio::test]
971 async fn test_multipart_headers_only() {
972 let mut mailet = BounceMailet::new();
973 mailet.include_headers = true;
974 mailet.include_body = false;
975
976 let mut mail = Mail::new(
977 Some(MailAddress::from_str("sender@test.com").unwrap()),
978 vec![MailAddress::from_str("user@test.com").unwrap()],
979 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
980 None,
981 None,
982 );
983 mail.set_attribute("header.Subject", "Test Subject");
984
985 let error_code = SmtpStatusCode::PermanentFailure(550);
986 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
987
988 assert!(bounce.contains("Content-Type: text/rfc822-headers"));
989 assert!(bounce.contains("Undelivered Message Headers"));
990 assert!(bounce.contains("Subject: Test Subject"));
991 }
992
993 #[test]
996 fn test_smtp_code_421_connection_timeout() {
997 let code = SmtpStatusCode::TemporaryFailure(421);
998 assert_eq!(code.enhanced_code(), "4.4.2");
999 assert!(!code.is_permanent());
1000 }
1001
1002 #[test]
1003 fn test_smtp_code_450_mailbox_unavailable() {
1004 let code = SmtpStatusCode::TemporaryFailure(450);
1005 assert_eq!(code.enhanced_code(), "4.2.1");
1006 assert!(!code.is_permanent());
1007 }
1008
1009 #[test]
1010 fn test_smtp_code_451_local_error() {
1011 let code = SmtpStatusCode::TemporaryFailure(451);
1012 assert_eq!(code.enhanced_code(), "4.3.0");
1013 assert!(!code.is_permanent());
1014 }
1015
1016 #[test]
1017 fn test_smtp_code_452_quota_exceeded_temp() {
1018 let code = SmtpStatusCode::TemporaryFailure(452);
1019 assert_eq!(code.enhanced_code(), "4.2.2");
1020 assert!(!code.is_permanent());
1021 }
1022
1023 #[test]
1024 fn test_smtp_code_554_policy_rejection() {
1025 let code = SmtpStatusCode::PermanentFailure(554);
1026 assert_eq!(code.enhanced_code(), "5.7.1");
1027 assert!(code.is_permanent());
1028 }
1029
1030 #[tokio::test]
1031 async fn test_bounce_quota_exceeded() {
1032 let mailet = BounceMailet::new();
1033 let mail = Mail::new(
1034 Some(MailAddress::from_str("sender@test.com").unwrap()),
1035 vec![MailAddress::from_str("user@test.com").unwrap()],
1036 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1037 None,
1038 None,
1039 );
1040
1041 let error_code = SmtpStatusCode::PermanentFailure(552);
1042 let bounce = mailet.generate_bounce(&mail, error_code, "Quota exceeded");
1043
1044 assert!(bounce.contains("552"));
1045 assert!(bounce.contains("5.2.2"));
1046 assert!(bounce.contains("Quota exceeded"));
1047 }
1048
1049 #[tokio::test]
1050 async fn test_bounce_message_too_large() {
1051 let mailet = BounceMailet::new();
1052 let mail = Mail::new(
1053 Some(MailAddress::from_str("sender@test.com").unwrap()),
1054 vec![MailAddress::from_str("user@test.com").unwrap()],
1055 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1056 None,
1057 None,
1058 );
1059
1060 let error_code = SmtpStatusCode::PermanentFailure(552);
1061 let bounce = mailet.generate_bounce(&mail, error_code, "Message too large");
1062
1063 assert!(bounce.contains("Message too large"));
1064 assert!(bounce.contains("5.2.2"));
1065 }
1066
1067 #[tokio::test]
1068 async fn test_bounce_spam_rejection() {
1069 let mailet = BounceMailet::new();
1070 let mail = Mail::new(
1071 Some(MailAddress::from_str("sender@test.com").unwrap()),
1072 vec![MailAddress::from_str("user@test.com").unwrap()],
1073 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1074 None,
1075 None,
1076 );
1077
1078 let error_code = SmtpStatusCode::PermanentFailure(554);
1079 let bounce = mailet.generate_bounce(&mail, error_code, "Spam detected");
1080
1081 assert!(bounce.contains("Spam detected"));
1082 assert!(bounce.contains("5.7.1"));
1083 }
1084
1085 #[tokio::test]
1086 async fn test_bounce_virus_rejection() {
1087 let mailet = BounceMailet::new();
1088 let mail = Mail::new(
1089 Some(MailAddress::from_str("sender@test.com").unwrap()),
1090 vec![MailAddress::from_str("user@test.com").unwrap()],
1091 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1092 None,
1093 None,
1094 );
1095
1096 let error_code = SmtpStatusCode::PermanentFailure(554);
1097 let bounce = mailet.generate_bounce(&mail, error_code, "Virus detected");
1098
1099 assert!(bounce.contains("Virus detected"));
1100 assert!(bounce.contains("5.7.1"));
1101 }
1102
1103 #[tokio::test]
1104 async fn test_bounce_relay_denied() {
1105 let mailet = BounceMailet::new();
1106 let mail = Mail::new(
1107 Some(MailAddress::from_str("sender@test.com").unwrap()),
1108 vec![MailAddress::from_str("user@test.com").unwrap()],
1109 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1110 None,
1111 None,
1112 );
1113
1114 let error_code = SmtpStatusCode::PermanentFailure(554);
1115 let bounce = mailet.generate_bounce(&mail, error_code, "Relay access denied");
1116
1117 assert!(bounce.contains("Relay access denied"));
1118 }
1119
1120 #[tokio::test]
1121 async fn test_bounce_network_unreachable() {
1122 let mailet = BounceMailet::new();
1123 let mail = Mail::new(
1124 Some(MailAddress::from_str("sender@test.com").unwrap()),
1125 vec![MailAddress::from_str("user@test.com").unwrap()],
1126 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1127 None,
1128 None,
1129 );
1130
1131 let error_code = SmtpStatusCode::TemporaryFailure(421);
1132 let bounce = mailet.generate_bounce(&mail, error_code, "Network unreachable");
1133
1134 assert!(bounce.contains("Network unreachable"));
1135 assert!(bounce.contains("4.4.2"));
1136 }
1137
1138 #[tokio::test]
1139 async fn test_bounce_invalid_address() {
1140 let mailet = BounceMailet::new();
1141 let mail = Mail::new(
1142 Some(MailAddress::from_str("sender@test.com").unwrap()),
1143 vec![MailAddress::from_str("user@test.com").unwrap()],
1144 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1145 None,
1146 None,
1147 );
1148
1149 let error_code = SmtpStatusCode::PermanentFailure(553);
1150 let bounce = mailet.generate_bounce(&mail, error_code, "Invalid address");
1151
1152 assert!(bounce.contains("Invalid address"));
1153 assert!(bounce.contains("5.1.3"));
1154 }
1155
1156 #[tokio::test]
1157 async fn test_bounce_mailbox_moved() {
1158 let mailet = BounceMailet::new();
1159 let mail = Mail::new(
1160 Some(MailAddress::from_str("sender@test.com").unwrap()),
1161 vec![MailAddress::from_str("user@test.com").unwrap()],
1162 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1163 None,
1164 None,
1165 );
1166
1167 let error_code = SmtpStatusCode::PermanentFailure(551);
1168 let bounce = mailet.generate_bounce(&mail, error_code, "Mailbox moved");
1169
1170 assert!(bounce.contains("Mailbox moved"));
1171 assert!(bounce.contains("5.1.6"));
1172 }
1173
1174 #[test]
1175 fn test_dsn_recipient_all_fields() {
1176 let recipient = DsnRecipient {
1177 final_recipient: "user@test.com".to_string(),
1178 action: DsnAction::Failed,
1179 status: "5.1.1".to_string(),
1180 diagnostic_code: Some("550 User unknown".to_string()),
1181 remote_mta: Some("mx.test.com".to_string()),
1182 last_attempt_date: Some(1609459200),
1183 };
1184
1185 assert_eq!(recipient.final_recipient, "user@test.com");
1186 assert_eq!(recipient.action, DsnAction::Failed);
1187 assert_eq!(recipient.status, "5.1.1");
1188 assert_eq!(
1189 recipient.diagnostic_code,
1190 Some("550 User unknown".to_string())
1191 );
1192 assert_eq!(recipient.remote_mta, Some("mx.test.com".to_string()));
1193 assert_eq!(recipient.last_attempt_date, Some(1609459200));
1194 }
1195
1196 #[test]
1197 fn test_dsn_generation_with_all_recipients() {
1198 let mut dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
1199
1200 for i in 1..=3 {
1201 dsn.add_recipient(DsnRecipient {
1202 final_recipient: format!("user{}@test.com", i),
1203 action: DsnAction::Failed,
1204 status: "5.1.1".to_string(),
1205 diagnostic_code: Some("550 User unknown".to_string()),
1206 remote_mta: None,
1207 last_attempt_date: None,
1208 });
1209 }
1210
1211 let body = dsn.generate_message_body();
1212 assert!(body.contains("user1@test.com"));
1213 assert!(body.contains("user2@test.com"));
1214 assert!(body.contains("user3@test.com"));
1215 }
1216
1217 #[tokio::test]
1218 async fn test_bounce_all_headers_present() {
1219 let mailet = BounceMailet::new();
1220 let mut mail = Mail::new(
1221 Some(MailAddress::from_str("sender@test.com").unwrap()),
1222 vec![MailAddress::from_str("user@test.com").unwrap()],
1223 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1224 None,
1225 None,
1226 );
1227
1228 mail.set_attribute("header.Subject", "Test Subject");
1229 mail.set_attribute("header.From", "sender@test.com");
1230 mail.set_attribute("header.To", "recipient@test.com");
1231 mail.set_attribute("header.Date", "Mon, 1 Jan 2024 00:00:00 +0000");
1232 mail.set_attribute("header.Cc", "cc@test.com");
1233 mail.set_attribute("header.Message-ID", "<test@example.com>");
1234
1235 let error_code = SmtpStatusCode::PermanentFailure(550);
1236 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
1237
1238 assert!(bounce.contains("Subject: Test Subject"));
1239 assert!(bounce.contains("From: sender@test.com"));
1240 assert!(bounce.contains("To: recipient@test.com"));
1241 assert!(bounce.contains("Date: Mon, 1 Jan 2024 00:00:00 +0000"));
1242 assert!(bounce.contains("Cc: cc@test.com"));
1243 assert!(bounce.contains("Message-ID: <test@example.com>"));
1244 }
1245
1246 #[tokio::test]
1247 async fn test_bounce_rfc3464_multipart_structure() {
1248 let mailet = BounceMailet::new();
1249 let mail = Mail::new(
1250 Some(MailAddress::from_str("sender@test.com").unwrap()),
1251 vec![MailAddress::from_str("user@test.com").unwrap()],
1252 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1253 None,
1254 None,
1255 );
1256
1257 let error_code = SmtpStatusCode::PermanentFailure(550);
1258 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
1259
1260 assert!(bounce.contains("MIME-Version: 1.0"));
1262 assert!(bounce.contains("multipart/report"));
1263 assert!(bounce.contains("report-type=delivery-status"));
1264 assert!(bounce.contains("boundary="));
1265
1266 assert!(bounce.contains("Content-Type: text/plain"));
1268 assert!(bounce.contains("Content-Description: Notification"));
1269
1270 assert!(bounce.contains("Content-Type: message/delivery-status"));
1272 assert!(bounce.contains("Content-Description: Delivery Report"));
1273
1274 assert!(bounce.contains("Content-Type: text/rfc822-headers"));
1276 assert!(bounce.contains("Content-Description: Undelivered Message Headers"));
1277 }
1278
1279 #[test]
1280 fn test_smtp_status_code_edge_cases() {
1281 assert_eq!(
1283 SmtpStatusCode::from_code(400),
1284 Some(SmtpStatusCode::TemporaryFailure(400))
1285 );
1286 assert_eq!(
1287 SmtpStatusCode::from_code(499),
1288 Some(SmtpStatusCode::TemporaryFailure(499))
1289 );
1290 assert_eq!(
1291 SmtpStatusCode::from_code(500),
1292 Some(SmtpStatusCode::PermanentFailure(500))
1293 );
1294 assert_eq!(
1295 SmtpStatusCode::from_code(599),
1296 Some(SmtpStatusCode::PermanentFailure(599))
1297 );
1298
1299 assert_eq!(SmtpStatusCode::from_code(200), None);
1301 assert_eq!(SmtpStatusCode::from_code(300), None);
1302 assert_eq!(SmtpStatusCode::from_code(600), None);
1303 }
1304
1305 #[tokio::test]
1306 async fn test_bounce_default_values() {
1307 let mailet = BounceMailet::default();
1308 assert_eq!(mailet.reporting_mta, "localhost");
1309 assert_eq!(mailet.postmaster, "postmaster@localhost");
1310 assert!(mailet.include_headers);
1311 assert!(!mailet.include_body);
1312 assert_eq!(mailet.max_body_size, 1024);
1313 }
1314
1315 #[tokio::test]
1316 async fn test_bounce_empty_body() {
1317 let mut mailet = BounceMailet::new();
1318 mailet.include_body = true;
1319
1320 let mail = Mail::new(
1321 Some(MailAddress::from_str("sender@test.com").unwrap()),
1322 vec![MailAddress::from_str("user@test.com").unwrap()],
1323 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from(""))),
1324 None,
1325 None,
1326 );
1327
1328 let error_code = SmtpStatusCode::PermanentFailure(550);
1329 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
1330
1331 assert!(bounce.contains("User unknown"));
1333 }
1334
1335 #[test]
1336 fn test_dsn_action_debug() {
1337 let action = DsnAction::Failed;
1338 let debug_str = format!("{:?}", action);
1339 assert!(debug_str.contains("Failed"));
1340 }
1341
1342 #[test]
1343 fn test_smtp_status_code_debug() {
1344 let code = SmtpStatusCode::PermanentFailure(550);
1345 let debug_str = format!("{:?}", code);
1346 assert!(debug_str.contains("550"));
1347 }
1348
1349 #[tokio::test]
1350 async fn test_dsn_action_relayed() {
1351 assert_eq!(DsnAction::Relayed.as_str(), "relayed");
1352 }
1353
1354 #[tokio::test]
1355 async fn test_dsn_action_expanded() {
1356 assert_eq!(DsnAction::Expanded.as_str(), "expanded");
1357 }
1358
1359 #[tokio::test]
1360 async fn test_dsn_with_cc_header() {
1361 let mailet = BounceMailet::new();
1362 let mut mail = Mail::new(
1363 Some(MailAddress::from_str("sender@test.com").unwrap()),
1364 vec![MailAddress::from_str("user@test.com").unwrap()],
1365 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1366 None,
1367 None,
1368 );
1369 mail.set_attribute("header.Cc", "cc@test.com");
1370
1371 let error_code = SmtpStatusCode::PermanentFailure(550);
1372 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
1373
1374 assert!(bounce.contains("Cc: cc@test.com"));
1375 }
1376
1377 #[tokio::test]
1378 async fn test_dsn_with_message_id_header() {
1379 let mailet = BounceMailet::new();
1380 let mut mail = Mail::new(
1381 Some(MailAddress::from_str("sender@test.com").unwrap()),
1382 vec![MailAddress::from_str("user@test.com").unwrap()],
1383 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1384 None,
1385 None,
1386 );
1387 mail.set_attribute("header.Message-ID", "<12345@test.com>");
1388
1389 let error_code = SmtpStatusCode::PermanentFailure(550);
1390 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
1391
1392 assert!(bounce.contains("Message-ID: <12345@test.com>"));
1393 }
1394
1395 #[test]
1396 fn test_format_date_rfc2822() {
1397 let dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
1398 let timestamp = 1609459200_u64; let formatted = dsn.format_date(timestamp);
1400
1401 assert!(formatted.contains("2021") || formatted.contains("timestamp:"));
1403 }
1404
1405 #[test]
1406 fn test_format_date_invalid() {
1407 let dsn = DeliveryStatusNotification::new("mail.example.com".to_string());
1408 let timestamp = i64::MAX as u64; let formatted = dsn.format_date(timestamp);
1410
1411 assert!(!formatted.is_empty());
1413 }
1414
1415 #[tokio::test]
1416 async fn test_bounce_boundary_string() {
1417 let mailet = BounceMailet::new();
1418 let mail = Mail::new(
1419 Some(MailAddress::from_str("sender@test.com").unwrap()),
1420 vec![MailAddress::from_str("user@test.com").unwrap()],
1421 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1422 None,
1423 None,
1424 );
1425
1426 let error_code = SmtpStatusCode::PermanentFailure(550);
1427 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
1428
1429 assert!(bounce.contains("boundary=\"rusmes-dsn-boundary"));
1431 assert!(bounce.contains("--rusmes-dsn-boundary"));
1432 }
1433
1434 #[test]
1435 fn test_smtp_status_code_code_method() {
1436 let temp = SmtpStatusCode::TemporaryFailure(450);
1437 assert_eq!(temp.code(), 450);
1438
1439 let perm = SmtpStatusCode::PermanentFailure(550);
1440 assert_eq!(perm.code(), 550);
1441 }
1442
1443 #[test]
1444 fn test_dsn_recipient_clone() {
1445 let recipient = DsnRecipient {
1446 final_recipient: "user@test.com".to_string(),
1447 action: DsnAction::Failed,
1448 status: "5.1.1".to_string(),
1449 diagnostic_code: Some("550 User unknown".to_string()),
1450 remote_mta: None,
1451 last_attempt_date: None,
1452 };
1453
1454 let cloned = recipient.clone();
1455 assert_eq!(cloned.final_recipient, recipient.final_recipient);
1456 assert_eq!(cloned.action, recipient.action);
1457 }
1458
1459 #[tokio::test]
1460 async fn test_bounce_multiple_headers() {
1461 let mailet = BounceMailet::new();
1462 let mut mail = Mail::new(
1463 Some(MailAddress::from_str("sender@test.com").unwrap()),
1464 vec![MailAddress::from_str("user@test.com").unwrap()],
1465 MimeMessage::new(HeaderMap::new(), MessageBody::Small(Bytes::from("Test"))),
1466 None,
1467 None,
1468 );
1469 mail.set_attribute("header.Subject", "Important Message");
1470 mail.set_attribute("header.From", "sender@test.com");
1471 mail.set_attribute("header.To", "user@test.com");
1472 mail.set_attribute("header.Date", "Mon, 1 Jan 2024 00:00:00 +0000");
1473
1474 let error_code = SmtpStatusCode::PermanentFailure(550);
1475 let bounce = mailet.generate_bounce(&mail, error_code, "User unknown");
1476
1477 assert!(bounce.contains("Subject: Important Message"));
1478 assert!(bounce.contains("From: sender@test.com"));
1479 assert!(bounce.contains("To: user@test.com"));
1480 assert!(bounce.contains("Date: Mon, 1 Jan 2024 00:00:00 +0000"));
1481 }
1482}