Files
zero2prod/tests/api/users.rs
2025-10-07 23:07:16 +02:00

528 lines
17 KiB
Rust

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
);
}
}