Profile update tests
This commit is contained in:
@@ -158,7 +158,10 @@ pub async fn update_post(
|
|||||||
))
|
))
|
||||||
.into_response())
|
.into_response())
|
||||||
}
|
}
|
||||||
_ => Ok(HtmlTemplate(ErrorTemplate::Forbidden).into_response()),
|
_ => Ok(HtmlTemplate(MessageTemplate::error(
|
||||||
|
"You are not authorized. Only the author can edit his post.".into(),
|
||||||
|
))
|
||||||
|
.into_response()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::authentication::AuthenticatedUser;
|
use crate::authentication::AuthenticatedUser;
|
||||||
use crate::routes::verify_password;
|
use crate::routes::verify_password;
|
||||||
use crate::session_state::TypedSession;
|
use crate::session_state::TypedSession;
|
||||||
use crate::templates::{ErrorTemplate, MessageTemplate, UserEditTemplate};
|
use crate::templates::{MessageTemplate, UserEditTemplate};
|
||||||
use crate::{
|
use crate::{
|
||||||
authentication::Role,
|
authentication::Role,
|
||||||
domain::{PostEntry, UserEntry},
|
domain::{PostEntry, UserEntry},
|
||||||
@@ -18,6 +18,7 @@ use axum::{
|
|||||||
use secrecy::{ExposeSecret, SecretString};
|
use secrecy::{ExposeSecret, SecretString};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
pub async fn user_edit_form(
|
pub async fn user_edit_form(
|
||||||
Extension(AuthenticatedUser { user_id, .. }): Extension<AuthenticatedUser>,
|
Extension(AuthenticatedUser { user_id, .. }): Extension<AuthenticatedUser>,
|
||||||
@@ -41,9 +42,10 @@ pub async fn user_edit_form(
|
|||||||
Ok(template.into_response())
|
Ok(template.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(Debug, Validate, serde::Deserialize)]
|
||||||
pub struct EditProfileForm {
|
pub struct EditProfileForm {
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
|
#[validate(length(min = 3, message = "Username must be at least 3 characters."))]
|
||||||
username: String,
|
username: String,
|
||||||
full_name: String,
|
full_name: String,
|
||||||
bio: String,
|
bio: String,
|
||||||
@@ -62,8 +64,27 @@ pub async fn update_user(
|
|||||||
}): Extension<AuthenticatedUser>,
|
}): Extension<AuthenticatedUser>,
|
||||||
Form(form): Form<EditProfileForm>,
|
Form(form): Form<EditProfileForm>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
|
if let Err(e) = form.validate() {
|
||||||
|
let error_messages: Vec<_> = e
|
||||||
|
.field_errors()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(field, errors)| {
|
||||||
|
errors.iter().map(move |error| {
|
||||||
|
error
|
||||||
|
.message
|
||||||
|
.as_ref()
|
||||||
|
.map(|msg| msg.to_string())
|
||||||
|
.unwrap_or(format!("Invalid field: {}", field))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let template = HtmlTemplate(MessageTemplate::error(error_messages.join("\n")));
|
||||||
|
return Ok(template.into_response());
|
||||||
|
}
|
||||||
if form.user_id != session_user_id {
|
if form.user_id != session_user_id {
|
||||||
let template = HtmlTemplate(ErrorTemplate::Forbidden);
|
let template = HtmlTemplate(MessageTemplate::error(
|
||||||
|
"You are not authorized. Refresh the page and try again.".into(),
|
||||||
|
));
|
||||||
return Ok(template.into_response());
|
return Ok(template.into_response());
|
||||||
}
|
}
|
||||||
let updated_username = form.username.trim();
|
let updated_username = form.username.trim();
|
||||||
@@ -78,7 +99,7 @@ pub async fn update_user(
|
|||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
let template = HtmlTemplate(MessageTemplate::error(
|
let template = HtmlTemplate(MessageTemplate::error(
|
||||||
"The username is already taken.".into(),
|
"This username is already taken.".into(),
|
||||||
));
|
));
|
||||||
return Ok(template.into_response());
|
return Ok(template.into_response());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,6 +375,30 @@ impl TestApp {
|
|||||||
.expect("Failed to execute request")
|
.expect("Failed to execute request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn edit_profile<Body>(&self, body: &Body) -> reqwest::Response
|
||||||
|
where
|
||||||
|
Body: serde::Serialize,
|
||||||
|
{
|
||||||
|
self.api_client
|
||||||
|
.put(format!("{}/users/edit", self.address))
|
||||||
|
.form(body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_profile(&self, username: &str) -> reqwest::Response {
|
||||||
|
self.api_client
|
||||||
|
.get(format!("{}/users/{}", self.address, username))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_profile_html(&self, username: &str) -> String {
|
||||||
|
self.get_profile(username).await.text().await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn post_create_post<Body>(&self, body: &Body) -> reqwest::Response
|
pub async fn post_create_post<Body>(&self, body: &Body) -> reqwest::Response
|
||||||
where
|
where
|
||||||
Body: serde::Serialize,
|
Body: serde::Serialize,
|
||||||
@@ -426,6 +450,14 @@ impl TestApp {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to execute request")
|
.expect("Failed to execute request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_id(&self, username: &str) -> Uuid {
|
||||||
|
let record = sqlx::query!("SELECT user_id FROM users WHERE username = $1", username)
|
||||||
|
.fetch_one(&self.connection_pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
record.user_id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assert_is_redirect_to(response: &reqwest::Response, location: &str) {
|
pub fn assert_is_redirect_to(response: &reqwest::Response, location: &str) {
|
||||||
|
|||||||
@@ -344,3 +344,184 @@ async fn writers_cannot_perform_admin_functions(connection_pool: PgPool) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(record.is_none());
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user