Test suite refactoring to match new htmx HTML swapping in pages

This commit is contained in:
Alphonse Paix
2025-09-17 14:16:27 +02:00
parent 859247d900
commit b5f0f448d7
8 changed files with 91 additions and 163 deletions

View File

@@ -21,14 +21,12 @@ async fn logout_clears_session_state() {
assert_is_redirect_to(&response, "/admin/dashboard");
let html_page = app.get_admin_dashboard_html().await;
assert!(html_page.contains(&format!("Welcome {}", app.test_user.username)));
assert!(html_page.contains("Connected as"));
assert!(html_page.contains(&app.test_user.username));
let response = app.post_logout().await;
assert_is_redirect_to(&response, "/login");
let html_page = app.get_login_html().await;
assert!(html_page.contains("You have successfully logged out"));
let response = app.get_admin_dashboard().await;
assert_is_redirect_to(&response, "/login");
}

View File

@@ -1,15 +1,5 @@
use uuid::Uuid;
use crate::helpers::{TestApp, assert_is_redirect_to};
#[tokio::test]
async fn you_must_be_logged_in_to_see_the_change_password_form() {
let app = TestApp::spawn().await;
let response = app.get_change_password().await;
assert_is_redirect_to(&response, "/login");
}
use uuid::Uuid;
#[tokio::test]
async fn you_must_be_logged_in_to_change_your_password() {
@@ -46,10 +36,10 @@ async fn new_password_fields_must_match() {
"new_password_check": another_new_password,
}))
.await;
assert_is_redirect_to(&response, "/admin/password");
assert!(response.status().is_success());
let html_page = app.get_change_password_html().await;
assert!(html_page.contains("You entered two different passwords"));
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains("You entered two different passwords"));
}
#[tokio::test]
@@ -70,10 +60,10 @@ async fn current_password_is_invalid() {
"new_password_check": new_password,
}))
.await;
assert_is_redirect_to(&response, "/admin/password");
assert!(response.status().is_success());
let html_page = app.get_change_password_html().await;
assert!(html_page.contains("The current password is incorrect"));
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains("The current password is incorrect"));
}
#[tokio::test]
@@ -95,17 +85,14 @@ async fn changing_password_works() {
"new_password_check": new_password,
}))
.await;
assert_is_redirect_to(&response, "/admin/password");
assert!(response.status().is_success());
let html_page = app.get_change_password_html().await;
assert!(html_page.contains("Your password has been changed"));
let html_page_fragment = response.text().await.unwrap();
assert!(html_page_fragment.contains("Your password has been changed"));
let response = app.post_logout().await;
assert_is_redirect_to(&response, "/login");
let html_page = app.get_login_html().await;
assert!(html_page.contains("You have successfully logged out"));
let login_body = &serde_json::json!({
"username": app.test_user.username,
"password": new_password,

View File

@@ -6,7 +6,10 @@ use linkify::LinkFinder;
use once_cell::sync::Lazy;
use sqlx::{Connection, Executor, PgConnection, PgPool};
use uuid::Uuid;
use wiremock::MockServer;
use wiremock::{
Mock, MockBuilder, MockServer,
matchers::{method, path},
};
use zero2prod::{
configuration::{DatabaseSettings, get_configuration},
email_client::EmailClient,
@@ -149,17 +152,6 @@ impl TestApp {
ConfirmationLinks { html, text }
}
pub async fn get_login_html(&self) -> String {
self.api_client
.get(format!("{}/login", &self.address))
.send()
.await
.expect("Failed to execute request")
.text()
.await
.unwrap()
}
pub async fn get_admin_dashboard(&self) -> reqwest::Response {
self.api_client
.get(format!("{}/admin/dashboard", &self.address))
@@ -172,29 +164,6 @@ impl TestApp {
self.get_admin_dashboard().await.text().await.unwrap()
}
pub async fn get_register_html(&self) -> String {
self.api_client
.get(format!("{}/register", &self.address))
.send()
.await
.expect("Failed to execute request")
.text()
.await
.unwrap()
}
pub async fn get_change_password(&self) -> reqwest::Response {
self.api_client
.get(format!("{}/admin/password", &self.address))
.send()
.await
.expect("Failed to execute request")
}
pub async fn get_change_password_html(&self) -> String {
self.get_change_password().await.text().await.unwrap()
}
pub async fn post_subscriptions(&self, body: String) -> reqwest::Response {
self.api_client
.post(format!("{}/subscriptions", self.address))
@@ -205,18 +174,6 @@ impl TestApp {
.expect("Failed to execute request")
}
pub async fn get_newsletter_form(&self) -> reqwest::Response {
self.api_client
.get(format!("{}/admin/password", &self.address))
.send()
.await
.expect("Failed to execute request")
}
pub async fn get_newsletter_form_html(&self) -> String {
self.get_newsletter_form().await.text().await.unwrap()
}
pub async fn post_newsletters<Body>(&self, body: &Body) -> reqwest::Response
where
Body: serde::Serialize,
@@ -291,6 +248,11 @@ async fn configure_database(config: &DatabaseSettings) -> PgPool {
}
pub fn assert_is_redirect_to(response: &reqwest::Response, location: &str) {
assert_eq!(response.status().as_u16(), 303);
assert_eq!(response.headers().get("Location").unwrap(), location);
dbg!(&response);
assert_eq!(response.status().as_u16(), 200);
assert_eq!(response.headers().get("hx-redirect").unwrap(), location);
}
pub fn when_sending_an_email() -> MockBuilder {
Mock::given(path("/email")).and(method("POST"))
}

View File

@@ -1,14 +1,8 @@
use crate::helpers::{ConfirmationLinks, TestApp, assert_is_redirect_to};
use fake::{
Fake,
faker::{internet::en::SafeEmail, name::fr_fr::Name},
};
use crate::helpers::{ConfirmationLinks, TestApp, assert_is_redirect_to, when_sending_an_email};
use fake::{Fake, faker::internet::en::SafeEmail};
use std::time::Duration;
use uuid::Uuid;
use wiremock::{
Mock, MockBuilder, ResponseTemplate,
matchers::{method, path},
};
use wiremock::ResponseTemplate;
#[tokio::test]
async fn newsletters_are_not_delivered_to_unconfirmed_subscribers() {
@@ -72,11 +66,11 @@ async fn newsletters_are_delivered_to_confirmed_subscribers() {
});
let response = app.post_newsletters(&newsletter_request_body).await;
assert_is_redirect_to(&response, "/admin/newsletters");
assert!(response.status().is_success());
let html_page = app.get_newsletter_form_html().await;
assert!(html_page.contains(&format!(
"The newsletter issue '{}' has been published",
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains(&format!(
r#"The newsletter issue &#34;{}&#34; has been published"#,
newsletter_title
)));
@@ -116,9 +110,13 @@ async fn form_shows_error_for_invalid_data() {
];
for (invalid_body, error_message) in test_cases {
app.post_newsletters(&invalid_body).await;
let html_page = app.get_newsletter_form_html().await;
assert!(html_page.contains(error_message));
let html_fragment = app
.post_newsletters(&invalid_body)
.await
.text()
.await
.unwrap();
assert!(html_fragment.contains(error_message));
}
}
@@ -143,20 +141,20 @@ async fn newsletter_creation_is_idempotent() {
});
let response = app.post_newsletters(&newsletter_request_body).await;
assert_is_redirect_to(&response, "/admin/newsletters");
assert!(response.status().is_success());
let html_page = app.get_newsletter_form_html().await;
assert!(html_page.contains(&format!(
"The newsletter issue '{}' has been published",
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_is_redirect_to(&response, "/admin/newsletters");
assert!(response.status().is_success());
let html_page = app.get_newsletter_form_html().await;
assert!(html_page.contains(&format!(
"The newsletter issue '{}' has been published",
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains(&format!(
r#"The newsletter issue &#34;{}&#34; has been published"#,
newsletter_title
)));
@@ -195,22 +193,16 @@ async fn concurrent_form_submission_is_handled_gracefully() {
}
async fn create_unconfirmed_subscriber(app: &TestApp) -> ConfirmationLinks {
let name: String = Name().fake();
let email: String = SafeEmail().fake();
let body = serde_urlencoded::to_string(serde_json::json!({
"name": name,
"email": email,
"email_check": email
}))
.unwrap();
let body = format!("email={email}");
let _mock_guard = Mock::given(path("/v1/email"))
.and(method("POST"))
let _mock_guard = when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.named("Create unconfirmed subscriber")
.expect(1)
.mount_as_scoped(&app.email_server)
.await;
app.post_subscriptions(body)
.await
.error_for_status()
@@ -223,6 +215,7 @@ async fn create_unconfirmed_subscriber(app: &TestApp) -> ConfirmationLinks {
.unwrap()
.pop()
.unwrap();
app.get_confirmation_links(email_request)
}
@@ -234,7 +227,3 @@ async fn create_confirmed_subscriber(app: &TestApp) {
.error_for_status()
.unwrap();
}
fn when_sending_an_email() -> MockBuilder {
Mock::given(path("/v1/email")).and(method("POST"))
}

View File

@@ -1,34 +1,11 @@
use crate::helpers::{TestApp, assert_is_redirect_to};
use wiremock::{
Mock, ResponseTemplate,
matchers::{method, path},
};
use crate::helpers::{TestApp, when_sending_an_email};
use wiremock::ResponseTemplate;
#[tokio::test]
async fn subscribe_displays_a_confirmation_message_for_valid_form_data() {
let app = TestApp::spawn().await;
Mock::given(path("/v1/email"))
.and(method("POST"))
.respond_with(ResponseTemplate::new(200))
.mount(&app.email_server)
.await;
let email = "alphonse.paix@outlook.com";
let body = format!("name=Alphonse&email={0}&email_check={0}", email);
let response = app.post_subscriptions(body).await;
assert_is_redirect_to(&response, "/register");
let page_html = app.get_register_html().await;
assert!(page_html.contains("A confirmation email has been sent"));
}
#[tokio::test]
async fn subscribe_persists_the_new_subscriber() {
let app = TestApp::spawn().await;
Mock::given(path("/v1/email"))
.and(method("POST"))
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.mount(&app.email_server)
.await;
@@ -37,9 +14,27 @@ async fn subscribe_persists_the_new_subscriber() {
let body = format!("email={email}");
let response = app.post_subscriptions(body).await;
assert_is_redirect_to(&response, "/register");
let page_html = app.get_register_html().await;
assert!(page_html.contains("A confirmation email has been sent"));
assert!(response.status().is_success());
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains("A confirmation email has been sent"));
}
#[tokio::test]
async fn subscribe_persists_the_new_subscriber() {
let app = TestApp::spawn().await;
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.mount(&app.email_server)
.await;
let email = "alphonse.paix@outlook.com";
let body = format!("email={email}");
let response = app.post_subscriptions(body).await;
assert!(response.status().is_success());
let html_fragment = response.text().await.unwrap();
assert!(html_fragment.contains("A confirmation email has been sent"));
let saved = sqlx::query!("SELECT email, status FROM subscriptions")
.fetch_one(&app.connection_pool)
@@ -100,10 +95,9 @@ async fn subscribe_sends_a_confirmation_email_for_valid_data() {
let app = TestApp::spawn().await;
let email = "alphonse.paix@outlook.com";
let body = format!("name=Alphonse&email={0}&email_check={0}", email);
let body = format!("email={email}");
Mock::given(path("v1/email"))
.and(method("POST"))
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&app.email_server)
@@ -117,10 +111,9 @@ async fn subscribe_sends_a_confirmation_email_with_a_link() {
let app = TestApp::spawn().await;
let email = "alphonse.paix@outlook.com";
let body = format!("name=Alphonse&email={0}&email_check={0}", email);
let body = format!("email={email}");
Mock::given(path("v1/email"))
.and(method("POST"))
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&app.email_server)

View File

@@ -1,8 +1,5 @@
use crate::helpers::TestApp;
use wiremock::{
Mock, ResponseTemplate,
matchers::{method, path},
};
use crate::helpers::{TestApp, when_sending_an_email};
use wiremock::ResponseTemplate;
#[tokio::test]
async fn confirmation_links_without_token_are_rejected_with_a_400() {
@@ -15,14 +12,13 @@ async fn confirmation_links_without_token_are_rejected_with_a_400() {
}
#[tokio::test]
async fn clicking_on_the_link_shows_a_confiramtion_message() {
async fn clicking_on_the_link_shows_a_confirmation_message() {
let app = TestApp::spawn().await;
let email = "alphonse.paix@outlook.com";
let body = format!("name=Alphonse&email={email}&email_check={email}");
let body = format!("email={email}");
Mock::given(path("v1/email"))
.and(method("POST"))
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&app.email_server)
@@ -39,7 +35,7 @@ async fn clicking_on_the_link_shows_a_confiramtion_message() {
.text()
.await
.unwrap()
.contains("Your account has been confirmed")
.contains("Subscription confirmed")
);
}
@@ -50,8 +46,7 @@ async fn clicking_on_the_confirmation_link_confirms_a_subscriber() {
let email = "alphonse.paix@outlook.com";
let body = format!("email={email}");
Mock::given(path("v1/email"))
.and(method("POST"))
when_sending_an_email()
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&app.email_server)