use crate::helpers::{spawn_app, ConfirmationLinks, TestApp}; use uuid::Uuid; use wiremock::matchers::{any, method, path}; use wiremock::{Mock, ResponseTemplate}; async fn create_unconfirmed_subscriber(app: &TestApp) -> ConfirmationLinks { let body = "name=le%20guin&email=ursula_le_guin%40gmail.com"; let _mock_guard = Mock::given(path("/email")) .and(method("POST")) .respond_with(ResponseTemplate::new(200)) .named("Create unconfirmed subscriber") .expect(1) .mount_as_scoped(&app.email_server) .await; app.post_subscriptions(body.into()) .await .error_for_status() .unwrap(); let email_request = &app .email_server .received_requests() .await .unwrap() .pop() .unwrap(); app.get_confirmation_links(email_request) } async fn create_confirmed_subscriber(app: &TestApp) { let confirmation_link = create_unconfirmed_subscriber(app).await.html; reqwest::get(confirmation_link) .await .unwrap() .error_for_status() .unwrap(); } #[tokio::test] async fn newsletters_are_not_delivered_to_unconfirmed_subscribers() { // Arrange let app = spawn_app().await; create_unconfirmed_subscriber(&app).await; Mock::given(any()) .respond_with(ResponseTemplate::new(200)) .expect(0) .mount(&app.email_server) .await; // Act let newsletter_request_body = serde_json::json!({ "title": "Newsletter title", "content": { "text": "Newsletter body as plain text", "html": "

Newsletter body as HTML

", } }); let response = app.post_newsletters(newsletter_request_body).await; // Assert assert_eq!(response.status().as_u16(), 200); // Mock verifies on Drop that we haven't sent the newsletter email } #[tokio::test] async fn newsletters_are_delivered_to_confirmed_subscribers() { // Arrange let app = spawn_app().await; create_confirmed_subscriber(&app).await; Mock::given(path("/email")) .and(method("POST")) .respond_with(ResponseTemplate::new(200)) .expect(1) .mount(&app.email_server) .await; // Act let newsletter_request_body = serde_json::json!({ "title": "Newsletter title", "content": { "text": "Newsletter body as plain text", "html": "

Newsletter body as HTML

", } }); let response = app.post_newsletters(newsletter_request_body).await; // Assert assert_eq!(response.status().as_u16(), 200); // Mock verifies on Drop that we have sent the newsletter email } #[tokio::test] async fn newsletters_returns_400_for_invalid_data() { // Arrange let app = spawn_app().await; let test_cases = vec![ ( serde_json::json!({ "content": { "text": "Newsletter body as plain text", "html": "

Newsletter body as HTML

", } }), "missing title", ), ( serde_json::json!({ "title": "Newsletter!" }), "missing content", ), ]; for (invalid_body, error_message) in test_cases { let response = app.post_newsletters(invalid_body).await; // Assert assert_eq!( 400, response.status().as_u16(), // Additional customised error message on test failure "The API did not fail with 400 Bad Request when the payload was {}.", error_message ); } } #[tokio::test] async fn requests_missing_authorization_are_rejected() { // Arrange let app = spawn_app().await; let response = reqwest::Client::new() .post(&format!("{}/newsletters", &app.address)) .json(&serde_json::json!({ "title": "Newsletter title", "content": { "text": "Newsletter body as plain text", "html": "

Newsletter body as HTML

", } })) .send() .await .expect("Failed to execute request."); // Assert assert_eq!(401, response.status().as_u16()); assert_eq!( r#"Basic realm="publish""#, response.headers()["WWW-Authenticate"] ); } #[tokio::test] async fn non_existing_user_is_rejected() { // Arrange let app = spawn_app().await; // Random credentials let username = Uuid::new_v4().to_string(); let password = Uuid::new_v4().to_string(); let response = reqwest::Client::new() .post(&format!("{}/newsletters", &app.address)) .basic_auth(username, Some(password)) .json(&serde_json::json!({ "title": "Newsletter title", "content": { "text": "Newsletter body as plain text", "html": "

Newsletter body as HTML

", } })) .send() .await .expect("Failed to execute request."); // Assert assert_eq!(401, response.status().as_u16()); assert_eq!( r#"Basic realm="publish""#, response.headers()["WWW-Authenticate"] ); } #[tokio::test] async fn invalid_password_is_rejected() { // Arrange let app = spawn_app().await; let username = &app.test_user.username; // Random password let password = Uuid::new_v4().to_string(); assert_ne!(app.test_user.password, password); let response = reqwest::Client::new() .post(&format!("{}/newsletters", &app.address)) .basic_auth(username, Some(password)) .json(&serde_json::json!({ "title": "Newsletter title", "content": { "text": "Newsletter body as plain text", "html": "

Newsletter body as HTML

", } })) .send() .await .expect("Failed to execute request."); // Assert assert_eq!(401, response.status().as_u16()); assert_eq!( r#"Basic realm="publish""#, response.headers()["WWW-Authenticate"] ); }