Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
Add links to confirmation email
Browse files Browse the repository at this point in the history
  • Loading branch information
0rzech committed Mar 1, 2024
1 parent 8380c52 commit c966986
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 10 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ validator = "0.16.1"
[dev-dependencies]
claims = "0.7.1"
fake = "2.9.2"
linkify = "0.10.0"
proptest = "1.4.0"
serde_json = "1.0.114"
tokio = { version = "1.36.0", features = ["macros", "rt"] }
Expand Down
33 changes: 25 additions & 8 deletions src/routes/subscriptions.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{
app_state::AppState,
domain::{NewSubscriber, SubscriberEmail, SubscriberName},
email_client::EmailClient,
};
use axum::{extract::State, http::StatusCode, routing::post, Form, Router};
use reqwest::Error;
use serde::Deserialize;
use sqlx::PgPool;
use time::OffsetDateTime;
Expand Down Expand Up @@ -36,14 +38,7 @@ async fn subscribe(State(app_state): State<AppState>, Form(form): Form<FormData>
return StatusCode::INTERNAL_SERVER_ERROR;
}

if app_state
.email_client
.send_email(
new_subscriber.email,
"Welcome!",
"Welcome to our newsletter!",
"Welcome to our newsletter!",
)
if send_confirmation_email(&app_state.email_client, new_subscriber)
.await
.is_err()
{
Expand Down Expand Up @@ -81,6 +76,28 @@ async fn insert_subscriber(
Ok(())
}

#[tracing::instrument(
name = "Sending confirmation email to a new subscriber",
skip(email_client, new_subscriber)
)]
async fn send_confirmation_email(
email_client: &EmailClient,
new_subscriber: NewSubscriber,
) -> Result<(), Error> {
let confirmation_link = "https://there-is-no-such-domain.com/subscriptions/confirm";
let html_body = format!(
"Welcome to our newsletter!<br/>\
Click <a href=\"{confirmation_link}\">here</a> to confirm your subscription."
);
let plain_body = format!(
"Welcome to our newsletter!\nVisit {confirmation_link} to confirm your subscription."
);

email_client
.send_email(new_subscriber.email, "Welcome!", &html_body, &plain_body)
.await
}

#[derive(Deserialize)]
struct FormData {
name: String,
Expand Down
38 changes: 36 additions & 2 deletions tests/api/subscriptions.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::helpers::TestApp;
use linkify::{LinkFinder, LinkKind};
use serde_json::Value;
use wiremock::{
matchers::{method, path},
Mock, ResponseTemplate,
};

use crate::helpers::TestApp;

#[tokio::test]
async fn subscribe_returns_a_200_for_valid_form_data() {
// given
Expand Down Expand Up @@ -105,3 +106,36 @@ async fn subscribe_sends_a_confirmation_email_for_valid_data() {

// then assert
}

#[tokio::test]
async fn subscribe_sends_a_confirmation_email_with_a_link() {
// given
let app = TestApp::spawn().await;
let body = "name=Imi%C4%99%20Nazwisko&email=imie.nazwisko%40example.com";

Mock::given(path("/email"))
.and(method("POST"))
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&app.email_server)
.await;

// when
app.post_subscriptions(body.into()).await;

// then
let request = &app.email_server.received_requests().await.unwrap()[0];
let body: Value = serde_json::from_slice(&request.body).unwrap();
let get_link = |s: &str| {
let links: Vec<_> = LinkFinder::new()
.links(s)
.filter(|l| *l.kind() == 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());

assert_eq!(html_link, text_link);
}

0 comments on commit c966986

Please sign in to comment.