mirror of
https://github.com/kristoferssolo/zero2prod.git
synced 2025-10-21 20:10:40 +00:00
Finished chapter 7.2
This commit is contained in:
parent
2ebb54ef0f
commit
d8fb203a2f
859
Cargo.lock
generated
859
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ axum = "0.7"
|
||||
chrono = { version = "0.4", features = ["serde", "clock"] }
|
||||
config = { version = "0.14", features = ["toml"], default-features = false }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
sqlx = { version = "0.7", default-features = false, features = [
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
"runtime-tokio",
|
||||
"tls-rustls",
|
||||
"macros",
|
||||
|
||||
@ -12,3 +12,4 @@ database_name = "newsletter"
|
||||
base_url = "localhost"
|
||||
sender_email = "test@gmail.com"
|
||||
auth_token = "super-secret-token"
|
||||
timeout_milliseconds = 10000
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::fmt::Display;
|
||||
use std::{fmt::Display, time::Duration};
|
||||
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
use serde::Deserialize;
|
||||
@ -45,12 +45,17 @@ pub struct EmailClientSettings {
|
||||
pub base_url: String,
|
||||
pub sender_email: String,
|
||||
pub auth_token: Secret<String>,
|
||||
pub timeout_milliseconds: u64,
|
||||
}
|
||||
|
||||
impl EmailClientSettings {
|
||||
pub fn sender(&self) -> Result<SubscriberEmail, String> {
|
||||
self.sender_email.clone().try_into()
|
||||
}
|
||||
|
||||
pub fn timeout(&self) -> Duration {
|
||||
Duration::from_millis(self.timeout_milliseconds)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config() -> Result<Settings, config::ConfigError> {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::{Client, Url};
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
use serde::Serialize;
|
||||
@ -13,14 +15,21 @@ pub struct EmailClient {
|
||||
}
|
||||
|
||||
impl EmailClient {
|
||||
pub fn new(base_url: String, sender: SubscriberEmail, auth_token: Secret<String>) -> Self {
|
||||
pub fn new(
|
||||
base_url: String,
|
||||
sender: SubscriberEmail,
|
||||
auth_token: Secret<String>,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
let http_client = Client::builder().timeout(timeout).build().unwrap();
|
||||
Self {
|
||||
http_client: Client::new(),
|
||||
http_client,
|
||||
base_url,
|
||||
sender,
|
||||
auth_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_email(
|
||||
&self,
|
||||
recipient: SubscriberEmail,
|
||||
@ -38,13 +47,13 @@ impl EmailClient {
|
||||
html_body: html_content,
|
||||
text_body: text_content,
|
||||
};
|
||||
let builder = self
|
||||
.http_client
|
||||
self.http_client
|
||||
.post(url)
|
||||
.header("X-Postmark-Server-Token", self.auth_token.expose_secret())
|
||||
.json(&request_body)
|
||||
.send()
|
||||
.await?;
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -62,6 +71,9 @@ struct SendEmailRequest<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use claims::{assert_err, assert_ok};
|
||||
use fake::{
|
||||
faker::{
|
||||
internet::en::SafeEmail,
|
||||
@ -71,7 +83,7 @@ mod tests {
|
||||
};
|
||||
use serde_json;
|
||||
use wiremock::{
|
||||
matchers::{header, header_exists, method, path},
|
||||
matchers::{any, header, header_exists, method, path},
|
||||
Match, Mock, MockServer, ResponseTemplate,
|
||||
};
|
||||
|
||||
@ -93,12 +105,32 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn subject() -> String {
|
||||
Sentence(1..2).fake()
|
||||
}
|
||||
|
||||
fn content() -> String {
|
||||
Paragraph(1..10).fake()
|
||||
}
|
||||
|
||||
fn email() -> SubscriberEmail {
|
||||
SubscriberEmail::from_str(&SafeEmail().fake::<String>()).unwrap()
|
||||
}
|
||||
|
||||
fn email_client(base_url: String) -> EmailClient {
|
||||
EmailClient::new(
|
||||
base_url,
|
||||
email(),
|
||||
Secret::new(Faker.fake()),
|
||||
Duration::from_millis(200),
|
||||
)
|
||||
}
|
||||
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn send_email_sends_the_expectred_request() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let sender = SafeEmail().fake::<String>().try_into().unwrap();
|
||||
let email_client = EmailClient::new(mock_server.uri(), sender, Secret::new(Faker.fake()));
|
||||
let email_client = email_client(mock_server.uri());
|
||||
|
||||
Mock::given(header_exists("X-Postmark-Server-Token"))
|
||||
.and(header("Content-Type", "application/json"))
|
||||
@ -110,12 +142,61 @@ mod tests {
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let subscriber_email = SafeEmail().fake::<String>().try_into().unwrap();
|
||||
let subject = Sentence(1..2).fake::<String>();
|
||||
dbg!(&subject);
|
||||
let content = Paragraph(1..10).fake::<String>();
|
||||
let _ = email_client
|
||||
.send_email(subscriber_email, &subject, &content, &content)
|
||||
.send_email(email(), &subject(), &content(), &content())
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_email_succeds_if_server_returns_200() {
|
||||
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;
|
||||
|
||||
let outcome = email_client
|
||||
.send_email(email(), &subject(), &content(), &content())
|
||||
.await;
|
||||
|
||||
assert_ok!(outcome);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_email_fails_if_server_returns_500() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let email_client = email_client(mock_server.uri());
|
||||
|
||||
Mock::given(any())
|
||||
.respond_with(ResponseTemplate::new(500))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let outcome = email_client
|
||||
.send_email(email(), &subject(), &content(), &content())
|
||||
.await;
|
||||
|
||||
assert_err!(outcome);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn send_email_times_out_if_the_server_takes_too_long() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let email_client = email_client(mock_server.uri());
|
||||
|
||||
let response = ResponseTemplate::new(200).set_delay(Duration::from_secs(180));
|
||||
|
||||
Mock::given(any())
|
||||
.respond_with(response)
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
let outcome = email_client
|
||||
.send_email(email(), &subject(), &content(), &content())
|
||||
.await;
|
||||
assert_err!(outcome);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,10 +22,12 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
.email_client
|
||||
.sender()
|
||||
.expect("Invalid sender email adress");
|
||||
let timeout = config.email_client.timeout();
|
||||
let email_client = EmailClient::new(
|
||||
config.email_client.base_url,
|
||||
sender_email,
|
||||
config.email_client.auth_token,
|
||||
timeout,
|
||||
);
|
||||
|
||||
axum::serve(listener, route(pool, email_client)).await
|
||||
|
||||
@ -146,10 +146,12 @@ async fn spawn_app() -> TestApp {
|
||||
.email_client
|
||||
.sender()
|
||||
.expect("Invalid sender email adress");
|
||||
let timeout = config.email_client.timeout();
|
||||
let email_client = EmailClient::new(
|
||||
config.email_client.base_url,
|
||||
sender_email,
|
||||
config.email_client.auth_token,
|
||||
timeout,
|
||||
);
|
||||
|
||||
let _ = tokio::spawn(async move {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user