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