Add message logging to StubTransport (#744)
This makes it more useful as a testing tool as it now allows you to retrieve all messages sent via this transport.
This commit is contained in:
committed by
GitHub
parent
8b40e438fd
commit
c24213c850
@@ -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<dyn Error>> {
|
||||
//! # fn try_main() -> Result<(), Box<dyn Error>> {
|
||||
//! let email = Message::builder()
|
||||
//! .from("NoBody <nobody@domain.tld>".parse()?)
|
||||
//! .reply_to("Yuin <yuin@domain.tld>".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<StdMutex<Vec<(Envelope, String)>>>,
|
||||
}
|
||||
|
||||
/// 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<FuturesMutex<Vec<(Envelope, String)>>>,
|
||||
}
|
||||
|
||||
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<Self::Ok, Self::Error> {
|
||||
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
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<Self::Ok, Self::Error> {
|
||||
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
self.message_log
|
||||
.lock()
|
||||
.await
|
||||
.push((envelope.clone(), String::from_utf8_lossy(email).into()));
|
||||
self.response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <nobody@domain.tld>".parse().unwrap())
|
||||
.reply_to("Yuin <yuin@domain.tld>".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 <nobody@domain.tld>".parse().unwrap())
|
||||
.reply_to("Yuin <yuin@domain.tld>".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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user