1use crate::application::dto::{
2 Claims, LoginRequest, LoginResponse, RefreshTokenRequest, RegisterRequest, UserResponse,
3 UserRoleSummary,
4};
5use crate::application::ports::{RefreshTokenRepository, UserRepository, UserRoleRepository};
6use crate::domain::entities::{RefreshToken, User, UserRole, UserRoleAssignment};
7use crate::infrastructure::audit::{log_audit_event, AuditEventType};
8use bcrypt::{hash, verify, DEFAULT_COST};
9use chrono::Utc;
10use jsonwebtoken::{encode, EncodingKey, Header};
11use std::sync::Arc;
12use uuid::Uuid;
13
14pub struct AuthUseCases {
15 user_repo: Arc<dyn UserRepository>,
16 refresh_token_repo: Arc<dyn RefreshTokenRepository>,
17 user_role_repo: Arc<dyn UserRoleRepository>,
18 jwt_secret: String,
19}
20
21impl AuthUseCases {
22 pub fn new(
23 user_repo: Arc<dyn UserRepository>,
24 refresh_token_repo: Arc<dyn RefreshTokenRepository>,
25 user_role_repo: Arc<dyn UserRoleRepository>,
26 jwt_secret: String,
27 ) -> Self {
28 Self {
29 user_repo,
30 refresh_token_repo,
31 user_role_repo,
32 jwt_secret,
33 }
34 }
35
36 pub async fn login(&self, request: LoginRequest) -> Result<LoginResponse, String> {
37 let user = self
38 .user_repo
39 .find_by_email(&request.email)
40 .await?
41 .ok_or_else(|| {
42 tokio::spawn(async move {
44 log_audit_event(
45 AuditEventType::AuthenticationFailed,
46 None,
47 None,
48 Some(format!("Failed login attempt for email: {}", request.email)),
49 None,
50 )
51 .await;
52 });
53 "Invalid email or password".to_string()
54 })?;
55
56 if !user.is_active {
57 let user_id = user.id;
59 tokio::spawn(async move {
60 log_audit_event(
61 AuditEventType::AuthenticationFailed,
62 Some(user_id),
63 None,
64 Some("Login attempt on deactivated account".to_string()),
65 None,
66 )
67 .await;
68 });
69 return Err("User account is deactivated".to_string());
70 }
71
72 let is_valid = verify(&request.password, &user.password_hash)
73 .map_err(|e| format!("Password verification failed: {}", e))?;
74
75 if !is_valid {
76 let user_id = user.id;
78 tokio::spawn(async move {
79 log_audit_event(
80 AuditEventType::AuthenticationFailed,
81 Some(user_id),
82 None,
83 Some("Invalid password".to_string()),
84 None,
85 )
86 .await;
87 });
88 return Err("Invalid email or password".to_string());
89 }
90
91 let (roles, active_role) = self.ensure_role_assignments(&user).await?;
92 let user_for_token = self.apply_active_role_metadata(&user, &active_role).await?;
93 let token = self.generate_token(&user_for_token, &active_role)?;
94
95 let refresh_token_string = self.generate_refresh_token_string(&user_for_token);
96 let refresh_token = RefreshToken::new(user_for_token.id, refresh_token_string.clone());
97 self.refresh_token_repo.create(&refresh_token).await?;
98
99 let user_id = user_for_token.id;
101 let organization_id = active_role.organization_id;
102 tokio::spawn(async move {
103 log_audit_event(
104 AuditEventType::UserLogin,
105 Some(user_id),
106 organization_id,
107 Some("Successful login".to_string()),
108 None,
109 )
110 .await;
111 });
112
113 Ok(LoginResponse {
114 token,
115 refresh_token: refresh_token_string,
116 user: self.build_user_response(&user_for_token, &roles, &active_role),
117 })
118 }
119
120 pub async fn register(&self, request: RegisterRequest) -> Result<LoginResponse, String> {
121 if (self.user_repo.find_by_email(&request.email).await?).is_some() {
122 return Err("Email already exists".to_string());
123 }
124
125 let role: UserRole = request
126 .role
127 .parse()
128 .map_err(|e| format!("Invalid role: {}", e))?;
129
130 let password_hash = hash(&request.password, DEFAULT_COST)
131 .map_err(|e| format!("Failed to hash password: {}", e))?;
132
133 let user = User::new(
134 request.email,
135 password_hash,
136 request.first_name,
137 request.last_name,
138 role.clone(),
139 request.organization_id,
140 )?;
141
142 let created_user = self.user_repo.create(&user).await?;
143
144 let primary_assignment = self
146 .user_role_repo
147 .create(&UserRoleAssignment::new(
148 created_user.id,
149 role,
150 created_user.organization_id,
151 true,
152 ))
153 .await?;
154 let roles = vec![primary_assignment.clone()];
155 let user_for_token = self
156 .apply_active_role_metadata(&created_user, &primary_assignment)
157 .await?;
158
159 let token = self.generate_token(&user_for_token, &primary_assignment)?;
160 let refresh_token_string = self.generate_refresh_token_string(&user_for_token);
161 let refresh_token = RefreshToken::new(user_for_token.id, refresh_token_string.clone());
162 self.refresh_token_repo.create(&refresh_token).await?;
163
164 let user_id = created_user.id;
166 let organization_id = created_user.organization_id;
167 let email = created_user.email.clone();
168 tokio::spawn(async move {
169 log_audit_event(
170 AuditEventType::UserRegistration,
171 Some(user_id),
172 organization_id,
173 Some(format!("New user registered: {}", email)),
174 None,
175 )
176 .await;
177 });
178
179 Ok(LoginResponse {
180 token,
181 refresh_token: refresh_token_string,
182 user: self.build_user_response(&user_for_token, &roles, &primary_assignment),
183 })
184 }
185
186 pub async fn switch_active_role(
187 &self,
188 user_id: Uuid,
189 role_id: Uuid,
190 ) -> Result<LoginResponse, String> {
191 let user = self
192 .user_repo
193 .find_by_id(user_id)
194 .await?
195 .ok_or("User not found")?;
196
197 if !user.is_active {
198 return Err("User account is deactivated".to_string());
199 }
200
201 let target_role = self
202 .user_role_repo
203 .find_by_id(role_id)
204 .await?
205 .ok_or("Role assignment not found")?;
206
207 if target_role.user_id != user.id {
208 return Err("Role assignment does not belong to user".to_string());
209 }
210
211 let updated_primary = self
212 .user_role_repo
213 .set_primary_role(user.id, role_id)
214 .await?;
215
216 let roles = self.user_role_repo.list_for_user(user.id).await?;
217 let active_role = roles
218 .iter()
219 .find(|assignment| assignment.is_primary)
220 .cloned()
221 .unwrap_or(updated_primary.clone());
222
223 let updated_user = self.apply_active_role_metadata(&user, &active_role).await?;
224
225 let token = self.generate_token(&updated_user, &active_role)?;
226 let refresh_token_string = self.generate_refresh_token_string(&updated_user);
227 let refresh_token = RefreshToken::new(updated_user.id, refresh_token_string.clone());
228 self.refresh_token_repo.create(&refresh_token).await?;
229
230 Ok(LoginResponse {
231 token,
232 refresh_token: refresh_token_string,
233 user: self.build_user_response(&updated_user, &roles, &active_role),
234 })
235 }
236
237 pub async fn get_user_by_id(&self, user_id: uuid::Uuid) -> Result<UserResponse, String> {
238 let user = self
239 .user_repo
240 .find_by_id(user_id)
241 .await?
242 .ok_or("User not found")?;
243
244 let (roles, active_role) = self.ensure_role_assignments(&user).await?;
245 Ok(self.build_user_response(&user, &roles, &active_role))
246 }
247
248 pub fn verify_token(&self, token: &str) -> Result<Claims, String> {
249 use jsonwebtoken::{decode, DecodingKey, Validation};
250
251 let token_data = decode::<Claims>(
252 token,
253 &DecodingKey::from_secret(self.jwt_secret.as_bytes()),
254 &Validation::default(),
255 )
256 .map_err(|e| format!("Invalid token: {}", e))?;
257
258 Ok(token_data.claims)
259 }
260
261 pub async fn refresh_token(
262 &self,
263 request: RefreshTokenRequest,
264 ) -> Result<LoginResponse, String> {
265 let refresh_token = self
266 .refresh_token_repo
267 .find_by_token(&request.refresh_token)
268 .await?
269 .ok_or_else(|| {
270 tokio::spawn(async {
272 log_audit_event(
273 AuditEventType::InvalidToken,
274 None,
275 None,
276 Some("Invalid refresh token attempted".to_string()),
277 None,
278 )
279 .await;
280 });
281 "Invalid refresh token".to_string()
282 })?;
283
284 if !refresh_token.is_valid() {
285 let user_id = refresh_token.user_id;
287 let reason = if refresh_token.is_expired() {
288 "Expired refresh token"
289 } else {
290 "Revoked refresh token"
291 };
292 tokio::spawn(async move {
293 log_audit_event(
294 AuditEventType::InvalidToken,
295 Some(user_id),
296 None,
297 Some(format!("{} attempted", reason)),
298 None,
299 )
300 .await;
301 });
302 return Err("Refresh token expired or revoked".to_string());
303 }
304
305 let user = self
306 .user_repo
307 .find_by_id(refresh_token.user_id)
308 .await?
309 .ok_or("User not found")?;
310
311 if !user.is_active {
312 let user_id = user.id;
314 tokio::spawn(async move {
315 log_audit_event(
316 AuditEventType::AuthenticationFailed,
317 Some(user_id),
318 None,
319 Some("Refresh token attempt on deactivated account".to_string()),
320 None,
321 )
322 .await;
323 });
324 return Err("User account is deactivated".to_string());
325 }
326
327 let (roles, active_role) = self.ensure_role_assignments(&user).await?;
328 let user_for_token = self.apply_active_role_metadata(&user, &active_role).await?;
329 let token = self.generate_token(&user_for_token, &active_role)?;
330
331 self.refresh_token_repo
333 .revoke(&request.refresh_token)
334 .await?;
335
336 let new_refresh_token_string = self.generate_refresh_token_string(&user_for_token);
337 let new_refresh_token =
338 RefreshToken::new(user_for_token.id, new_refresh_token_string.clone());
339 self.refresh_token_repo.create(&new_refresh_token).await?;
340
341 let user_id = user_for_token.id;
343 let organization_id = active_role.organization_id;
344 tokio::spawn(async move {
345 log_audit_event(
346 AuditEventType::TokenRefresh,
347 Some(user_id),
348 organization_id,
349 Some("Refresh token successfully exchanged".to_string()),
350 None,
351 )
352 .await;
353 });
354
355 Ok(LoginResponse {
356 token,
357 refresh_token: new_refresh_token_string,
358 user: self.build_user_response(&user_for_token, &roles, &active_role),
359 })
360 }
361
362 pub async fn revoke_all_refresh_tokens(&self, user_id: Uuid) -> Result<u64, String> {
363 self.refresh_token_repo.revoke_all_for_user(user_id).await
364 }
365
366 fn build_user_response(
367 &self,
368 user: &User,
369 roles: &[UserRoleAssignment],
370 active_role: &UserRoleAssignment,
371 ) -> UserResponse {
372 UserResponse {
373 id: user.id,
374 email: user.email.clone(),
375 first_name: user.first_name.clone(),
376 last_name: user.last_name.clone(),
377 role: active_role.role.to_string(),
378 organization_id: active_role.organization_id,
379 is_active: user.is_active,
380 roles: roles.iter().map(Self::summarize_role).collect(),
381 active_role: Some(Self::summarize_role(active_role)),
382 }
383 }
384
385 async fn ensure_role_assignments(
386 &self,
387 user: &User,
388 ) -> Result<(Vec<UserRoleAssignment>, UserRoleAssignment), String> {
389 let mut assignments = self.user_role_repo.list_for_user(user.id).await?;
390
391 if assignments.is_empty() {
392 let assignment = self
393 .user_role_repo
394 .create(&UserRoleAssignment::new(
395 user.id,
396 user.role.clone(),
397 user.organization_id,
398 true,
399 ))
400 .await?;
401 assignments.push(assignment.clone());
402 }
403
404 if !assignments.iter().any(|assignment| assignment.is_primary) {
405 let first = assignments[0].id;
406 self.user_role_repo.set_primary_role(user.id, first).await?;
407 assignments = self.user_role_repo.list_for_user(user.id).await?;
408 }
409
410 let active = assignments
411 .iter()
412 .find(|assignment| assignment.is_primary)
413 .cloned()
414 .unwrap_or_else(|| assignments[0].clone());
415
416 Ok((assignments, active))
417 }
418
419 async fn apply_active_role_metadata(
420 &self,
421 user: &User,
422 active_role: &UserRoleAssignment,
423 ) -> Result<User, String> {
424 let mut updated_user = user.clone();
425 let mut requires_update = false;
426
427 if updated_user.role != active_role.role {
428 updated_user.role = active_role.role.clone();
429 requires_update = true;
430 }
431
432 if updated_user.organization_id != active_role.organization_id {
433 updated_user.organization_id = active_role.organization_id;
434 requires_update = true;
435 }
436
437 if requires_update {
438 updated_user.updated_at = Utc::now();
439 return self.user_repo.update(&updated_user).await;
440 }
441
442 Ok(updated_user)
443 }
444
445 fn summarize_role(assignment: &UserRoleAssignment) -> UserRoleSummary {
446 UserRoleSummary {
447 id: assignment.id,
448 role: assignment.role.to_string(),
449 organization_id: assignment.organization_id,
450 is_primary: assignment.is_primary,
451 }
452 }
453
454 fn generate_token(
455 &self,
456 user: &User,
457 active_role: &UserRoleAssignment,
458 ) -> Result<String, String> {
459 let now = Utc::now().timestamp();
460 let expiration = now + (15 * 60);
461
462 let claims = Claims {
463 sub: user.id.to_string(),
464 email: user.email.clone(),
465 role: active_role.role.to_string(),
466 organization_id: active_role.organization_id,
467 role_id: Some(active_role.id),
468 exp: expiration,
469 iat: now,
470 };
471
472 encode(
473 &Header::default(),
474 &claims,
475 &EncodingKey::from_secret(self.jwt_secret.as_bytes()),
476 )
477 .map_err(|e| format!("Failed to generate token: {}", e))
478 }
479
480 fn generate_refresh_token_string(&self, user: &User) -> String {
481 let now = Utc::now().timestamp();
482 format!("{}:{}:{}", user.id, now, uuid::Uuid::new_v4())
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::application::ports::{RefreshTokenRepository, UserRoleRepository};
490 use crate::domain::entities::{RefreshToken, User, UserRole, UserRoleAssignment};
491 use async_trait::async_trait;
492 use mockall::mock;
493 use std::sync::Arc;
494
495 use crate::application::ports::user_repository::MockUserRepo;
497
498 mock! {
500 pub RefreshTokenRepo {}
501
502 #[async_trait]
503 impl RefreshTokenRepository for RefreshTokenRepo {
504 async fn create(&self, refresh_token: &RefreshToken) -> Result<RefreshToken, String>;
505 async fn find_by_token(&self, token: &str) -> Result<Option<RefreshToken>, String>;
506 async fn find_by_user_id(&self, user_id: Uuid) -> Result<Vec<RefreshToken>, String>;
507 async fn revoke(&self, token: &str) -> Result<bool, String>;
508 async fn revoke_all_for_user(&self, user_id: Uuid) -> Result<u64, String>;
509 async fn delete_expired(&self) -> Result<u64, String>;
510 }
511 }
512
513 mock! {
515 pub UserRoleRepo {}
516
517 #[async_trait]
518 impl UserRoleRepository for UserRoleRepo {
519 async fn create(&self, assignment: &UserRoleAssignment) -> Result<UserRoleAssignment, String>;
520 async fn list_for_user(&self, user_id: Uuid) -> Result<Vec<UserRoleAssignment>, String>;
521 async fn list_for_users(&self, user_ids: &[Uuid]) -> Result<std::collections::HashMap<Uuid, Vec<UserRoleAssignment>>, String>;
522 async fn replace_all(&self, user_id: Uuid, assignments: &[UserRoleAssignment]) -> Result<(), String>;
523 async fn find_by_id(&self, id: Uuid) -> Result<Option<UserRoleAssignment>, String>;
524 async fn set_primary_role(&self, user_id: Uuid, role_id: Uuid) -> Result<UserRoleAssignment, String>;
525 }
526 }
527
528 const TEST_JWT_SECRET: &str = "test-secret-key-that-is-long-enough-for-jwt";
529
530 fn make_user(email: &str, password: &str, org_id: Option<Uuid>) -> User {
532 let hash = bcrypt::hash(password, 4).expect("bcrypt hash");
533 User::new(
534 email.to_string(),
535 hash,
536 "Test".to_string(),
537 "User".to_string(),
538 UserRole::Syndic,
539 org_id,
540 )
541 .expect("valid user")
542 }
543
544 fn make_primary_role(user_id: Uuid, org_id: Option<Uuid>) -> UserRoleAssignment {
546 UserRoleAssignment::new(user_id, UserRole::Syndic, org_id, true)
547 }
548
549 fn build_use_cases(
551 user_repo: MockUserRepo,
552 refresh_repo: MockRefreshTokenRepo,
553 role_repo: MockUserRoleRepo,
554 ) -> AuthUseCases {
555 AuthUseCases::new(
556 Arc::new(user_repo),
557 Arc::new(refresh_repo),
558 Arc::new(role_repo),
559 TEST_JWT_SECRET.to_string(),
560 )
561 }
562
563 #[tokio::test]
566 async fn test_login_success() {
567 let org_id = Some(Uuid::new_v4());
568 let user = make_user("login@example.com", "password123", org_id);
569 let user_clone = user.clone();
570 let _user_for_update = user.clone();
571 let role = make_primary_role(user.id, org_id);
572 let role_clone = role.clone();
573
574 let mut user_repo = MockUserRepo::new();
575 user_repo
576 .expect_find_by_email()
577 .withf(|e| e == "login@example.com")
578 .returning(move |_| Ok(Some(user_clone.clone())));
579 user_repo.expect_update().returning(move |u| Ok(u.clone()));
580
581 let mut refresh_repo = MockRefreshTokenRepo::new();
582 refresh_repo.expect_create().returning(|rt| Ok(rt.clone()));
583
584 let mut role_repo = MockUserRoleRepo::new();
585 role_repo
586 .expect_list_for_user()
587 .returning(move |_| Ok(vec![role_clone.clone()]));
588
589 let uc = build_use_cases(user_repo, refresh_repo, role_repo);
590
591 let result = uc
592 .login(LoginRequest {
593 email: "login@example.com".to_string(),
594 password: "password123".to_string(),
595 })
596 .await;
597
598 assert!(result.is_ok(), "login should succeed: {:?}", result.err());
599 let response = result.unwrap();
600 assert!(!response.token.is_empty());
601 assert!(!response.refresh_token.is_empty());
602 assert_eq!(response.user.email, "login@example.com");
603 assert!(response.user.is_active);
604 }
605
606 #[tokio::test]
609 async fn test_login_invalid_email() {
610 let mut user_repo = MockUserRepo::new();
611 user_repo.expect_find_by_email().returning(|_| Ok(None));
612
613 let uc = build_use_cases(
614 user_repo,
615 MockRefreshTokenRepo::new(),
616 MockUserRoleRepo::new(),
617 );
618
619 let result = uc
620 .login(LoginRequest {
621 email: "nonexistent@example.com".to_string(),
622 password: "whatever".to_string(),
623 })
624 .await;
625
626 assert!(result.is_err());
627 assert_eq!(result.unwrap_err(), "Invalid email or password");
628 }
629
630 #[tokio::test]
633 async fn test_login_invalid_password() {
634 let user = make_user("user@example.com", "correct_password", None);
635 let user_clone = user.clone();
636
637 let mut user_repo = MockUserRepo::new();
638 user_repo
639 .expect_find_by_email()
640 .returning(move |_| Ok(Some(user_clone.clone())));
641
642 let uc = build_use_cases(
643 user_repo,
644 MockRefreshTokenRepo::new(),
645 MockUserRoleRepo::new(),
646 );
647
648 let result = uc
649 .login(LoginRequest {
650 email: "user@example.com".to_string(),
651 password: "wrong_password".to_string(),
652 })
653 .await;
654
655 assert!(result.is_err());
656 assert_eq!(result.unwrap_err(), "Invalid email or password");
657 }
658
659 #[tokio::test]
662 async fn test_login_deactivated_account() {
663 let mut user = make_user("deactivated@example.com", "password123", None);
664 user.deactivate();
665 let user_clone = user.clone();
666
667 let mut user_repo = MockUserRepo::new();
668 user_repo
669 .expect_find_by_email()
670 .returning(move |_| Ok(Some(user_clone.clone())));
671
672 let uc = build_use_cases(
673 user_repo,
674 MockRefreshTokenRepo::new(),
675 MockUserRoleRepo::new(),
676 );
677
678 let result = uc
679 .login(LoginRequest {
680 email: "deactivated@example.com".to_string(),
681 password: "password123".to_string(),
682 })
683 .await;
684
685 assert!(result.is_err());
686 assert_eq!(result.unwrap_err(), "User account is deactivated");
687 }
688
689 #[tokio::test]
692 async fn test_register_success() {
693 let mut user_repo = MockUserRepo::new();
694 user_repo.expect_find_by_email().returning(|_| Ok(None)); user_repo.expect_create().returning(|u| Ok(u.clone()));
696 user_repo.expect_update().returning(|u| Ok(u.clone()));
697
698 let mut refresh_repo = MockRefreshTokenRepo::new();
699 refresh_repo.expect_create().returning(|rt| Ok(rt.clone()));
700
701 let mut role_repo = MockUserRoleRepo::new();
702 role_repo.expect_create().returning(|a| Ok(a.clone()));
703
704 let uc = build_use_cases(user_repo, refresh_repo, role_repo);
705
706 let result = uc
707 .register(RegisterRequest {
708 email: "new@example.com".to_string(),
709 password: "password123".to_string(),
710 first_name: "Alice".to_string(),
711 last_name: "Dupont".to_string(),
712 role: "syndic".to_string(),
713 organization_id: None,
714 })
715 .await;
716
717 assert!(
718 result.is_ok(),
719 "register should succeed: {:?}",
720 result.err()
721 );
722 let response = result.unwrap();
723 assert!(!response.token.is_empty());
724 assert_eq!(response.user.email, "new@example.com");
725 assert_eq!(response.user.first_name, "Alice");
726 }
727
728 #[tokio::test]
731 async fn test_register_duplicate_email() {
732 let existing = make_user("taken@example.com", "pw123456", None);
733 let existing_clone = existing.clone();
734
735 let mut user_repo = MockUserRepo::new();
736 user_repo
737 .expect_find_by_email()
738 .returning(move |_| Ok(Some(existing_clone.clone())));
739
740 let uc = build_use_cases(
741 user_repo,
742 MockRefreshTokenRepo::new(),
743 MockUserRoleRepo::new(),
744 );
745
746 let result = uc
747 .register(RegisterRequest {
748 email: "taken@example.com".to_string(),
749 password: "password123".to_string(),
750 first_name: "Bob".to_string(),
751 last_name: "Martin".to_string(),
752 role: "owner".to_string(),
753 organization_id: None,
754 })
755 .await;
756
757 assert!(result.is_err());
758 assert_eq!(result.unwrap_err(), "Email already exists");
759 }
760
761 #[tokio::test]
764 async fn test_switch_role_success() {
765 let org_id = Some(Uuid::new_v4());
766 let user = make_user("multi@example.com", "password123", org_id);
767 let user_clone = user.clone();
768
769 let target_role = UserRoleAssignment::new(user.id, UserRole::Accountant, org_id, false);
771 let target_role_id = target_role.id;
772 let target_clone = target_role.clone();
773
774 let mut switched_role = target_role.clone();
776 switched_role.is_primary = true;
777 let switched_clone = switched_role.clone();
778 let switched_for_list = switched_role.clone();
779
780 let mut user_repo = MockUserRepo::new();
781 user_repo
782 .expect_find_by_id()
783 .returning(move |_| Ok(Some(user_clone.clone())));
784 user_repo.expect_update().returning(|u| Ok(u.clone()));
785
786 let mut refresh_repo = MockRefreshTokenRepo::new();
787 refresh_repo.expect_create().returning(|rt| Ok(rt.clone()));
788
789 let mut role_repo = MockUserRoleRepo::new();
790 role_repo
791 .expect_find_by_id()
792 .returning(move |_| Ok(Some(target_clone.clone())));
793 role_repo
794 .expect_set_primary_role()
795 .returning(move |_, _| Ok(switched_clone.clone()));
796 role_repo
797 .expect_list_for_user()
798 .returning(move |_| Ok(vec![switched_for_list.clone()]));
799
800 let uc = build_use_cases(user_repo, refresh_repo, role_repo);
801
802 let result = uc.switch_active_role(user.id, target_role_id).await;
803
804 assert!(
805 result.is_ok(),
806 "switch_role should succeed: {:?}",
807 result.err()
808 );
809 let response = result.unwrap();
810 assert!(!response.token.is_empty());
811 assert_eq!(response.user.role, "accountant");
812 }
813
814 #[tokio::test]
817 async fn test_switch_role_not_found() {
818 let user = make_user("user@example.com", "password123", None);
819 let user_clone = user.clone();
820
821 let mut user_repo = MockUserRepo::new();
822 user_repo
823 .expect_find_by_id()
824 .returning(move |_| Ok(Some(user_clone.clone())));
825
826 let mut role_repo = MockUserRoleRepo::new();
827 role_repo.expect_find_by_id().returning(|_| Ok(None)); let uc = build_use_cases(user_repo, MockRefreshTokenRepo::new(), role_repo);
830
831 let result = uc.switch_active_role(user.id, Uuid::new_v4()).await;
832
833 assert!(result.is_err());
834 assert_eq!(result.unwrap_err(), "Role assignment not found");
835 }
836
837 #[tokio::test]
840 async fn test_verify_token_valid() {
841 let org_id = Some(Uuid::new_v4());
842 let user = make_user("verify@example.com", "password123", org_id);
843 let role = make_primary_role(user.id, org_id);
844
845 let uc = build_use_cases(
846 MockUserRepo::new(),
847 MockRefreshTokenRepo::new(),
848 MockUserRoleRepo::new(),
849 );
850
851 let token = uc.generate_token(&user, &role).expect("token generation");
853
854 let claims = uc.verify_token(&token);
855 assert!(claims.is_ok(), "verify should succeed: {:?}", claims.err());
856 let claims = claims.unwrap();
857 assert_eq!(claims.sub, user.id.to_string());
858 assert_eq!(claims.email, "verify@example.com");
859 assert_eq!(claims.role, "syndic");
860 assert_eq!(claims.organization_id, org_id);
861 }
862
863 #[tokio::test]
866 async fn test_verify_token_invalid() {
867 let uc = build_use_cases(
868 MockUserRepo::new(),
869 MockRefreshTokenRepo::new(),
870 MockUserRoleRepo::new(),
871 );
872
873 let result = uc.verify_token("this.is.not.a.valid.jwt");
874 assert!(result.is_err());
875 assert!(result.unwrap_err().starts_with("Invalid token:"));
876 }
877
878 #[tokio::test]
881 async fn test_revoke_all_refresh_tokens() {
882 let user_id = Uuid::new_v4();
883
884 let mut refresh_repo = MockRefreshTokenRepo::new();
885 refresh_repo
886 .expect_revoke_all_for_user()
887 .withf(move |id| *id == user_id)
888 .returning(|_| Ok(3));
889
890 let uc = build_use_cases(MockUserRepo::new(), refresh_repo, MockUserRoleRepo::new());
891
892 let result = uc.revoke_all_refresh_tokens(user_id).await;
893 assert!(result.is_ok());
894 assert_eq!(result.unwrap(), 3);
895 }
896}