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