users.rs
1 use microvisor::{ 2 app::App, 3 models::users::{self, Model, RegisterParams}, 4 }; 5 use chrono::{offset::Local, Duration}; 6 use insta::assert_debug_snapshot; 7 use loco_rs::testing::prelude::*; 8 use sea_orm::{ActiveModelTrait, ActiveValue, IntoActiveModel}; 9 use serial_test::serial; 10 11 macro_rules! configure_insta { 12 ($($expr:expr),*) => { 13 let mut settings = insta::Settings::clone_current(); 14 settings.set_prepend_module_to_snapshot(false); 15 settings.set_snapshot_suffix("users"); 16 let _guard = settings.bind_to_scope(); 17 }; 18 } 19 20 #[tokio::test] 21 #[serial] 22 async fn test_can_validate_model() { 23 configure_insta!(); 24 25 let boot = boot_test::<App>() 26 .await 27 .expect("Failed to boot test application"); 28 29 let invalid_user = users::ActiveModel { 30 name: ActiveValue::set("1".to_string()), 31 email: ActiveValue::set("invalid-email".to_string()), 32 ..Default::default() 33 }; 34 35 let res = invalid_user.insert(&boot.app_context.db).await; 36 37 assert_debug_snapshot!(res); 38 } 39 40 #[tokio::test] 41 #[serial] 42 async fn can_create_with_password() { 43 configure_insta!(); 44 45 let boot = boot_test::<App>() 46 .await 47 .expect("Failed to boot test application"); 48 49 let params = RegisterParams { 50 email: "test@framework.com".to_string(), 51 password: "1234".to_string(), 52 name: "framework".to_string(), 53 }; 54 55 let res = Model::create_with_password(&boot.app_context.db, ¶ms).await; 56 57 insta::with_settings!({ 58 filters => cleanup_user_model() 59 }, { 60 assert_debug_snapshot!(res); 61 }); 62 } 63 #[tokio::test] 64 #[serial] 65 async fn handle_create_with_password_with_duplicate() { 66 configure_insta!(); 67 68 let boot = boot_test::<App>() 69 .await 70 .expect("Failed to boot test application"); 71 seed::<App>(&boot.app_context) 72 .await 73 .expect("Failed to seed database"); 74 75 let new_user = Model::create_with_password( 76 &boot.app_context.db, 77 &RegisterParams { 78 email: "user1@example.com".to_string(), 79 password: "1234".to_string(), 80 name: "framework".to_string(), 81 }, 82 ) 83 .await; 84 85 assert_debug_snapshot!(new_user); 86 } 87 88 #[tokio::test] 89 #[serial] 90 async fn can_find_by_email() { 91 configure_insta!(); 92 93 let boot = boot_test::<App>() 94 .await 95 .expect("Failed to boot test application"); 96 seed::<App>(&boot.app_context) 97 .await 98 .expect("Failed to seed database"); 99 100 let existing_user = Model::find_by_email(&boot.app_context.db, "user1@example.com").await; 101 let non_existing_user_results = 102 Model::find_by_email(&boot.app_context.db, "un@existing-email.com").await; 103 104 assert_debug_snapshot!(existing_user); 105 assert_debug_snapshot!(non_existing_user_results); 106 } 107 108 #[tokio::test] 109 #[serial] 110 async fn can_find_by_pid() { 111 configure_insta!(); 112 113 let boot = boot_test::<App>() 114 .await 115 .expect("Failed to boot test application"); 116 seed::<App>(&boot.app_context) 117 .await 118 .expect("Failed to seed database"); 119 120 let existing_user = 121 Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111").await; 122 let non_existing_user_results = 123 Model::find_by_pid(&boot.app_context.db, "23232323-2323-2323-2323-232323232323").await; 124 125 assert_debug_snapshot!(existing_user); 126 assert_debug_snapshot!(non_existing_user_results); 127 } 128 129 #[tokio::test] 130 #[serial] 131 async fn can_verification_token() { 132 configure_insta!(); 133 134 let boot = boot_test::<App>() 135 .await 136 .expect("Failed to boot test application"); 137 seed::<App>(&boot.app_context) 138 .await 139 .expect("Failed to seed database"); 140 141 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 142 .await 143 .expect("Failed to find user by PID"); 144 145 assert!( 146 user.email_verification_sent_at.is_none(), 147 "Expected no email verification sent timestamp" 148 ); 149 assert!( 150 user.email_verification_token.is_none(), 151 "Expected no email verification token" 152 ); 153 154 let result = user 155 .into_active_model() 156 .set_email_verification_sent(&boot.app_context.db) 157 .await; 158 159 assert!(result.is_ok(), "Failed to set email verification sent"); 160 161 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 162 .await 163 .expect("Failed to find user by PID after setting verification sent"); 164 165 assert!( 166 user.email_verification_sent_at.is_some(), 167 "Expected email verification sent timestamp to be present" 168 ); 169 assert!( 170 user.email_verification_token.is_some(), 171 "Expected email verification token to be present" 172 ); 173 } 174 175 #[tokio::test] 176 #[serial] 177 async fn can_set_forgot_password_sent() { 178 configure_insta!(); 179 180 let boot = boot_test::<App>() 181 .await 182 .expect("Failed to boot test application"); 183 seed::<App>(&boot.app_context) 184 .await 185 .expect("Failed to seed database"); 186 187 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 188 .await 189 .expect("Failed to find user by PID"); 190 191 assert!( 192 user.reset_sent_at.is_none(), 193 "Expected no reset sent timestamp" 194 ); 195 assert!(user.reset_token.is_none(), "Expected no reset token"); 196 197 let result = user 198 .into_active_model() 199 .set_forgot_password_sent(&boot.app_context.db) 200 .await; 201 202 assert!(result.is_ok(), "Failed to set forgot password sent"); 203 204 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 205 .await 206 .expect("Failed to find user by PID after setting forgot password sent"); 207 208 assert!( 209 user.reset_sent_at.is_some(), 210 "Expected reset sent timestamp to be present" 211 ); 212 assert!( 213 user.reset_token.is_some(), 214 "Expected reset token to be present" 215 ); 216 } 217 218 #[tokio::test] 219 #[serial] 220 async fn can_verified() { 221 configure_insta!(); 222 223 let boot = boot_test::<App>() 224 .await 225 .expect("Failed to boot test application"); 226 seed::<App>(&boot.app_context) 227 .await 228 .expect("Failed to seed database"); 229 230 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 231 .await 232 .expect("Failed to find user by PID"); 233 234 assert!( 235 user.email_verified_at.is_none(), 236 "Expected email to be unverified" 237 ); 238 239 let result = user 240 .into_active_model() 241 .verified(&boot.app_context.db) 242 .await; 243 244 assert!(result.is_ok(), "Failed to mark email as verified"); 245 246 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 247 .await 248 .expect("Failed to find user by PID after verification"); 249 250 assert!( 251 user.email_verified_at.is_some(), 252 "Expected email to be verified" 253 ); 254 } 255 256 #[tokio::test] 257 #[serial] 258 async fn can_reset_password() { 259 configure_insta!(); 260 261 let boot = boot_test::<App>() 262 .await 263 .expect("Failed to boot test application"); 264 seed::<App>(&boot.app_context) 265 .await 266 .expect("Failed to seed database"); 267 268 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 269 .await 270 .expect("Failed to find user by PID"); 271 272 assert!( 273 user.verify_password("12341234"), 274 "Password verification failed for original password" 275 ); 276 277 let result = user 278 .clone() 279 .into_active_model() 280 .reset_password(&boot.app_context.db, "new-password") 281 .await; 282 283 assert!(result.is_ok(), "Failed to reset password"); 284 285 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 286 .await 287 .expect("Failed to find user by PID after password reset"); 288 289 assert!( 290 user.verify_password("new-password"), 291 "Password verification failed for new password" 292 ); 293 } 294 295 #[tokio::test] 296 #[serial] 297 async fn magic_link() { 298 let boot = boot_test::<App>().await.unwrap(); 299 seed::<App>(&boot.app_context).await.unwrap(); 300 301 let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 302 .await 303 .unwrap(); 304 305 assert!( 306 user.magic_link_token.is_none(), 307 "Magic link token should be initially unset" 308 ); 309 assert!( 310 user.magic_link_expiration.is_none(), 311 "Magic link expiration should be initially unset" 312 ); 313 314 let create_result = user 315 .into_active_model() 316 .create_magic_link(&boot.app_context.db) 317 .await; 318 319 assert!( 320 create_result.is_ok(), 321 "Failed to create magic link: {:?}", 322 create_result.unwrap_err() 323 ); 324 325 let updated_user = 326 Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") 327 .await 328 .expect("Failed to refetch user after magic link creation"); 329 330 assert!( 331 updated_user.magic_link_token.is_some(), 332 "Magic link token should be set after creation" 333 ); 334 335 let magic_link_token = updated_user.magic_link_token.unwrap(); 336 assert_eq!( 337 magic_link_token.len(), 338 users::MAGIC_LINK_LENGTH as usize, 339 "Magic link token length does not match expected length" 340 ); 341 342 assert!( 343 updated_user.magic_link_expiration.is_some(), 344 "Magic link expiration should be set after creation" 345 ); 346 347 let now = Local::now(); 348 let should_expired_at = now + Duration::minutes(users::MAGIC_LINK_EXPIRATION_MIN.into()); 349 let actual_expiration = updated_user.magic_link_expiration.unwrap(); 350 351 assert!( 352 actual_expiration >= now, 353 "Magic link expiration should be in the future or now" 354 ); 355 356 assert!( 357 actual_expiration <= should_expired_at, 358 "Magic link expiration exceeds expected maximum expiration time" 359 ); 360 }