Skip to main content

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}