From 90fc6abf19245c2063b5431e0e28fc6f31f9471f Mon Sep 17 00:00:00 2001 From: Sandro Eiler Date: Tue, 5 Mar 2024 10:12:34 +0100 Subject: [PATCH] feat: add confirmation link to email --- Cargo.lock | 10 ++++++++++ Cargo.toml | 6 +----- src/routes/subscriptions.rs | 12 ++++++++++-- tests/api/subscriptions.rs | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14bb877..6ae4c9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1112,6 +1112,7 @@ dependencies = [ "config", "fake", "hyper 1.2.0", + "linkify", "once_cell", "quickcheck", "quickcheck_macros", @@ -1165,6 +1166,15 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linkify" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" +dependencies = [ + "memchr", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 5c621aa..fe81084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,10 @@ hyper = { version = "1.2.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1" serde-aux = "4" -# serde_with = "3" # Axum axum = { version = "0.7" } tower = { version = "0.4" } tower-http = { version = "0.5", features = ["trace", "request-id", "util"] } -# tower-cookies = "0.10" # Others config = "0.14" uuid = { version = "1", features = ["v4", "fast-rng"] } @@ -38,9 +36,6 @@ unicode-segmentation = "1" strum_macros = "0.26" validator = "0.16" -# async-trait = "0.1" -# strum_macros = "0.25" - [dependencies.sqlx] version = "0.7" default-features = false @@ -68,3 +63,4 @@ quickcheck_macros = "1.0.0" rand = "0.8.5" wiremock = "0.6.0" serde_json = "1" +linkify = "0.10" diff --git a/src/routes/subscriptions.rs b/src/routes/subscriptions.rs index 6dd1fbb..7167657 100644 --- a/src/routes/subscriptions.rs +++ b/src/routes/subscriptions.rs @@ -56,12 +56,20 @@ pub async fn subscribe( if insert_subscriber(&db_pool, &new_subscriber).await.is_err() { return (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong.").into_response(); } + let confirmation_link = "https://my-api.com/subscriptions/confirm"; if email_client .send_email( new_subscriber.email, "Welcome!", - "Welcome to our newsletter!", - "Welcome to our newsletter!", + &format!( + "Welcome to our newsletter!
\ + Click here to confirm your subscription.", + confirmation_link, + ), + &format!( + "Welcome to our newsletter! \nVisit {} to confirm your subscription.", + confirmation_link, + ), ) .await .is_err() diff --git a/tests/api/subscriptions.rs b/tests/api/subscriptions.rs index bfa8587..e9bae61 100644 --- a/tests/api/subscriptions.rs +++ b/tests/api/subscriptions.rs @@ -2,6 +2,41 @@ use crate::helpers::spawn_app; use wiremock::matchers::{method, path}; use wiremock::{Mock, ResponseTemplate}; +#[tokio::test] +async fn subscribe_sends_a_confirmation_email_with_a_link() { + //Arrange + let app = spawn_app().await; + let body = "name=le%20guin&email=ursula_le_guin%40gmail.com"; + + Mock::given(path("/email")) + .and(method("POST")) + .respond_with(ResponseTemplate::new(200)) + .mount(&app.email_server) + .await; + + // Act + app.post_subscriptions(body.into()).await; + + // Assert + // Get the first intercepted request + let email_request = &app.email_server.received_requests().await.unwrap()[0]; + // Parse the body as JSON, starting from raw bytes + let body: serde_json::Value = serde_json::from_slice(&email_request.body).unwrap(); + // Extract the link from one of the request fields. + let get_link = |s: &str| { + let links: Vec<_> = linkify::LinkFinder::new() + .links(s) + .filter(|l| *l.kind() == linkify::LinkKind::Url) + .collect(); + assert_eq!(links.len(), 1); + links[0].as_str().to_owned() + }; + let html_link = get_link(body["HtmlBody"].as_str().unwrap()); + let text_link = get_link(body["TextBody"].as_str().unwrap()); + // The two links should be identical + assert_eq!(html_link, text_link); +} + #[tokio::test] async fn subscribe_sends_a_confirmation_email_for_valid_data() { // Arrange