/ tests / models / users.rs
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, &params).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  }