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, UserRepository, 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 find_by_id(&self, id: Uuid) -> Result<Option<UserRoleAssignment>, String>;
522 async fn set_primary_role(&self, user_id: Uuid, role_id: Uuid) -> Result<UserRoleAssignment, String>;
523 }
524 }
525
526 const TEST_JWT_SECRET: &str = "test-secret-key-that-is-long-enough-for-jwt";
527
528 fn make_user(email: &str, password: &str, org_id: Option<Uuid>) -> User {
530 let hash = bcrypt::hash(password, 4).expect("bcrypt hash");
531 User::new(
532 email.to_string(),
533 hash,
534 "Test".to_string(),
535 "User".to_string(),
536 UserRole::Syndic,
537 org_id,
538 )
539 .expect("valid user")
540 }
541
542 fn make_primary_role(user_id: Uuid, org_id: Option<Uuid>) -> UserRoleAssignment {
544 UserRoleAssignment::new(user_id, UserRole::Syndic, org_id, true)
545 }
546
547 fn build_use_cases(
549 user_repo: MockUserRepo,
550 refresh_repo: MockRefreshTokenRepo,
551 role_repo: MockUserRoleRepo,
552 ) -> AuthUseCases {
553 AuthUseCases::new(
554 Arc::new(user_repo),
555 Arc::new(refresh_repo),
556 Arc::new(role_repo),
557 TEST_JWT_SECRET.to_string(),
558 )
559 }
560
561 #[tokio::test]
564 async fn test_login_success() {
565 let org_id = Some(Uuid::new_v4());
566 let user = make_user("login@example.com", "password123", org_id);
567 let user_clone = user.clone();
568 let user_for_update = user.clone();
569 let role = make_primary_role(user.id, org_id);
570 let role_clone = role.clone();
571
572 let mut user_repo = MockUserRepo::new();
573 user_repo
574 .expect_find_by_email()
575 .withf(|e| e == "login@example.com")
576 .returning(move |_| Ok(Some(user_clone.clone())));
577 user_repo.expect_update().returning(move |u| Ok(u.clone()));
578
579 let mut refresh_repo = MockRefreshTokenRepo::new();
580 refresh_repo.expect_create().returning(|rt| Ok(rt.clone()));
581
582 let mut role_repo = MockUserRoleRepo::new();
583 role_repo
584 .expect_list_for_user()
585 .returning(move |_| Ok(vec![role_clone.clone()]));
586
587 let uc = build_use_cases(user_repo, refresh_repo, role_repo);
588
589 let result = uc
590 .login(LoginRequest {
591 email: "login@example.com".to_string(),
592 password: "password123".to_string(),
593 })
594 .await;
595
596 assert!(result.is_ok(), "login should succeed: {:?}", result.err());
597 let response = result.unwrap();
598 assert!(!response.token.is_empty());
599 assert!(!response.refresh_token.is_empty());
600 assert_eq!(response.user.email, "login@example.com");
601 assert!(response.user.is_active);
602 }
603
604 #[tokio::test]
607 async fn test_login_invalid_email() {
608 let mut user_repo = MockUserRepo::new();
609 user_repo.expect_find_by_email().returning(|_| Ok(None));
610
611 let uc = build_use_cases(
612 user_repo,
613 MockRefreshTokenRepo::new(),
614 MockUserRoleRepo::new(),
615 );
616
617 let result = uc
618 .login(LoginRequest {
619 email: "nonexistent@example.com".to_string(),
620 password: "whatever".to_string(),
621 })
622 .await;
623
624 assert!(result.is_err());
625 assert_eq!(result.unwrap_err(), "Invalid email or password");
626 }
627
628 #[tokio::test]
631 async fn test_login_invalid_password() {
632 let user = make_user("user@example.com", "correct_password", None);
633 let user_clone = user.clone();
634
635 let mut user_repo = MockUserRepo::new();
636 user_repo
637 .expect_find_by_email()
638 .returning(move |_| Ok(Some(user_clone.clone())));
639
640 let uc = build_use_cases(
641 user_repo,
642 MockRefreshTokenRepo::new(),
643 MockUserRoleRepo::new(),
644 );
645
646 let result = uc
647 .login(LoginRequest {
648 email: "user@example.com".to_string(),
649 password: "wrong_password".to_string(),
650 })
651 .await;
652
653 assert!(result.is_err());
654 assert_eq!(result.unwrap_err(), "Invalid email or password");
655 }
656
657 #[tokio::test]
660 async fn test_login_deactivated_account() {
661 let mut user = make_user("deactivated@example.com", "password123", None);
662 user.deactivate();
663 let user_clone = user.clone();
664
665 let mut user_repo = MockUserRepo::new();
666 user_repo
667 .expect_find_by_email()
668 .returning(move |_| Ok(Some(user_clone.clone())));
669
670 let uc = build_use_cases(
671 user_repo,
672 MockRefreshTokenRepo::new(),
673 MockUserRoleRepo::new(),
674 );
675
676 let result = uc
677 .login(LoginRequest {
678 email: "deactivated@example.com".to_string(),
679 password: "password123".to_string(),
680 })
681 .await;
682
683 assert!(result.is_err());
684 assert_eq!(result.unwrap_err(), "User account is deactivated");
685 }
686
687 #[tokio::test]
690 async fn test_register_success() {
691 let mut user_repo = MockUserRepo::new();
692 user_repo.expect_find_by_email().returning(|_| Ok(None)); user_repo.expect_create().returning(|u| Ok(u.clone()));
694 user_repo.expect_update().returning(|u| Ok(u.clone()));
695
696 let mut refresh_repo = MockRefreshTokenRepo::new();
697 refresh_repo.expect_create().returning(|rt| Ok(rt.clone()));
698
699 let mut role_repo = MockUserRoleRepo::new();
700 role_repo.expect_create().returning(|a| Ok(a.clone()));
701
702 let uc = build_use_cases(user_repo, refresh_repo, role_repo);
703
704 let result = uc
705 .register(RegisterRequest {
706 email: "new@example.com".to_string(),
707 password: "password123".to_string(),
708 first_name: "Alice".to_string(),
709 last_name: "Dupont".to_string(),
710 role: "syndic".to_string(),
711 organization_id: None,
712 })
713 .await;
714
715 assert!(
716 result.is_ok(),
717 "register should succeed: {:?}",
718 result.err()
719 );
720 let response = result.unwrap();
721 assert!(!response.token.is_empty());
722 assert_eq!(response.user.email, "new@example.com");
723 assert_eq!(response.user.first_name, "Alice");
724 }
725
726 #[tokio::test]
729 async fn test_register_duplicate_email() {
730 let existing = make_user("taken@example.com", "pw123456", None);
731 let existing_clone = existing.clone();
732
733 let mut user_repo = MockUserRepo::new();
734 user_repo
735 .expect_find_by_email()
736 .returning(move |_| Ok(Some(existing_clone.clone())));
737
738 let uc = build_use_cases(
739 user_repo,
740 MockRefreshTokenRepo::new(),
741 MockUserRoleRepo::new(),
742 );
743
744 let result = uc
745 .register(RegisterRequest {
746 email: "taken@example.com".to_string(),
747 password: "password123".to_string(),
748 first_name: "Bob".to_string(),
749 last_name: "Martin".to_string(),
750 role: "owner".to_string(),
751 organization_id: None,
752 })
753 .await;
754
755 assert!(result.is_err());
756 assert_eq!(result.unwrap_err(), "Email already exists");
757 }
758
759 #[tokio::test]
762 async fn test_switch_role_success() {
763 let org_id = Some(Uuid::new_v4());
764 let user = make_user("multi@example.com", "password123", org_id);
765 let user_clone = user.clone();
766
767 let target_role = UserRoleAssignment::new(user.id, UserRole::Accountant, org_id, false);
769 let target_role_id = target_role.id;
770 let target_clone = target_role.clone();
771
772 let mut switched_role = target_role.clone();
774 switched_role.is_primary = true;
775 let switched_clone = switched_role.clone();
776 let switched_for_list = switched_role.clone();
777
778 let mut user_repo = MockUserRepo::new();
779 user_repo
780 .expect_find_by_id()
781 .returning(move |_| Ok(Some(user_clone.clone())));
782 user_repo.expect_update().returning(|u| Ok(u.clone()));
783
784 let mut refresh_repo = MockRefreshTokenRepo::new();
785 refresh_repo.expect_create().returning(|rt| Ok(rt.clone()));
786
787 let mut role_repo = MockUserRoleRepo::new();
788 role_repo
789 .expect_find_by_id()
790 .returning(move |_| Ok(Some(target_clone.clone())));
791 role_repo
792 .expect_set_primary_role()
793 .returning(move |_, _| Ok(switched_clone.clone()));
794 role_repo
795 .expect_list_for_user()
796 .returning(move |_| Ok(vec![switched_for_list.clone()]));
797
798 let uc = build_use_cases(user_repo, refresh_repo, role_repo);
799
800 let result = uc.switch_active_role(user.id, target_role_id).await;
801
802 assert!(
803 result.is_ok(),
804 "switch_role should succeed: {:?}",
805 result.err()
806 );
807 let response = result.unwrap();
808 assert!(!response.token.is_empty());
809 assert_eq!(response.user.role, "accountant");
810 }
811
812 #[tokio::test]
815 async fn test_switch_role_not_found() {
816 let user = make_user("user@example.com", "password123", None);
817 let user_clone = user.clone();
818
819 let mut user_repo = MockUserRepo::new();
820 user_repo
821 .expect_find_by_id()
822 .returning(move |_| Ok(Some(user_clone.clone())));
823
824 let mut role_repo = MockUserRoleRepo::new();
825 role_repo.expect_find_by_id().returning(|_| Ok(None)); let uc = build_use_cases(user_repo, MockRefreshTokenRepo::new(), role_repo);
828
829 let result = uc.switch_active_role(user.id, Uuid::new_v4()).await;
830
831 assert!(result.is_err());
832 assert_eq!(result.unwrap_err(), "Role assignment not found");
833 }
834
835 #[tokio::test]
838 async fn test_verify_token_valid() {
839 let org_id = Some(Uuid::new_v4());
840 let user = make_user("verify@example.com", "password123", org_id);
841 let role = make_primary_role(user.id, org_id);
842
843 let uc = build_use_cases(
844 MockUserRepo::new(),
845 MockRefreshTokenRepo::new(),
846 MockUserRoleRepo::new(),
847 );
848
849 let token = uc.generate_token(&user, &role).expect("token generation");
851
852 let claims = uc.verify_token(&token);
853 assert!(claims.is_ok(), "verify should succeed: {:?}", claims.err());
854 let claims = claims.unwrap();
855 assert_eq!(claims.sub, user.id.to_string());
856 assert_eq!(claims.email, "verify@example.com");
857 assert_eq!(claims.role, "syndic");
858 assert_eq!(claims.organization_id, org_id);
859 }
860
861 #[tokio::test]
864 async fn test_verify_token_invalid() {
865 let uc = build_use_cases(
866 MockUserRepo::new(),
867 MockRefreshTokenRepo::new(),
868 MockUserRoleRepo::new(),
869 );
870
871 let result = uc.verify_token("this.is.not.a.valid.jwt");
872 assert!(result.is_err());
873 assert!(result.unwrap_err().starts_with("Invalid token:"));
874 }
875
876 #[tokio::test]
879 async fn test_revoke_all_refresh_tokens() {
880 let user_id = Uuid::new_v4();
881
882 let mut refresh_repo = MockRefreshTokenRepo::new();
883 refresh_repo
884 .expect_revoke_all_for_user()
885 .withf(move |id| *id == user_id)
886 .returning(|_| Ok(3));
887
888 let uc = build_use_cases(MockUserRepo::new(), refresh_repo, MockUserRoleRepo::new());
889
890 let result = uc.revoke_all_refresh_tokens(user_id).await;
891 assert!(result.is_ok());
892 assert_eq!(result.unwrap(), 3);
893 }
894}