Files
zero2prod/tests/api/newsletters.rs
Alphonse Paix 54218f92a9 Admin can now write posts
Posts can be displayed on the website. Subscribers are automatically
notified by email. This gives the opportunity to track explicitly how
many people followed the link provided in the emails sent without being
intrusive (no invisible image).
2025-09-18 17:22:33 +02:00

193 lines
5.8 KiB
Rust

use crate::helpers::{TestApp, assert_is_redirect_to, when_sending_an_email};
use std::time::Duration;
use uuid::Uuid;
use wiremock::ResponseTemplate;
#[tokio::test]
async fn newsletters_are_not_delivered_to_unconfirmed_subscribers() {
let app = TestApp::spawn().await;
app.create_unconfirmed_subscriber().await;
app.admin_login().await;
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(0)
.mount(&app.email_server)
.await;
let newsletter_request_body = serde_json::json!({
"title": "Newsletter title",
"text": "Newsletter body as plain text",
"html": "<p>Newsletter body as HTML</p>"
});
app.post_newsletters(&newsletter_request_body).await;
app.dispatch_all_pending_emails().await;
}
#[tokio::test]
async fn requests_without_authentication_are_redirected() {
let app = TestApp::spawn().await;
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(0)
.mount(&app.email_server)
.await;
let newsletter_request_body = serde_json::json!({
"title": "Newsletter title",
"text": "Newsletter body as plain text",
"html": "<p>Newsletter body as HTML</p>"
});
let response = app.post_newsletters(&newsletter_request_body).await;
assert_is_redirect_to(&response, "/login");
}
#[tokio::test]
async fn newsletters_are_delivered_to_confirmed_subscribers() {
let app = TestApp::spawn().await;
app.create_confirmed_subscriber().await;
app.admin_login().await;
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&app.email_server)
.await;
let newsletter_title = "Newsletter title";
let newsletter_request_body = serde_json::json!({
"title": newsletter_title,
"text": "Newsletter body as plain text",
"html": "<p>Newsletter body as HTML</p>",
"idempotency_key": Uuid::new_v4().to_string(),
});
let response = app.post_newsletters(&newsletter_request_body).await;
assert!(response.status().is_success());
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains(&format!(
r#"The newsletter issue &#34;{}&#34; has been published"#,
newsletter_title
)));
app.dispatch_all_pending_emails().await;
}
#[tokio::test]
async fn form_shows_error_for_invalid_data() {
let app = TestApp::spawn().await;
app.admin_login().await;
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(0)
.mount(&app.email_server)
.await;
let test_cases = [
(
serde_json::json!({
"title": "",
"text": "Newsletter body as plain text",
"html": "<p>Newsletter body as HTML</p>",
"idempotency_key": Uuid::new_v4().to_string(),
}),
"The title was empty",
),
(
serde_json::json!({
"title": "Newsletter",
"text": "",
"html": "",
"idempotency_key": Uuid::new_v4().to_string(),
}),
"The content was empty",
),
];
for (invalid_body, error_message) in test_cases {
let html_fragment = app
.post_newsletters(&invalid_body)
.await
.text()
.await
.unwrap();
assert!(html_fragment.contains(error_message));
}
}
#[tokio::test]
async fn newsletter_creation_is_idempotent() {
let app = TestApp::spawn().await;
app.create_confirmed_subscriber().await;
app.admin_login().await;
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&app.email_server)
.await;
let newsletter_title = "Newsletter title";
let newsletter_request_body = serde_json::json!({
"title": newsletter_title,
"text": "Newsletter body as plain text",
"html": "<p>Newsletter body as HTML</p>",
"idempotency_key": Uuid::new_v4().to_string(),
});
let response = app.post_newsletters(&newsletter_request_body).await;
assert!(response.status().is_success());
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains(&format!(
r#"The newsletter issue &#34;{}&#34; has been published"#,
newsletter_title
)));
let response = app.post_newsletters(&newsletter_request_body).await;
assert!(response.status().is_success());
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains(&format!(
r#"The newsletter issue &#34;{}&#34; has been published"#,
newsletter_title
)));
app.dispatch_all_pending_emails().await;
}
#[tokio::test]
async fn concurrent_form_submission_is_handled_gracefully() {
let app = TestApp::spawn().await;
app.create_confirmed_subscriber().await;
app.admin_login().await;
when_sending_an_email()
.respond_with(ResponseTemplate::new(200).set_delay(Duration::from_secs(2)))
.expect(1)
.mount(&app.email_server)
.await;
let newsletter_request_body = serde_json::json!({
"title": "Newsletter title",
"text": "Newsletter body as plain text",
"html": "<p>Newsletter body as HTML</p>",
"idempotency_key": Uuid::new_v4().to_string(),
});
let response1 = app.post_newsletters(&newsletter_request_body);
let response2 = app.post_newsletters(&newsletter_request_body);
let (response1, response2) = tokio::join!(response1, response2);
assert_eq!(response1.status(), response2.status());
assert_eq!(
response1.text().await.unwrap(),
response2.text().await.unwrap(),
);
app.dispatch_all_pending_emails().await;
}