diff --git a/src/transport/stub/mod.rs b/src/transport/stub/mod.rs index 7b0a13f..aa43e7c 100644 --- a/src/transport/stub/mod.rs +++ b/src/transport/stub/mod.rs @@ -1,10 +1,12 @@ -//! The stub transport only logs message envelope and drops the content. It can be useful for -//! testing purposes. +//! The stub transport logs message envelopes as well as contents. It can be useful for testing +//! purposes. //! -//! #### Stub Transport +//! # Stub Transport //! -//! The stub transport returns provided result and drops the content. It can be useful for -//! testing purposes. +//! The stub transport logs message envelopes as well as contents. It can be useful for testing +//! purposes. +//! +//! # Examples //! //! ```rust //! # #[cfg(feature = "builder")] @@ -12,7 +14,7 @@ //! use lettre::{transport::stub::StubTransport, Message, Transport}; //! //! # use std::error::Error; -//! # fn main() -> Result<(), Box> { +//! # fn try_main() -> Result<(), Box> { //! let email = Message::builder() //! .from("NoBody ".parse()?) //! .reply_to("Yuin ".parse()?) @@ -23,15 +25,29 @@ //! let mut sender = StubTransport::new_ok(); //! let result = sender.send(&email); //! assert!(result.is_ok()); +//! assert_eq!( +//! sender.messages(), +//! vec![( +//! email.envelope().clone(), +//! String::from_utf8(email.formatted()).unwrap() +//! )], +//! ); //! # Ok(()) //! # } +//! # try_main().unwrap(); //! # } //! ``` -use std::{error::Error as StdError, fmt}; +use std::{ + error::Error as StdError, + fmt, + sync::{Arc, Mutex as StdMutex}, +}; #[cfg(any(feature = "tokio1", feature = "async-std1"))] use async_trait::async_trait; +#[cfg(any(feature = "tokio1", feature = "async-std1"))] +use futures_util::lock::Mutex as FuturesMutex; #[cfg(any(feature = "tokio1", feature = "async-std1"))] use crate::AsyncTransport; @@ -48,47 +64,113 @@ impl fmt::Display for Error { impl StdError for Error {} -/// This transport logs the message envelope and returns the given response -#[derive(Debug, Clone, Copy)] +/// This transport logs messages and always returns the given response +#[derive(Debug, Clone)] pub struct StubTransport { response: Result<(), Error>, + message_log: Arc>>, +} + +/// This transport logs messages and always returns the given response +#[derive(Debug, Clone)] +#[cfg(any(feature = "tokio1", feature = "async-std1"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))] +pub struct AsyncStubTransport { + response: Result<(), Error>, + message_log: Arc>>, } impl StubTransport { /// Creates a new transport that always returns the given Result - pub fn new(response: Result<(), Error>) -> StubTransport { - StubTransport { response } + pub fn new(response: Result<(), Error>) -> Self { + Self { + response, + message_log: Arc::new(StdMutex::new(vec![])), + } } /// Creates a new transport that always returns a success response - pub fn new_ok() -> StubTransport { - StubTransport { response: Ok(()) } + pub fn new_ok() -> Self { + Self { + response: Ok(()), + message_log: Arc::new(StdMutex::new(vec![])), + } } /// Creates a new transport that always returns an error - pub fn new_error() -> StubTransport { - StubTransport { + pub fn new_error() -> Self { + Self { response: Err(Error), + message_log: Arc::new(StdMutex::new(vec![])), } } + + /// Return all logged messages sent using [`Transport::send_raw`] + pub fn messages(&self) -> Vec<(Envelope, String)> { + self.message_log + .lock() + .expect("Couldn't acquire lock to write message log") + .clone() + } +} + +#[cfg(any(feature = "async-std1", feature = "tokio1"))] +impl AsyncStubTransport { + /// Creates a new transport that always returns the given Result + pub fn new(response: Result<(), Error>) -> Self { + Self { + response, + message_log: Arc::new(FuturesMutex::new(vec![])), + } + } + + /// Creates a new transport that always returns a success response + pub fn new_ok() -> Self { + Self { + response: Ok(()), + message_log: Arc::new(FuturesMutex::new(vec![])), + } + } + + /// Creates a new transport that always returns an error + pub fn new_error() -> Self { + Self { + response: Err(Error), + message_log: Arc::new(FuturesMutex::new(vec![])), + } + } + + /// Return all logged messages sent using [`AsyncTransport::send_raw`] + #[cfg(any(feature = "tokio1", feature = "async-std1"))] + pub async fn messages(&self) -> Vec<(Envelope, String)> { + self.message_log.lock().await.clone() + } } impl Transport for StubTransport { type Ok = (); type Error = Error; - fn send_raw(&self, _envelope: &Envelope, _email: &[u8]) -> Result { + fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { + self.message_log + .lock() + .expect("Couldn't acquire lock to write message log") + .push((envelope.clone(), String::from_utf8_lossy(email).into())); self.response } } #[cfg(any(feature = "tokio1", feature = "async-std1"))] #[async_trait] -impl AsyncTransport for StubTransport { +impl AsyncTransport for AsyncStubTransport { type Ok = (); type Error = Error; - async fn send_raw(&self, _envelope: &Envelope, _email: &[u8]) -> Result { + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { + self.message_log + .lock() + .await + .push((envelope.clone(), String::from_utf8_lossy(email).into())); self.response } } diff --git a/tests/transport_stub.rs b/tests/transport_stub.rs index 01b7119..321d1f3 100644 --- a/tests/transport_stub.rs +++ b/tests/transport_stub.rs @@ -17,19 +17,25 @@ mod sync { sender_ok.send(&email).unwrap(); sender_ko.send(&email).unwrap_err(); + + let expected_messages = vec![( + email.envelope().clone(), + String::from_utf8(email.formatted()).unwrap(), + )]; + assert_eq!(sender_ok.messages(), expected_messages); } } #[cfg(test)] #[cfg(all(feature = "builder", feature = "tokio1"))] mod tokio_1 { - use lettre::{transport::stub::StubTransport, AsyncTransport, Message}; + use lettre::{transport::stub::AsyncStubTransport, AsyncTransport, Message}; use tokio1_crate as tokio; #[tokio::test] async fn stub_transport_tokio1() { - let sender_ok = StubTransport::new_ok(); - let sender_ko = StubTransport::new_error(); + let sender_ok = AsyncStubTransport::new_ok(); + let sender_ko = AsyncStubTransport::new_error(); let email = Message::builder() .from("NoBody ".parse().unwrap()) .reply_to("Yuin ".parse().unwrap()) @@ -39,19 +45,25 @@ mod tokio_1 { .unwrap(); sender_ok.send(email.clone()).await.unwrap(); - sender_ko.send(email).await.unwrap_err(); + sender_ko.send(email.clone()).await.unwrap_err(); + + let expected_messages = vec![( + email.envelope().clone(), + String::from_utf8(email.formatted()).unwrap(), + )]; + assert_eq!(sender_ok.messages().await, expected_messages); } } #[cfg(test)] #[cfg(all(feature = "builder", feature = "async-std1"))] mod asyncstd_1 { - use lettre::{transport::stub::StubTransport, AsyncTransport, Message}; + use lettre::{transport::stub::AsyncStubTransport, AsyncTransport, Message}; #[async_std::test] async fn stub_transport_asyncstd1() { - let sender_ok = StubTransport::new_ok(); - let sender_ko = StubTransport::new_error(); + let sender_ok = AsyncStubTransport::new_ok(); + let sender_ko = AsyncStubTransport::new_error(); let email = Message::builder() .from("NoBody ".parse().unwrap()) .reply_to("Yuin ".parse().unwrap()) @@ -61,6 +73,12 @@ mod asyncstd_1 { .unwrap(); sender_ok.send(email.clone()).await.unwrap(); - sender_ko.send(email).await.unwrap_err(); + sender_ko.send(email.clone()).await.unwrap_err(); + + let expected_messages = vec![( + email.envelope().clone(), + String::from_utf8(email.formatted()).unwrap(), + )]; + assert_eq!(sender_ok.messages().await, expected_messages); } }