use crate::helpers::{TestApp, fake_newsletter_body, fake_post_body, when_sending_an_email}; use sqlx::PgPool; use wiremock::ResponseTemplate; use zero2prod::authentication::Role; #[sqlx::test] async fn admin_can_create_user(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 record = sqlx::query!("SELECT user_id FROM users WHERE username = $1", username) .fetch_optional(&app.connection_pool) .await .unwrap(); assert!(record.is_some()); let html = app.get_admin_dashboard_html().await; assert!(html.contains(username)); } #[sqlx::test] async fn admin_can_create_admin_user(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, true).await; let record = sqlx::query!( r#" SELECT role as "role: Role" FROM users WHERE username = $1 "#, username ) .fetch_one(&app.connection_pool) .await .unwrap(); matches!(record.role, Role::Admin); app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let html = app.get_admin_dashboard_html().await; assert!(html.contains("Administration")); } #[sqlx::test] async fn admin_users_can_create_posts(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, true).await; app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; app.create_confirmed_subscriber().await; when_sending_an_email() .respond_with(ResponseTemplate::new(200)) .expect(1) .mount(&app.email_server) .await; app.post_create_post(&fake_post_body()).await; app.dispatch_all_pending_emails().await; } #[sqlx::test] async fn admin_users_can_send_emails(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, true).await; app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; app.create_confirmed_subscriber().await; when_sending_an_email() .respond_with(ResponseTemplate::new(200)) .expect(1) .mount(&app.email_server) .await; app.post_newsletters(&fake_newsletter_body()).await; app.dispatch_all_pending_emails().await; } #[sqlx::test] async fn admin_users_can_create_users(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, true).await; app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let username = "other_user"; app.create_user(username, password, true).await; let html = app.get_admin_dashboard_html().await; assert!(html.contains(username)); } #[sqlx::test] async fn admin_users_can_delete_contents(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, true).await; app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; app.create_confirmed_subscriber().await; let (subscriber_id, email) = { let record = sqlx::query!("SELECT id, email FROM subscriptions") .fetch_one(&app.connection_pool) .await .unwrap(); (record.id, record.email) }; let response = app.delete_subscriber(subscriber_id).await; let text = response.text().await.unwrap(); assert!(text.contains(&email)); assert!(text.contains("has been deleted")); app.create_user("other_user", password, true).await; let user_id = { let record = sqlx::query!("SELECT user_id FROM users") .fetch_one(&app.connection_pool) .await .unwrap(); record.user_id }; let response = app.delete_user(user_id).await; let text = response.text().await.unwrap(); assert!(text.contains("The user has been deleted")); app.post_create_post(&fake_post_body()).await; let post_id = { let record = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap(); record.post_id }; let comment_body = serde_json::json!({ "author": "author", "content": "comment", "idempotency_key": "key", }); app.post_comment(&post_id, &comment_body).await; let comment_id = { let record = sqlx::query!("SELECT comment_id FROM comments") .fetch_one(&app.connection_pool) .await .unwrap(); record.comment_id }; let response = app.delete_comment(comment_id).await; assert!( response .text() .await .unwrap() .contains("The comment has been deleted") ); let response = app.delete_post(post_id).await; let text = response.text().await.unwrap(); assert!(text.contains("The post has been deleted")); } #[sqlx::test] async fn admin_functions_are_hidden_for_non_admin_users(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 record = sqlx::query!( r#" SELECT role as "role: Role" FROM users WHERE username = $1 "#, username ) .fetch_one(&app.connection_pool) .await .unwrap(); matches!(record.role, Role::Writer); app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); let response = app.post_login(&login_body).await; assert!(!response.text().await.unwrap().contains("Administration")); } #[sqlx::test] async fn writers_can_publish_posts_and_send_emails(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; app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; app.create_confirmed_subscriber().await; when_sending_an_email() .respond_with(ResponseTemplate::new(200)) .expect(2) .mount(&app.email_server) .await; app.post_create_post(&fake_post_body()).await; app.post_newsletters(&fake_newsletter_body()).await; app.dispatch_all_pending_emails().await; } #[sqlx::test] async fn writers_cannot_perform_admin_functions(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; app.post_create_post(&fake_post_body()).await; let post_id = { let record = sqlx::query!("SELECT post_id FROM posts") .fetch_one(&app.connection_pool) .await .unwrap(); record.post_id }; app.create_confirmed_subscriber().await; let subscriber_id = { let record = sqlx::query!("SELECT id FROM subscriptions") .fetch_one(&app.connection_pool) .await .unwrap(); record.id }; let comment_body = serde_json::json!({ "author": "author", "content": "comment", "idempotency_key": "key", }); app.post_comment(&post_id, &comment_body).await; let comment_id = { let record = sqlx::query!("SELECT comment_id FROM comments") .fetch_one(&app.connection_pool) .await .unwrap(); record.comment_id }; app.logout().await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let response = app.delete_subscriber(subscriber_id).await; let html = response.text().await.unwrap(); assert!(html.contains("requires administrator privileges")); let record = sqlx::query!("SELECT id FROM subscriptions") .fetch_optional(&app.connection_pool) .await .unwrap(); assert!(record.is_some()); let response = app.delete_comment(comment_id).await; let html = response.text().await.unwrap(); assert!(html.contains("requires administrator privileges")); let record = sqlx::query!("SELECT comment_id FROM comments") .fetch_optional(&app.connection_pool) .await .unwrap(); assert!(record.is_some()); let response = app.delete_post(post_id).await; let html = response.text().await.unwrap(); assert!(html.contains("requires administrator privileges")); let record = sqlx::query!("SELECT post_id FROM posts") .fetch_optional(&app.connection_pool) .await .unwrap(); assert!(record.is_some()); let user_id = { let record = sqlx::query!("SELECT user_id FROM users") .fetch_one(&app.connection_pool) .await .unwrap(); record.user_id }; let response = app.delete_user(user_id).await; let html = response.text().await.unwrap(); assert!(html.contains("requires administrator privileges")); let record = sqlx::query_scalar!("SELECT username FROM users WHERE user_id = $1", user_id) .fetch_optional(&app.connection_pool) .await .unwrap(); assert!(record.is_some()); let username = "friend"; let password = "123456789abc"; let response = app.create_user(username, password, false).await; let html = response.text().await.unwrap(); assert!(html.contains("requires administrator privileges")); let record = sqlx::query!("SELECT user_id FROM users WHERE username = $1", username) .fetch_optional(&app.connection_pool) .await .unwrap(); assert!(record.is_none()); } #[sqlx::test] async fn user_can_change_his_display_name(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 user_id = app.get_user_id(username).await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let full_name = "Alphonse Paix"; let edit_body = serde_json::json!( { "user_id": user_id, "username": username, "full_name": full_name, "bio": "", }); let html = app.get_profile_html(username).await; assert!(!html.contains(full_name)); let response = app.edit_profile(&edit_body).await; assert!(dbg!(response.text().await.unwrap()).contains("Your profile has been updated")); let html = app.get_profile_html(username).await; assert!(html.contains(full_name)); } #[sqlx::test] async fn user_can_change_his_bio(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 user_id = app.get_user_id(username).await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let bio = "This is me"; let edit_body = serde_json::json!( { "user_id": user_id, "username": username, "full_name": "", "bio": bio, }); let html = app.get_profile_html(username).await; assert!(!html.contains(bio)); let response = app.edit_profile(&edit_body).await; assert!(dbg!(response.text().await.unwrap()).contains("Your profile has been updated")); let html = app.get_profile_html(username).await; assert!(html.contains(bio)); } #[sqlx::test] async fn user_can_change_his_username(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 user_id = app.get_user_id(username).await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let new_username = "alphonsepaix"; let edit_body = serde_json::json!( { "user_id": user_id, "username": new_username, "full_name": "", "bio": "", }); let html = app.get_profile_html(username).await; assert!(html.contains(username)); let response = app.edit_profile(&edit_body).await; assert!(dbg!(response.text().await.unwrap()).contains("Your profile has been updated")); let html = app.get_profile_html(username).await; assert!(html.contains("404")); let html = app.get_profile_html(new_username).await; assert!(html.contains(new_username)); } #[sqlx::test] async fn user_cannot_change_other_profiles(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 other_user_id = app.get_user_id("admin").await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let new_username = "alphonsepaix"; let edit_body = serde_json::json!( { "user_id": other_user_id, "username": new_username, "full_name": "", "bio": "", }); let response = app.edit_profile(&edit_body).await; assert!( response .text() .await .unwrap() .contains("You are not authorized") ); } #[sqlx::test] async fn user_cannot_take_an_existing_username(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 user_id = app.get_user_id(username).await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let edit_body = serde_json::json!( { "user_id": user_id, "username": "admin", "full_name": "", "bio": "", }); let response = app.edit_profile(&edit_body).await; assert!( response .text() .await .unwrap() .contains("This username is already taken") ); let html = app.get_profile_html(username).await; assert!(html.contains(username)); } #[sqlx::test] async fn invalid_fields_are_rejected(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 user_id = app.get_user_id(username).await; let login_body = serde_json::json!({ "username": username, "password": password }); app.post_login(&login_body).await; let test_cases = [( serde_json::json!({ "user_id": user_id, "username": "ab", "full_name": "", "bio": "", }), "Username must be at least 3 characters", "the username was too short", )]; for (invalid_body, expected_error_message, explaination) in test_cases { let html = app.edit_profile(&invalid_body).await; assert!( html.text().await.unwrap().contains(expected_error_message), "The API did not reject the changes when {}", explaination ); } }