reinhardt_auth/user_management.rs
1//! User Management
2//!
3//! Provides CRUD operations for user management.
4
5use crate::PasswordHasher;
6use crate::core::AuthIdentity;
7use std::collections::HashMap;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10use uuid::Uuid;
11
12/// A user managed by [`UserManager`].
13///
14/// Carries common user fields and implements [`AuthIdentity`].
15/// Suitable for in-memory user management in testing and development.
16#[derive(Debug, Clone)]
17pub struct ManagedUser {
18 /// The unique user identifier.
19 pub id: Uuid,
20 /// The user's login name.
21 pub username: String,
22 /// The user's email address.
23 pub email: String,
24 /// Whether the user account is active.
25 pub is_active: bool,
26 /// Whether the user has admin privileges.
27 pub is_admin: bool,
28 /// Whether the user is a staff member.
29 pub is_staff: bool,
30 /// Whether the user is a superuser.
31 pub is_superuser: bool,
32}
33
34impl AuthIdentity for ManagedUser {
35 fn id(&self) -> String {
36 self.id.to_string()
37 }
38
39 fn is_authenticated(&self) -> bool {
40 true
41 }
42
43 fn is_admin(&self) -> bool {
44 self.is_admin
45 }
46
47 fn is_account_active(&self) -> bool {
48 self.is_active
49 }
50}
51
52/// User management error
53#[non_exhaustive]
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum UserManagementError {
56 /// User not found
57 UserNotFound,
58 /// User already exists
59 UserAlreadyExists,
60 /// Invalid username
61 InvalidUsername,
62 /// Invalid email
63 InvalidEmail,
64 /// Invalid password
65 InvalidPassword,
66 /// Database error
67 DatabaseError(String),
68 /// Other error
69 Other(String),
70}
71
72impl std::fmt::Display for UserManagementError {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 match self {
75 UserManagementError::UserNotFound => write!(f, "User not found"),
76 UserManagementError::UserAlreadyExists => write!(f, "User already exists"),
77 UserManagementError::InvalidUsername => write!(f, "Invalid username"),
78 UserManagementError::InvalidEmail => write!(f, "Invalid email"),
79 UserManagementError::InvalidPassword => write!(f, "Invalid password"),
80 UserManagementError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
81 UserManagementError::Other(msg) => write!(f, "Error: {}", msg),
82 }
83 }
84}
85
86impl std::error::Error for UserManagementError {}
87
88/// User management result
89pub type UserManagementResult<T> = Result<T, UserManagementError>;
90
91/// User data for creation
92///
93/// # Examples
94///
95/// ```
96/// use reinhardt_auth::user_management::CreateUserData;
97///
98/// let user_data = CreateUserData {
99/// username: "alice".to_string(),
100/// email: "alice@example.com".to_string(),
101/// password: "password123".to_string(),
102/// is_active: true,
103/// is_admin: false,
104/// };
105///
106/// assert_eq!(user_data.username, "alice");
107/// assert_eq!(user_data.email, "alice@example.com");
108/// ```
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct CreateUserData {
111 /// Login username for the new user.
112 pub username: String,
113 /// Email address for the new user.
114 pub email: String,
115 /// Plain-text password (will be hashed before storage).
116 pub password: String,
117 /// Whether the new user account should be active.
118 pub is_active: bool,
119 /// Whether the new user should have admin privileges.
120 pub is_admin: bool,
121}
122
123/// User data for update
124///
125/// # Examples
126///
127/// ```
128/// use reinhardt_auth::user_management::UpdateUserData;
129///
130/// let update_data = UpdateUserData {
131/// email: Some("newemail@example.com".to_string()),
132/// is_active: Some(false),
133/// is_admin: None,
134/// };
135///
136/// assert_eq!(update_data.email, Some("newemail@example.com".to_string()));
137/// assert_eq!(update_data.is_active, Some(false));
138/// ```
139#[derive(Debug, Clone, Default, PartialEq, Eq)]
140pub struct UpdateUserData {
141 /// New email address, if being updated.
142 pub email: Option<String>,
143 /// New active status, if being updated.
144 pub is_active: Option<bool>,
145 /// New admin status, if being updated.
146 pub is_admin: Option<bool>,
147}
148
149/// User manager
150///
151/// Provides CRUD operations for users.
152///
153/// # Examples
154///
155/// ```rust
156/// # use reinhardt_auth::PasswordHasher;
157/// # struct MockHasher;
158/// # impl PasswordHasher for MockHasher {
159/// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
160/// # Ok(format!("hash:{p}"))
161/// # }
162/// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
163/// # Ok(h == format!("hash:{p}"))
164/// # }
165/// # }
166/// use reinhardt_auth::user_management::{UserManager, CreateUserData};
167///
168/// #[tokio::main]
169/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
170/// let hasher = MockHasher;
171/// let mut manager = UserManager::new(hasher);
172///
173/// // Create user
174/// let user_data = CreateUserData {
175/// username: "alice".to_string(),
176/// email: "alice@example.com".to_string(),
177/// password: "password123".to_string(),
178/// is_active: true,
179/// is_admin: false,
180/// };
181///
182/// let user = manager.create_user(user_data).await.unwrap();
183/// assert_eq!(user.username, "alice");
184///
185/// // Get user
186/// let retrieved = manager.get_user(&user.id.to_string()).await.unwrap();
187/// assert_eq!(retrieved.username, "alice");
188///
189/// // Delete user
190/// manager.delete_user(&user.id.to_string()).await.unwrap();
191/// assert!(manager.get_user(&user.id.to_string()).await.is_err());
192/// Ok(())
193/// }
194/// ```
195pub struct UserManager<H: PasswordHasher> {
196 users: Arc<RwLock<HashMap<Uuid, ManagedUser>>>,
197 username_index: Arc<RwLock<HashMap<String, Uuid>>>,
198 password_hashes: Arc<RwLock<HashMap<Uuid, String>>>,
199 hasher: H,
200}
201
202impl<H: PasswordHasher> UserManager<H> {
203 /// Create a new user manager
204 ///
205 /// # Examples
206 ///
207 /// ```rust
208 /// # use reinhardt_auth::PasswordHasher;
209 /// # struct MockHasher;
210 /// # impl PasswordHasher for MockHasher {
211 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
212 /// # Ok(format!("hash:{p}"))
213 /// # }
214 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
215 /// # Ok(h == format!("hash:{p}"))
216 /// # }
217 /// # }
218 /// use reinhardt_auth::user_management::UserManager;
219 ///
220 /// let manager = UserManager::new(MockHasher);
221 /// ```
222 pub fn new(hasher: H) -> Self {
223 Self {
224 users: Arc::new(RwLock::new(HashMap::new())),
225 username_index: Arc::new(RwLock::new(HashMap::new())),
226 password_hashes: Arc::new(RwLock::new(HashMap::new())),
227 hasher,
228 }
229 }
230
231 /// Create a new user
232 ///
233 /// # Examples
234 ///
235 /// ```rust
236 /// # use reinhardt_auth::PasswordHasher;
237 /// # struct MockHasher;
238 /// # impl PasswordHasher for MockHasher {
239 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
240 /// # Ok(format!("hash:{p}"))
241 /// # }
242 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
243 /// # Ok(h == format!("hash:{p}"))
244 /// # }
245 /// # }
246 /// use reinhardt_auth::user_management::{UserManager, CreateUserData};
247 ///
248 /// #[tokio::main]
249 /// async fn main() {
250 /// let mut manager = UserManager::new(MockHasher);
251 ///
252 /// let user_data = CreateUserData {
253 /// username: "bob".to_string(),
254 /// email: "bob@example.com".to_string(),
255 /// password: "securepass".to_string(),
256 /// is_active: true,
257 /// is_admin: false,
258 /// };
259 ///
260 /// let user = manager.create_user(user_data).await.unwrap();
261 /// assert_eq!(user.username, "bob");
262 /// }
263 /// ```
264 pub async fn create_user(&mut self, data: CreateUserData) -> UserManagementResult<ManagedUser> {
265 // Validate username
266 if data.username.is_empty() || data.username.len() < 3 {
267 return Err(UserManagementError::InvalidUsername);
268 }
269
270 // Validate email
271 // JWT/MFA authentication may not provide email; skip validation for empty emails
272 if !data.email.is_empty() && (!data.email.contains('@') || !data.email.contains('.')) {
273 return Err(UserManagementError::InvalidEmail);
274 }
275
276 // Validate password
277 if data.password.len() < 8 {
278 return Err(UserManagementError::InvalidPassword);
279 }
280
281 // Check if username already exists
282 let username_index = self.username_index.read().await;
283 if username_index.contains_key(&data.username) {
284 return Err(UserManagementError::UserAlreadyExists);
285 }
286 drop(username_index);
287
288 // Hash password
289 let password_hash = self
290 .hasher
291 .hash(&data.password)
292 .map_err(|e| UserManagementError::Other(e.to_string()))?;
293
294 // Create user
295 let user = ManagedUser {
296 id: Uuid::now_v7(),
297 username: data.username.clone(),
298 email: data.email,
299 is_active: data.is_active,
300 is_admin: data.is_admin,
301 is_staff: false,
302 is_superuser: false,
303 };
304
305 // Store user
306 let mut users = self.users.write().await;
307 let mut username_index = self.username_index.write().await;
308 let mut password_hashes = self.password_hashes.write().await;
309
310 users.insert(user.id, user.clone());
311 username_index.insert(data.username, user.id);
312 password_hashes.insert(user.id, password_hash);
313
314 Ok(user)
315 }
316
317 /// Get user by ID
318 ///
319 /// # Examples
320 ///
321 /// ```rust
322 /// # use reinhardt_auth::PasswordHasher;
323 /// # struct MockHasher;
324 /// # impl PasswordHasher for MockHasher {
325 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
326 /// # Ok(format!("hash:{p}"))
327 /// # }
328 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
329 /// # Ok(h == format!("hash:{p}"))
330 /// # }
331 /// # }
332 /// use reinhardt_auth::user_management::{UserManager, CreateUserData};
333 ///
334 /// #[tokio::main]
335 /// async fn main() {
336 /// let mut manager = UserManager::new(MockHasher);
337 ///
338 /// let user_data = CreateUserData {
339 /// username: "charlie".to_string(),
340 /// email: "charlie@example.com".to_string(),
341 /// password: "password123".to_string(),
342 /// is_active: true,
343 /// is_admin: false,
344 /// };
345 ///
346 /// let user = manager.create_user(user_data).await.unwrap();
347 /// let retrieved = manager.get_user(&user.id.to_string()).await.unwrap();
348 /// assert_eq!(retrieved.username, "charlie");
349 /// }
350 /// ```
351 pub async fn get_user(&self, user_id: &str) -> UserManagementResult<ManagedUser> {
352 let uuid = Uuid::parse_str(user_id)
353 .map_err(|_| UserManagementError::Other("Invalid UUID".to_string()))?;
354
355 let users = self.users.read().await;
356 users
357 .get(&uuid)
358 .cloned()
359 .ok_or(UserManagementError::UserNotFound)
360 }
361
362 /// Get user by username
363 ///
364 /// # Examples
365 ///
366 /// ```rust
367 /// # use reinhardt_auth::PasswordHasher;
368 /// # struct MockHasher;
369 /// # impl PasswordHasher for MockHasher {
370 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
371 /// # Ok(format!("hash:{p}"))
372 /// # }
373 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
374 /// # Ok(h == format!("hash:{p}"))
375 /// # }
376 /// # }
377 /// use reinhardt_auth::user_management::{UserManager, CreateUserData};
378 ///
379 /// #[tokio::main]
380 /// async fn main() {
381 /// let mut manager = UserManager::new(MockHasher);
382 ///
383 /// let user_data = CreateUserData {
384 /// username: "diana".to_string(),
385 /// email: "diana@example.com".to_string(),
386 /// password: "password123".to_string(),
387 /// is_active: true,
388 /// is_admin: false,
389 /// };
390 ///
391 /// manager.create_user(user_data).await.unwrap();
392 /// let retrieved = manager.get_user_by_username("diana").await.unwrap();
393 /// assert_eq!(retrieved.username, "diana");
394 /// }
395 /// ```
396 pub async fn get_user_by_username(&self, username: &str) -> UserManagementResult<ManagedUser> {
397 let username_index = self.username_index.read().await;
398 let user_id = username_index
399 .get(username)
400 .ok_or(UserManagementError::UserNotFound)?;
401
402 let users = self.users.read().await;
403 users
404 .get(user_id)
405 .cloned()
406 .ok_or(UserManagementError::UserNotFound)
407 }
408
409 /// Update user
410 ///
411 /// # Examples
412 ///
413 /// ```rust
414 /// # use reinhardt_auth::PasswordHasher;
415 /// # struct MockHasher;
416 /// # impl PasswordHasher for MockHasher {
417 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
418 /// # Ok(format!("hash:{p}"))
419 /// # }
420 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
421 /// # Ok(h == format!("hash:{p}"))
422 /// # }
423 /// # }
424 /// use reinhardt_auth::user_management::{UserManager, CreateUserData, UpdateUserData};
425 ///
426 /// #[tokio::main]
427 /// async fn main() {
428 /// let mut manager = UserManager::new(MockHasher);
429 ///
430 /// let user_data = CreateUserData {
431 /// username: "eve".to_string(),
432 /// email: "eve@example.com".to_string(),
433 /// password: "password123".to_string(),
434 /// is_active: true,
435 /// is_admin: false,
436 /// };
437 ///
438 /// let user = manager.create_user(user_data).await.unwrap();
439 ///
440 /// let update_data = UpdateUserData {
441 /// email: Some("newemail@example.com".to_string()),
442 /// is_active: Some(false),
443 /// is_admin: None,
444 /// };
445 ///
446 /// let updated = manager.update_user(&user.id.to_string(), update_data).await.unwrap();
447 /// assert_eq!(updated.email, "newemail@example.com");
448 /// assert!(!updated.is_active);
449 /// }
450 /// ```
451 pub async fn update_user(
452 &mut self,
453 user_id: &str,
454 data: UpdateUserData,
455 ) -> UserManagementResult<ManagedUser> {
456 let uuid = Uuid::parse_str(user_id)
457 .map_err(|_| UserManagementError::Other("Invalid UUID".to_string()))?;
458
459 let mut users = self.users.write().await;
460 let user = users
461 .get_mut(&uuid)
462 .ok_or(UserManagementError::UserNotFound)?;
463
464 if let Some(email) = data.email {
465 if !email.contains('@') || !email.contains('.') {
466 return Err(UserManagementError::InvalidEmail);
467 }
468 user.email = email;
469 }
470
471 if let Some(is_active) = data.is_active {
472 user.is_active = is_active;
473 }
474
475 if let Some(is_admin) = data.is_admin {
476 user.is_admin = is_admin;
477 }
478
479 Ok(user.clone())
480 }
481
482 /// Delete user
483 ///
484 /// # Examples
485 ///
486 /// ```rust
487 /// # use reinhardt_auth::PasswordHasher;
488 /// # struct MockHasher;
489 /// # impl PasswordHasher for MockHasher {
490 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
491 /// # Ok(format!("hash:{p}"))
492 /// # }
493 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
494 /// # Ok(h == format!("hash:{p}"))
495 /// # }
496 /// # }
497 /// use reinhardt_auth::user_management::{UserManager, CreateUserData};
498 ///
499 /// #[tokio::main]
500 /// async fn main() {
501 /// let mut manager = UserManager::new(MockHasher);
502 ///
503 /// let user_data = CreateUserData {
504 /// username: "frank".to_string(),
505 /// email: "frank@example.com".to_string(),
506 /// password: "password123".to_string(),
507 /// is_active: true,
508 /// is_admin: false,
509 /// };
510 ///
511 /// let user = manager.create_user(user_data).await.unwrap();
512 /// manager.delete_user(&user.id.to_string()).await.unwrap();
513 /// assert!(manager.get_user(&user.id.to_string()).await.is_err());
514 /// }
515 /// ```
516 pub async fn delete_user(&mut self, user_id: &str) -> UserManagementResult<()> {
517 let uuid = Uuid::parse_str(user_id)
518 .map_err(|_| UserManagementError::Other("Invalid UUID".to_string()))?;
519
520 let mut users = self.users.write().await;
521 let user = users
522 .get(&uuid)
523 .ok_or(UserManagementError::UserNotFound)?
524 .clone();
525
526 let mut username_index = self.username_index.write().await;
527 let mut password_hashes = self.password_hashes.write().await;
528
529 users.remove(&uuid);
530 username_index.remove(&user.username);
531 password_hashes.remove(&uuid);
532
533 Ok(())
534 }
535
536 /// List all users
537 ///
538 /// # Examples
539 ///
540 /// ```rust
541 /// # use reinhardt_auth::PasswordHasher;
542 /// # struct MockHasher;
543 /// # impl PasswordHasher for MockHasher {
544 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
545 /// # Ok(format!("hash:{p}"))
546 /// # }
547 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
548 /// # Ok(h == format!("hash:{p}"))
549 /// # }
550 /// # }
551 /// use reinhardt_auth::user_management::{UserManager, CreateUserData};
552 ///
553 /// #[tokio::main]
554 /// async fn main() {
555 /// let mut manager = UserManager::new(MockHasher);
556 ///
557 /// let user_data1 = CreateUserData {
558 /// username: "grace".to_string(),
559 /// email: "grace@example.com".to_string(),
560 /// password: "password123".to_string(),
561 /// is_active: true,
562 /// is_admin: false,
563 /// };
564 ///
565 /// let user_data2 = CreateUserData {
566 /// username: "henry".to_string(),
567 /// email: "henry@example.com".to_string(),
568 /// password: "password123".to_string(),
569 /// is_active: true,
570 /// is_admin: false,
571 /// };
572 ///
573 /// manager.create_user(user_data1).await.unwrap();
574 /// manager.create_user(user_data2).await.unwrap();
575 ///
576 /// let users = manager.list_users().await;
577 /// assert_eq!(users.len(), 2);
578 /// }
579 /// ```
580 pub async fn list_users(&self) -> Vec<ManagedUser> {
581 let users = self.users.read().await;
582 users.values().cloned().collect()
583 }
584
585 /// Verify user password
586 ///
587 /// # Examples
588 ///
589 /// ```rust
590 /// # use reinhardt_auth::PasswordHasher;
591 /// # struct MockHasher;
592 /// # impl PasswordHasher for MockHasher {
593 /// # fn hash(&self, p: &str) -> Result<String, reinhardt_core::exception::Error> {
594 /// # Ok(format!("hash:{p}"))
595 /// # }
596 /// # fn verify(&self, p: &str, h: &str) -> Result<bool, reinhardt_core::exception::Error> {
597 /// # Ok(h == format!("hash:{p}"))
598 /// # }
599 /// # }
600 /// use reinhardt_auth::user_management::{UserManager, CreateUserData};
601 ///
602 /// #[tokio::main]
603 /// async fn main() {
604 /// let mut manager = UserManager::new(MockHasher);
605 ///
606 /// let user_data = CreateUserData {
607 /// username: "iris".to_string(),
608 /// email: "iris@example.com".to_string(),
609 /// password: "mypassword".to_string(),
610 /// is_active: true,
611 /// is_admin: false,
612 /// };
613 ///
614 /// let user = manager.create_user(user_data).await.unwrap();
615 /// assert!(manager.verify_password(&user.id.to_string(), "mypassword").await.unwrap());
616 /// assert!(!manager.verify_password(&user.id.to_string(), "wrongpassword").await.unwrap());
617 /// }
618 /// ```
619 pub async fn verify_password(
620 &self,
621 user_id: &str,
622 password: &str,
623 ) -> UserManagementResult<bool> {
624 let uuid = Uuid::parse_str(user_id)
625 .map_err(|_| UserManagementError::Other("Invalid UUID".to_string()))?;
626
627 let password_hashes = self.password_hashes.read().await;
628 let hash = password_hashes
629 .get(&uuid)
630 .ok_or(UserManagementError::UserNotFound)?;
631
632 self.hasher
633 .verify(password, hash)
634 .map_err(|e| UserManagementError::Other(e.to_string()))
635 }
636}
637
638#[cfg(all(test, feature = "argon2-hasher"))]
639mod tests {
640 use super::*;
641 use crate::Argon2Hasher;
642
643 #[tokio::test]
644 async fn test_create_user() {
645 let hasher = Argon2Hasher::new();
646 let mut manager = UserManager::new(hasher);
647
648 let user_data = CreateUserData {
649 username: "alice".to_string(),
650 email: "alice@example.com".to_string(),
651 password: "password123".to_string(),
652 is_active: true,
653 is_admin: false,
654 };
655
656 let user = manager.create_user(user_data).await.unwrap();
657 assert_eq!(user.username, "alice");
658 assert_eq!(user.email, "alice@example.com");
659 assert!(user.is_active);
660 assert!(!user.is_admin);
661 }
662
663 #[tokio::test]
664 async fn test_create_user_duplicate_username() {
665 let hasher = Argon2Hasher::new();
666 let mut manager = UserManager::new(hasher);
667
668 let user_data1 = CreateUserData {
669 username: "bob".to_string(),
670 email: "bob@example.com".to_string(),
671 password: "password123".to_string(),
672 is_active: true,
673 is_admin: false,
674 };
675
676 let user_data2 = CreateUserData {
677 username: "bob".to_string(),
678 email: "bob2@example.com".to_string(),
679 password: "password456".to_string(),
680 is_active: true,
681 is_admin: false,
682 };
683
684 manager.create_user(user_data1).await.unwrap();
685 let result = manager.create_user(user_data2).await;
686 assert!(result.is_err());
687 }
688
689 #[tokio::test]
690 async fn test_get_user() {
691 let hasher = Argon2Hasher::new();
692 let mut manager = UserManager::new(hasher);
693
694 let user_data = CreateUserData {
695 username: "charlie".to_string(),
696 email: "charlie@example.com".to_string(),
697 password: "password123".to_string(),
698 is_active: true,
699 is_admin: false,
700 };
701
702 let user = manager.create_user(user_data).await.unwrap();
703 let retrieved = manager.get_user(&user.id.to_string()).await.unwrap();
704 assert_eq!(retrieved.username, "charlie");
705 }
706
707 #[tokio::test]
708 async fn test_get_user_by_username() {
709 let hasher = Argon2Hasher::new();
710 let mut manager = UserManager::new(hasher);
711
712 let user_data = CreateUserData {
713 username: "diana".to_string(),
714 email: "diana@example.com".to_string(),
715 password: "password123".to_string(),
716 is_active: true,
717 is_admin: false,
718 };
719
720 manager.create_user(user_data).await.unwrap();
721 let retrieved = manager.get_user_by_username("diana").await.unwrap();
722 assert_eq!(retrieved.username, "diana");
723 }
724
725 #[tokio::test]
726 async fn test_update_user() {
727 let hasher = Argon2Hasher::new();
728 let mut manager = UserManager::new(hasher);
729
730 let user_data = CreateUserData {
731 username: "eve".to_string(),
732 email: "eve@example.com".to_string(),
733 password: "password123".to_string(),
734 is_active: true,
735 is_admin: false,
736 };
737
738 let user = manager.create_user(user_data).await.unwrap();
739
740 let update_data = UpdateUserData {
741 email: Some("newemail@example.com".to_string()),
742 is_active: Some(false),
743 is_admin: Some(true),
744 };
745
746 let updated = manager
747 .update_user(&user.id.to_string(), update_data)
748 .await
749 .unwrap();
750 assert_eq!(updated.email, "newemail@example.com");
751 assert!(!updated.is_active);
752 assert!(updated.is_admin);
753 }
754
755 #[tokio::test]
756 async fn test_delete_user() {
757 let hasher = Argon2Hasher::new();
758 let mut manager = UserManager::new(hasher);
759
760 let user_data = CreateUserData {
761 username: "frank".to_string(),
762 email: "frank@example.com".to_string(),
763 password: "password123".to_string(),
764 is_active: true,
765 is_admin: false,
766 };
767
768 let user = manager.create_user(user_data).await.unwrap();
769 manager.delete_user(&user.id.to_string()).await.unwrap();
770 let result = manager.get_user(&user.id.to_string()).await;
771 assert!(result.is_err());
772 }
773
774 #[tokio::test]
775 async fn test_list_users() {
776 let hasher = Argon2Hasher::new();
777 let mut manager = UserManager::new(hasher);
778
779 let user_data1 = CreateUserData {
780 username: "grace".to_string(),
781 email: "grace@example.com".to_string(),
782 password: "password123".to_string(),
783 is_active: true,
784 is_admin: false,
785 };
786
787 let user_data2 = CreateUserData {
788 username: "henry".to_string(),
789 email: "henry@example.com".to_string(),
790 password: "password123".to_string(),
791 is_active: true,
792 is_admin: false,
793 };
794
795 manager.create_user(user_data1).await.unwrap();
796 manager.create_user(user_data2).await.unwrap();
797
798 let users = manager.list_users().await;
799 assert_eq!(users.len(), 2);
800 }
801
802 #[tokio::test]
803 async fn test_verify_password() {
804 let hasher = Argon2Hasher::new();
805 let mut manager = UserManager::new(hasher);
806
807 let user_data = CreateUserData {
808 username: "iris".to_string(),
809 email: "iris@example.com".to_string(),
810 password: "mypassword".to_string(),
811 is_active: true,
812 is_admin: false,
813 };
814
815 let user = manager.create_user(user_data).await.unwrap();
816 assert!(
817 manager
818 .verify_password(&user.id.to_string(), "mypassword")
819 .await
820 .unwrap()
821 );
822 assert!(
823 !manager
824 .verify_password(&user.id.to_string(), "wrongpassword")
825 .await
826 .unwrap()
827 );
828 }
829
830 #[rstest::rstest]
831 #[tokio::test]
832 async fn test_create_user_with_email_missing_at_sign_returns_invalid_email() {
833 // Arrange
834 let hasher = Argon2Hasher::new();
835 let mut manager = UserManager::new(hasher);
836
837 let user_data = CreateUserData {
838 username: "testuser".to_string(),
839 email: "invalidemail.com".to_string(),
840 password: "password123".to_string(),
841 is_active: true,
842 is_admin: false,
843 };
844
845 // Act
846 let result = manager.create_user(user_data).await;
847
848 // Assert
849 assert_eq!(result.unwrap_err(), UserManagementError::InvalidEmail);
850 }
851
852 #[rstest::rstest]
853 #[tokio::test]
854 async fn test_create_user_with_email_missing_dot_returns_invalid_email() {
855 // Arrange
856 let hasher = Argon2Hasher::new();
857 let mut manager = UserManager::new(hasher);
858
859 let user_data = CreateUserData {
860 username: "testuser".to_string(),
861 email: "invalid@emailcom".to_string(),
862 password: "password123".to_string(),
863 is_active: true,
864 is_admin: false,
865 };
866
867 // Act
868 let result = manager.create_user(user_data).await;
869
870 // Assert
871 assert_eq!(result.unwrap_err(), UserManagementError::InvalidEmail);
872 }
873
874 #[rstest::rstest]
875 #[tokio::test]
876 async fn test_get_user_nonexistent_returns_user_not_found() {
877 // Arrange
878 let hasher = Argon2Hasher::new();
879 let manager = UserManager::new(hasher);
880 let nonexistent_id = Uuid::now_v7().to_string();
881
882 // Act
883 let result = manager.get_user(&nonexistent_id).await;
884
885 // Assert
886 assert_eq!(result.unwrap_err(), UserManagementError::UserNotFound);
887 }
888
889 #[rstest::rstest]
890 #[tokio::test]
891 async fn test_create_user_with_empty_email_succeeds() {
892 // Arrange
893 let hasher = Argon2Hasher::new();
894 let mut manager = UserManager::new(hasher);
895
896 let user_data = CreateUserData {
897 username: "jwt_user".to_string(),
898 email: String::new(),
899 password: "password123".to_string(),
900 is_active: true,
901 is_admin: false,
902 };
903
904 // Act
905 let result = manager.create_user(user_data).await;
906
907 // Assert
908 let user = result.unwrap();
909 assert_eq!(user.username, "jwt_user");
910 assert_eq!(user.email, "");
911 assert!(user.is_active);
912 }
913}