use crate::helpers::{ TestApp, assert_is_redirect_to, content, fake_post_body, subject, when_sending_an_email, }; use sqlx::PgPool; use uuid::Uuid; use wiremock::ResponseTemplate; #[sqlx::test] async fn you_must_be_logged_in_to_create_a_new_post(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; let title = subject(); let content = content(); let body = serde_json::json!({ "title": title, "content": content, }); let response = app.post_create_post(&body).await; assert_is_redirect_to(&response, "/login"); } #[sqlx::test] async fn new_posts_are_stored_in_the_database(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.admin_login().await; let title = subject(); let content = content(); let body = serde_json::json!({ "title": title, "content": content, "idempotency_key": Uuid::new_v4(), }); let response = app.post_create_post(&body).await; assert!(response.status().is_success()); let html_fragment = response.text().await.unwrap(); assert!(html_fragment.contains("Your new post has been published")); let saved = sqlx::query!("SELECT title, content FROM posts") .fetch_one(&app.connection_pool) .await .expect("Failed to fetch saved post"); assert_eq!(saved.title, title); assert_eq!(saved.content, content); } #[sqlx::test] async fn confirmed_subscribers_are_notified_when_a_new_post_is_published(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.create_unconfirmed_subscriber().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 title = subject(); let content = content(); let body = serde_json::json!({ "title": title, "content": content, "idempotency_key": Uuid::new_v4(), }); app.post_create_post(&body).await; app.dispatch_all_pending_emails().await; } #[sqlx::test] async fn notification_contains_the_blog_post_url(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.create_confirmed_subscriber().await; app.admin_login().await; let title = subject(); let content = content(); let body = serde_json::json!({ "title": title, "content": content, "idempotency_key": Uuid::new_v4(), }); app.post_create_post(&body).await; when_sending_an_email() .respond_with(ResponseTemplate::new(200)) .expect(1) .mount(&app.email_server) .await; app.dispatch_all_pending_emails().await; let email_request = app .email_server .received_requests() .await .unwrap() .pop() .unwrap(); let post_id = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap() .post_id; let links = app.get_post_urls(&email_request); let text = String::from_utf8(email_request.body).unwrap(); assert!(text.contains(&title)); assert!(links.html.as_str().contains(&post_id.to_string())); } #[sqlx::test] async fn new_posts_are_visible_on_the_website(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; let html = app.get_posts_html().await; assert!(html.contains("No posts yet")); let title = subject(); let content = content(); let body = serde_json::json!({ "title": title, "content": content, "idempotency_key": Uuid::new_v4(), }); app.admin_login().await; app.post_create_post(&body).await; app.logout().await; let html = app.get_posts_html().await; assert!(html.contains(&title)); let post = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap(); let html = app.get_post_html(&post.post_id).await; assert!(html.contains(&title)); } #[sqlx::test] async fn visitor_can_read_a_blog_post(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; let title = subject(); let content = content(); let body = serde_json::json!({ "title": title, "content": content, "idempotency_key": Uuid::new_v4(), }); app.admin_login().await; app.post_create_post(&body).await; app.logout().await; let html = app.get_posts_html().await; assert!(html.contains(&title)); let post = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap(); let html = app.get_post_html(&post.post_id).await; assert!(html.contains(&title)); } #[sqlx::test] async fn a_deleted_blog_post_returns_404(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.admin_login().await; let title = subject(); let content = content(); let body = serde_json::json!({ "title": title, "content": content, "idempotency_key": Uuid::new_v4(), }); app.post_create_post(&body).await; let post = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap(); app.delete_post(post.post_id).await; let html = app.get_post_html(&post.post_id).await; assert!(html.contains("Not Found")); } #[sqlx::test] async fn clicking_the_notification_link_marks_the_email_as_opened(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.admin_login().await; app.create_confirmed_subscriber().await; app.post_create_post(&fake_post_body()).await; when_sending_an_email() .respond_with(ResponseTemplate::new(200)) .expect(1) .mount(&app.email_server) .await; app.dispatch_all_pending_emails().await; let email_request = app .email_server .received_requests() .await .unwrap() .pop() .unwrap(); let links = app.get_post_urls(&email_request); reqwest::get(links.html.as_str()).await.unwrap(); assert!( sqlx::query!("SELECT opened FROM notifications_delivered") .fetch_one(&app.connection_pool) .await .unwrap() .opened ); } #[sqlx::test] async fn only_post_author_can_access_the_edit_form(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.admin_login().await; let username = "alphonse"; let password = "123456789abc"; app.create_user(username, password, false).await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; app.post_create_post(&fake_post_body()).await; let post_id = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap() .post_id; let html = app.get_post_html(&post_id).await; assert!(html.contains("Edit")); app.logout().await; app.admin_login().await; let html = app.get_post_html(&post_id).await; assert!(!html.contains("Edit")); } #[sqlx::test] async fn only_post_author_can_edit_post(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.admin_login().await; let username = "alphonse"; let password = "123456789abc"; app.create_user(username, password, false).await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; app.post_create_post(&fake_post_body()).await; let post_id = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap() .post_id; let new_title = "Stunning new title"; let new_content = "Astonishing content"; let edit_body = serde_json::json!({ "title": new_title, "content": new_content, }); let response = app.edit_post(&edit_body, &post_id).await; let text = response.text().await.unwrap(); assert!(text.contains("Your changes have been saved")); let text = app.get_post_html(&post_id).await; assert!(text.contains(new_title)); assert!(text.contains(new_content)); app.logout().await; app.admin_login().await; let response = app.edit_post(&edit_body, &post_id).await; let text = response.text().await.unwrap(); assert!(text.contains("You are not authorized.")); } #[sqlx::test] async fn invalid_fields_are_rejected(connection_pool: PgPool) { let app = TestApp::spawn(connection_pool).await; app.admin_login().await; app.post_create_post(&fake_post_body()).await; let post_id = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap() .post_id; let test_cases = [ ( serde_json::json!({ "title": "", "content": "content" }), "Title must be at least one character", "title was empty", ), ( serde_json::json!({ "title": "Title", "content": "" }), "Content must be at least one character", "content was empty", ), ]; for (invalid_body, expected_error_message, explaination) in test_cases { let response = app.edit_post(&invalid_body, &post_id).await; let text = response.text().await.unwrap(); assert!( text.contains(expected_error_message), "The API did not reject the changes when the {}", explaination ); } }