diff --git a/Cargo.toml b/Cargo.toml index 190c3c4..96143d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,3 +67,4 @@ quickcheck = "1.0.3" quickcheck_macros = "1.0.0" rand = "0.8.5" wiremock = "0.6.0" +serde_json = "1" diff --git a/src/email_client.rs b/src/email_client.rs index 4de227e..bcdeb63 100644 --- a/src/email_client.rs +++ b/src/email_client.rs @@ -40,7 +40,7 @@ impl EmailClient { html_body: html_content.to_owned(), text_body: text_content.to_owned(), }; - let builder = self + let _builder = self .http_client .post(&url) .header( @@ -54,6 +54,7 @@ impl EmailClient { } } #[derive(serde::Serialize)] +#[serde(rename_all = "PascalCase")] struct SendEmailRequest { from: String, to: String, @@ -71,17 +72,43 @@ mod tests { use fake::faker::lorem::en::{Paragraph, Sentence}; use fake::{Fake, Faker}; use secrecy::Secret; - use wiremock::matchers::any; - use wiremock::{Mock, MockServer, ResponseTemplate}; + use wiremock::matchers::{header, header_exists, method, path}; + use wiremock::{Mock, MockServer, Request, ResponseTemplate}; + + struct SendEmailBodyMatcher; + + impl wiremock::Match for SendEmailBodyMatcher { + fn matches(&self, request: &Request) -> bool { + // Try to parse the body as a JSON value + let result: Result = serde_json::from_slice(&request.body); + if let Ok(body) = result { + dbg!(&body); + // Check that all the mandatory fields are populated + // without inspecting the field values + body.get("From").is_some() + && body.get("To").is_some() + && body.get("Subject").is_some() + && body.get("HtmlBody").is_some() + && body.get("TextBody").is_some() + } else { + // If parsing failed, do not match the request + false + } + } + } #[tokio::test] - async fn send_email_fires_a_request_to_base_url() { + async fn send_email_sends_the_expected_request() { // Arrange let mock_server = MockServer::start().await; let sender = SubscriberEmail::parse(SafeEmail().fake()).unwrap(); let email_client = EmailClient::new(mock_server.uri(), sender, Secret::new(Faker.fake())); - Mock::given(any()) + Mock::given(header_exists("X-Postmark-Server-Token")) + .and(header("Content-Type", "application/json")) + .and(path("/email")) + .and(method("POST")) + .and(SendEmailBodyMatcher) .respond_with(ResponseTemplate::new(200)) .expect(1) .mount(&mock_server) @@ -97,5 +124,6 @@ mod tests { .await; // Assert + // Mock expectations are checked on drop } } diff --git a/src/routes/subscriptions.rs b/src/routes/subscriptions.rs index c3ffd1c..d8aa4c2 100644 --- a/src/routes/subscriptions.rs +++ b/src/routes/subscriptions.rs @@ -1,6 +1,5 @@ use crate::domain::SubscriberEmail; use crate::domain::{NewSubscriber, SubscriberName}; -use crate::email_client; use crate::startup::AppState; use axum::extract::State; use axum::routing::post;