test: make email client tests better

This commit is contained in:
Sandro Eiler 2024-03-02 14:55:51 +01:00
parent 170f34738b
commit b61f02c03f
5 changed files with 81 additions and 39 deletions

View file

@ -10,3 +10,4 @@ email_client:
base_url: "localhost" base_url: "localhost"
sender_email: "test@fmail.com" sender_email: "test@fmail.com"
authorization_token: "my-secret-token" authorization_token: "my-secret-token"
timeout_milliseconds: 10000

View file

@ -22,12 +22,16 @@ pub struct EmailClientSettings {
pub base_url: String, pub base_url: String,
pub sender_email: String, pub sender_email: String,
pub authorization_token: Secret<String>, pub authorization_token: Secret<String>,
pub timeout_milliseconds: u64,
} }
impl EmailClientSettings { impl EmailClientSettings {
pub fn sender(&self) -> Result<SubscriberEmail, String> { pub fn sender(&self) -> Result<SubscriberEmail, String> {
SubscriberEmail::parse(self.sender_email.clone()) SubscriberEmail::parse(self.sender_email.clone())
} }
pub fn timeout(&self) -> std::time::Duration {
std::time::Duration::from_millis(self.timeout_milliseconds)
}
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]

View file

@ -15,9 +15,11 @@ impl EmailClient {
base_url: String, base_url: String,
sender: SubscriberEmail, sender: SubscriberEmail,
authorization_token: Secret<String>, authorization_token: Secret<String>,
timeout: std::time::Duration,
) -> Self { ) -> Self {
let http_client = Client::builder().timeout(timeout).build().unwrap();
Self { Self {
http_client: Client::new(), http_client,
base_url, base_url,
sender, sender,
authorization_token, authorization_token,
@ -98,38 +100,33 @@ mod tests {
} }
} }
#[tokio::test] /// Generate a random email subject
async fn send_email_succeeds_if_the_server_returns_200() { fn subject() -> String {
// Arrange Sentence(1..2).fake()
let mock_server = MockServer::start().await; }
let sender = SubscriberEmail::parse(SafeEmail().fake()).unwrap(); /// Generate a random email content
let email_client = EmailClient::new(mock_server.uri(), sender, Secret::new(Faker.fake())); fn content() -> String {
Paragraph(1..10).fake()
let subscriber_email = SubscriberEmail::parse(SafeEmail().fake()).unwrap(); }
let subject: String = Sentence(1..2).fake(); /// Generate a random subscriber email
let content: String = Paragraph(1..10).fake(); fn email() -> SubscriberEmail {
SubscriberEmail::parse(SafeEmail().fake()).unwrap()
Mock::given(any()) }
.respond_with(ResponseTemplate::new(200)) /// Get a test instance of `EmailClient`.
.expect(1) fn email_client(base_url: String) -> EmailClient {
.mount(&mock_server) EmailClient::new(
.await; base_url,
email(),
// Act Secret::new(Faker.fake()),
let outcome = email_client std::time::Duration::from_millis(200),
.send_email(subscriber_email, &subject, &content, &content) )
.await;
// Assert
assert_ok!(outcome);
} }
#[tokio::test] #[tokio::test]
async fn send_email_sends_the_expected_request() { async fn send_email_sends_the_expected_request() {
// Arrange // Arrange
let mock_server = MockServer::start().await; let mock_server = MockServer::start().await;
let sender = SubscriberEmail::parse(SafeEmail().fake()).unwrap(); let email_client = email_client(mock_server.uri());
let email_client = EmailClient::new(mock_server.uri(), sender, Secret::new(Faker.fake()));
Mock::given(header_exists("X-Postmark-Server-Token")) Mock::given(header_exists("X-Postmark-Server-Token"))
.and(header("Content-Type", "application/json")) .and(header("Content-Type", "application/json"))
@ -141,29 +138,41 @@ mod tests {
.mount(&mock_server) .mount(&mock_server)
.await; .await;
let subscriber_email = SubscriberEmail::parse(SafeEmail().fake()).unwrap();
let subject: String = Sentence(1..2).fake();
let content: String = Paragraph(1..10).fake();
// Act // Act
let _ = email_client let _ = email_client
.send_email(subscriber_email, &subject, &content, &content) .send_email(email(), &subject(), &content(), &content())
.await; .await;
// Assert // Assert
// Mock expectations are checked on drop // Mock expectations are checked on drop
} }
#[tokio::test]
async fn send_email_succeeds_if_the_server_returns_200() {
// Arrange
let mock_server = MockServer::start().await;
let email_client = email_client(mock_server.uri());
Mock::given(any())
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&mock_server)
.await;
// Act
let outcome = email_client
.send_email(email(), &subject(), &content(), &content())
.await;
// Assert
assert_ok!(outcome);
}
#[tokio::test] #[tokio::test]
async fn send_email_fails_if_the_server_returns_500() { async fn send_email_fails_if_the_server_returns_500() {
// Arrange // Arrange
let mock_server = MockServer::start().await; let mock_server = MockServer::start().await;
let sender = SubscriberEmail::parse(SafeEmail().fake()).unwrap(); let email_client = email_client(mock_server.uri());
let email_client = EmailClient::new(mock_server.uri(), sender, Secret::new(Faker.fake()));
let subscriber_email = SubscriberEmail::parse(SafeEmail().fake()).unwrap();
let subject: String = Sentence(1..2).fake();
let content: String = Paragraph(1..10).fake();
Mock::given(any()) Mock::given(any())
// Not a 200 anymore! // Not a 200 anymore!
@ -174,7 +183,31 @@ mod tests {
// Act // Act
let outcome = email_client let outcome = email_client
.send_email(subscriber_email, &subject, &content, &content) .send_email(email(), &subject(), &content(), &content())
.await;
// Assert
assert_err!(outcome);
}
#[tokio::test]
async fn send_email_times_out_if_the_server_takes_too_long() {
// Arrange
let mock_server = MockServer::start().await;
let email_client = email_client(mock_server.uri());
let response = ResponseTemplate::new(200)
// 3 minutes!
.set_delay(std::time::Duration::from_secs(180));
Mock::given(any())
.respond_with(response)
.expect(1)
.mount(&mock_server)
.await;
// Act
let outcome = email_client
.send_email(email(), &subject(), &content(), &content())
.await; .await;
// Assert // Assert

View file

@ -25,10 +25,12 @@ async fn main() {
.email_client .email_client
.sender() .sender()
.expect("Invalid sender email address."); .expect("Invalid sender email address.");
let timeout = configuration.email_client.timeout();
let email_client = EmailClient::new( let email_client = EmailClient::new(
configuration.email_client.base_url, configuration.email_client.base_url,
sender_email, sender_email,
configuration.email_client.authorization_token, configuration.email_client.authorization_token,
timeout,
); );
startup::run(listener, connection_pool, email_client) startup::run(listener, connection_pool, email_client)
.await .await

View file

@ -153,10 +153,12 @@ async fn spawn_app() -> TestApp {
.email_client .email_client
.sender() .sender()
.expect("Invalid sender email address."); .expect("Invalid sender email address.");
let timeout = configuration.email_client.timeout();
let email_client = EmailClient::new( let email_client = EmailClient::new(
configuration.email_client.base_url, configuration.email_client.base_url,
sender_email, sender_email,
configuration.email_client.authorization_token, configuration.email_client.authorization_token,
timeout,
); );
let service = learn_axum::startup::app(connection_pool.clone(), email_client); let service = learn_axum::startup::app(connection_pool.clone(), email_client);