From 22353942c75f1e1ad953d77478f4bc065cf08d7a Mon Sep 17 00:00:00 2001 From: Theo2 Date: Fri, 27 Mar 2026 15:07:28 +0100 Subject: [PATCH 1/2] :white_check_mark: ajout des tests pour le service utilisateur --- backend/tests/user_service_tests.rs | 298 ++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 backend/tests/user_service_tests.rs diff --git a/backend/tests/user_service_tests.rs b/backend/tests/user_service_tests.rs new file mode 100644 index 0000000..c8c46b5 --- /dev/null +++ b/backend/tests/user_service_tests.rs @@ -0,0 +1,298 @@ +use backend::dto::update_account_request_dto::UpdateAccountRequest; +use backend::models::user::User; +use backend::repository::user_repository::UserRepository; +use backend::service::user_service::UserService; +use sqlx::{MySqlPool, Row}; + +#[sqlx::test] +async fn devrait_confirmer_email_existant(pool: MySqlPool) { + // GIVEN + let email = "existing_user_service@test.com"; + UserRepository::insert_user(&pool, email, "pwd", "Jean", "Dupont") + .await + .expect("Failed to insert user"); + + // WHEN + let exists = UserService::user_with_email_exists(&pool, email.to_string()).await; + + // THEN + assert!(exists, "L'email insere devrait etre detecte comme existant"); +} + +#[sqlx::test] +async fn devrait_confirmer_email_inexistant(pool: MySqlPool) { + // GIVEN + let email = "missing_user_service@test.com"; + + // WHEN + let exists = UserService::user_with_email_exists(&pool, email.to_string()).await; + + // THEN + assert!(!exists, "Un email non present ne doit pas etre considere existant"); +} + +#[sqlx::test] +async fn devrait_mettre_a_jour_utilisateur_et_hasher_mot_de_passe(pool: MySqlPool) { + // GIVEN + let user_id = UserRepository::insert_user(&pool, "update_service_old@test.com", "old_pwd", "Old", "User") + .await + .expect("Failed to insert user"); + + let current_user = User { + id: user_id, + id_service: None, + email: "update_service_old@test.com".to_string(), + prenom: "Old".to_string(), + nom: "User".to_string(), + total_carbon_footprint: 0.0, + }; + + let payload = UpdateAccountRequest { + email: Some("update_service_new@test.com".to_string()), + prenom: Some("Nouveau".to_string()), + nom: Some("Nom".to_string()), + password: Some("motdepasse123".to_string()), + }; + + // WHEN + let updated = UserService::update_user(&pool, current_user, payload) + .await + .expect("La mise a jour devrait reussir"); + + // THEN + assert_eq!(updated.email, "update_service_new@test.com"); + assert_eq!(updated.prenom, "Nouveau"); + assert_eq!(updated.nom, "Nom"); + + let (_id, stored_hash, first_name, last_name, _service_id) = + UserRepository::find_with_password_by_email(&pool, "update_service_new@test.com") + .await + .expect("Failed to find updated user") + .expect("Updated user not found"); + + assert_eq!(first_name, "Nouveau"); + assert_eq!(last_name, "Nom"); + assert_ne!(stored_hash, "motdepasse123", "Le mot de passe doit etre hashe"); + assert!( + bcrypt::verify("motdepasse123", &stored_hash).expect("bcrypt verify failed"), + "Le hash stocke doit correspondre au mot de passe fourni" + ); +} + +#[sqlx::test] +async fn devrait_refuser_mise_a_jour_si_email_deja_utilise(pool: MySqlPool) { + // GIVEN + UserRepository::insert_user(&pool, "taken_email@test.com", "pwd", "Taken", "User") + .await + .expect("Failed to insert existing user"); + + let user_id = UserRepository::insert_user(&pool, "editable_user@test.com", "pwd", "Edit", "User") + .await + .expect("Failed to insert editable user"); + + let current_user = User { + id: user_id, + id_service: None, + email: "editable_user@test.com".to_string(), + prenom: "Edit".to_string(), + nom: "User".to_string(), + total_carbon_footprint: 0.0, + }; + + let payload = UpdateAccountRequest { + email: Some("taken_email@test.com".to_string()), + prenom: None, + nom: None, + password: None, + }; + + // WHEN + let result = UserService::update_user(&pool, current_user, payload).await; + + // THEN + assert_eq!(result.unwrap_err(), "Cet email est déjà utilisé"); +} + +#[sqlx::test] +async fn devrait_rejoindre_organisation_avec_code_valide(pool: MySqlPool) { + // GIVEN + let user_id = 2; // test_default@example.com n'est membre d'aucune orga dans la migration + + // WHEN + let joined_org = UserService::join_organization(&pool, "TEST_ORG".to_string(), user_id) + .await + .expect("Le join devrait reussir") + .expect("Une organisation devrait etre retournee"); + + // THEN + assert_eq!(joined_org.id, 1); + assert_eq!(joined_org.code, "TEST_ORG"); + + let membership = sqlx::query("SELECT est_admin FROM organisation_user WHERE user_id = ? AND organisation_id = ?") + .bind(user_id) + .bind(1) + .fetch_optional(&pool) + .await + .expect("Failed to query organisation_user"); + + assert!(membership.is_some(), "Le user devrait etre lie a l'organisation"); + let est_admin: bool = membership.unwrap().try_get("est_admin").expect("Failed to get est_admin"); + assert!(!est_admin, "Le join via ce service doit ajouter un membre non admin"); +} + +#[sqlx::test] +async fn devrait_refuser_rejoindre_organisation_code_invalide(pool: MySqlPool) { + // GIVEN + let user_id = 2; + + // WHEN + let result = UserService::join_organization(&pool, "UNKNOWN_CODE".to_string(), user_id).await; + + // THEN + assert_eq!(result.unwrap_err(), "Code d'organisation invalide"); +} + +#[sqlx::test] +async fn devrait_refuser_rejoindre_organisation_deja_membre(pool: MySqlPool) { + // GIVEN + let user_id = 1; // deja membre de TEST_ORG dans la migration + + // WHEN + let result = UserService::join_organization(&pool, "TEST_ORG".to_string(), user_id).await; + + // THEN + assert_eq!(result.unwrap_err(), "Déjà membre de cette organisation"); +} + +#[sqlx::test] +async fn devrait_recuperer_membres_organisation(pool: MySqlPool) { + // GIVEN + let orga_id = 1; + + // WHEN + let members = UserService::get_organization_members(&pool, orga_id) + .await + .expect("Failed to get organization members"); + + // THEN + assert!(members.iter().any(|m| m.id == 1), "Le user seed id=1 doit etre present"); + let user_1 = members.into_iter().find(|m| m.id == 1).expect("Le membre id=1 devrait exister"); + assert_eq!(user_1.service_name, Some("Test Service".to_string())); +} + +#[sqlx::test] +async fn devrait_supprimer_membre_organisation_et_retirer_service_associe(pool: MySqlPool) { + // GIVEN + let user_id = 1; + let orga_id = 1; + + // WHEN + UserService::remove_organization_member(&pool, user_id, orga_id) + .await + .expect("La suppression du membre devrait reussir"); + + // THEN + let service_id_after: Option = sqlx::query_scalar("SELECT service_id FROM user WHERE id = ?") + .bind(user_id) + .fetch_one(&pool) + .await + .expect("Failed to query user service_id"); + assert!(service_id_after.is_none(), "Le service doit etre retire pour cette organisation"); + + let membership_after = sqlx::query("SELECT 1 FROM organisation_user WHERE user_id = ? AND organisation_id = ?") + .bind(user_id) + .bind(orga_id) + .fetch_optional(&pool) + .await + .expect("Failed to query membership after delete"); + assert!(membership_after.is_none(), "Le lien organisation_user doit etre supprime"); +} + +#[sqlx::test] +async fn devrait_supprimer_utilisateur(pool: MySqlPool) { + // GIVEN + let user_id = UserRepository::insert_user(&pool, "delete_user_service@test.com", "pwd", "To", "Delete") + .await + .expect("Failed to insert user"); + + // WHEN + UserService::delete_user(&pool, user_id) + .await + .expect("La suppression devrait reussir"); + + // THEN + let found = UserRepository::find_id_by_email(&pool, "delete_user_service@test.com".to_string()) + .await + .expect("Failed to query user after delete"); + assert!(found.is_none(), "L'utilisateur ne doit plus exister"); +} + + +#[sqlx::test] +async fn devrait_retourner_erreur_mise_a_jour_si_pool_ferme(pool: MySqlPool) { + // GIVEN + let user = User { + id: 1, + id_service: Some(1), + email: "test@example.com".to_string(), + prenom: "Test".to_string(), + nom: "User".to_string(), + total_carbon_footprint: 100.0, + }; + + let payload = UpdateAccountRequest { + email: None, + prenom: Some("PrenomFerme".to_string()), + nom: None, + password: None, + }; + + pool.close().await; + + // WHEN + let result = UserService::update_user(&pool, user, payload).await; + + // THEN + let error_message = result.unwrap_err(); + assert!( + error_message.starts_with("Erreur mise à jour:"), + "Le message devrait être préfixé par 'Erreur mise à jour:', obtenu: {}", + error_message + ); +} + +#[sqlx::test] +#[ignore = "bcrypt::hash(DEFAULT_COST) ne permet pas de forcer une erreur de hash de façon déterministe sans modifier le service"] +async fn hash_error_est_non_deterministe_sans_injection(pool: MySqlPool) { + // GIVEN + let user_id = UserRepository::insert_user(&pool, "hash_error_probe@test.com", "pwd", "Hash", "Probe") + .await + .expect("Failed to insert user"); + + let user = User { + id: user_id, + id_service: None, + email: "hash_error_probe@test.com".to_string(), + prenom: "Hash".to_string(), + nom: "Probe".to_string(), + total_carbon_footprint: 0.0, + }; + + let payload = UpdateAccountRequest { + email: None, + prenom: None, + nom: None, + password: Some("motdepasse123".to_string()), + }; + + // WHEN + let result = UserService::update_user(&pool, user, payload).await; + + // THEN + // Ce test est ignoré volontairement : la branche hash_error n'est pas pilotable en entrée. + match result { + Err(err) => assert_eq!(err, "errors.auth.hash_error"), + Ok(_) => panic!("Le hash a reussi : branche hash_error non atteinte"), + } +} + From b48e1d0aba439107014fabe3161a619a0986e4b3 Mon Sep 17 00:00:00 2001 From: Theo2 Date: Fri, 27 Mar 2026 15:12:49 +0100 Subject: [PATCH 2/2] :white_check_mark: ajout des accents --- backend/tests/user_service_tests.rs | 40 ++--------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/backend/tests/user_service_tests.rs b/backend/tests/user_service_tests.rs index c8c46b5..8ea78ec 100644 --- a/backend/tests/user_service_tests.rs +++ b/backend/tests/user_service_tests.rs @@ -16,7 +16,7 @@ async fn devrait_confirmer_email_existant(pool: MySqlPool) { let exists = UserService::user_with_email_exists(&pool, email.to_string()).await; // THEN - assert!(exists, "L'email insere devrait etre detecte comme existant"); + assert!(exists, "L'email inséré devrait être détecté comme existant"); } #[sqlx::test] @@ -28,7 +28,7 @@ async fn devrait_confirmer_email_inexistant(pool: MySqlPool) { let exists = UserService::user_with_email_exists(&pool, email.to_string()).await; // THEN - assert!(!exists, "Un email non present ne doit pas etre considere existant"); + assert!(!exists, "Un email non présent ne doit pas être considéré existant"); } #[sqlx::test] @@ -260,39 +260,3 @@ async fn devrait_retourner_erreur_mise_a_jour_si_pool_ferme(pool: MySqlPool) { error_message ); } - -#[sqlx::test] -#[ignore = "bcrypt::hash(DEFAULT_COST) ne permet pas de forcer une erreur de hash de façon déterministe sans modifier le service"] -async fn hash_error_est_non_deterministe_sans_injection(pool: MySqlPool) { - // GIVEN - let user_id = UserRepository::insert_user(&pool, "hash_error_probe@test.com", "pwd", "Hash", "Probe") - .await - .expect("Failed to insert user"); - - let user = User { - id: user_id, - id_service: None, - email: "hash_error_probe@test.com".to_string(), - prenom: "Hash".to_string(), - nom: "Probe".to_string(), - total_carbon_footprint: 0.0, - }; - - let payload = UpdateAccountRequest { - email: None, - prenom: None, - nom: None, - password: Some("motdepasse123".to_string()), - }; - - // WHEN - let result = UserService::update_user(&pool, user, payload).await; - - // THEN - // Ce test est ignoré volontairement : la branche hash_error n'est pas pilotable en entrée. - match result { - Err(err) => assert_eq!(err, "errors.auth.hash_error"), - Ok(_) => panic!("Le hash a reussi : branche hash_error non atteinte"), - } -} -