1use paladin_core::platform::container::user::{UserError, UserProfile, UserRole};
9use paladin_core::platform::manager::user_service::{
10 UserLoginRequest, UserProfileUpdateRequest, UserRegistrationRequest, UserServiceTrait,
11};
12
13use axum::{
14 Extension, Router,
15 extract::{Path, State},
16 http::StatusCode,
17 response::Json,
18 routing::{get, post, put},
19};
20use paladin_ports::output::auth_port::AuthClaims;
21use serde::{Deserialize, Serialize};
22use std::sync::Arc;
23use uuid::Uuid;
24
25#[derive(Debug, Deserialize)]
27pub struct RegisterUserRequest {
28 pub username: String,
29 pub email: String,
30 pub password: String,
31 pub first_name: Option<String>,
32 pub last_name: Option<String>,
33 pub bio: Option<String>,
34 pub timezone: Option<String>,
35 pub locale: Option<String>,
36}
37
38#[derive(Debug, Deserialize)]
40pub struct LoginUserRequest {
41 pub email: String,
42 pub password: String,
43}
44
45#[derive(Debug, Deserialize)]
47pub struct UpdateUserProfileRequest {
48 pub username: Option<String>,
49 pub email: Option<String>,
50 pub first_name: Option<String>,
51 pub last_name: Option<String>,
52 pub bio: Option<String>,
53 pub avatar_url: Option<String>,
54 pub timezone: Option<String>,
55 pub locale: Option<String>,
56}
57
58#[derive(Debug, Serialize)]
60pub struct UserResponse {
61 pub id: Uuid,
62 pub username: String,
63 pub email: String,
64 pub is_active: bool,
65 pub is_verified: bool,
66 pub profile: UserProfileResponse,
67 pub created_at: String,
68 pub modified_at: String,
69}
70
71#[derive(Debug, Serialize)]
73pub struct UserProfileResponse {
74 pub first_name: Option<String>,
75 pub last_name: Option<String>,
76 pub bio: Option<String>,
77 pub avatar_url: Option<String>,
78 pub timezone: Option<String>,
79 pub locale: Option<String>,
80}
81
82#[derive(Debug, Serialize)]
84pub struct LoginResponse {
85 pub user_id: Uuid,
86 pub username: String,
87 pub email: String,
88 pub is_verified: bool,
89 pub success: bool,
90 pub token: Option<String>,
92 pub token_expires_at: Option<String>,
94}
95
96#[derive(Debug, Serialize)]
98pub struct ErrorResponse {
99 pub error: String,
100 pub code: String,
101}
102
103#[derive(Debug, Serialize)]
105pub struct ApiResponse<T> {
106 pub success: bool,
107 pub data: Option<T>,
108 pub error: Option<ErrorResponse>,
109}
110
111impl<T> ApiResponse<T> {
112 pub fn success(data: T) -> Self {
113 Self {
114 success: true,
115 data: Some(data),
116 error: None,
117 }
118 }
119
120 pub fn error(error: String, code: String) -> Self {
121 Self {
122 success: false,
123 data: None,
124 error: Some(ErrorResponse { error, code }),
125 }
126 }
127}
128
129fn user_error_to_response(error: UserError) -> (StatusCode, Json<ApiResponse<()>>) {
131 let (status, code, message) = match error {
132 UserError::InvalidEmail(_) => (StatusCode::BAD_REQUEST, "INVALID_EMAIL", error.to_string()),
133 UserError::InvalidUsername(_) => (
134 StatusCode::BAD_REQUEST,
135 "INVALID_USERNAME",
136 error.to_string(),
137 ),
138 UserError::InvalidRole(_) => (StatusCode::BAD_REQUEST, "INVALID_ROLE", error.to_string()),
139 UserError::InvalidPassword(_) => (
140 StatusCode::BAD_REQUEST,
141 "INVALID_PASSWORD",
142 error.to_string(),
143 ),
144 UserError::EmailAlreadyExists(_) => {
145 (StatusCode::CONFLICT, "EMAIL_EXISTS", error.to_string())
146 }
147 UserError::UsernameAlreadyExists(_) => {
148 (StatusCode::CONFLICT, "USERNAME_EXISTS", error.to_string())
149 }
150 UserError::UserNotFound(_) => (StatusCode::NOT_FOUND, "USER_NOT_FOUND", error.to_string()),
151 UserError::UserNotFoundByEmail(_) => {
152 (StatusCode::NOT_FOUND, "USER_NOT_FOUND", error.to_string())
153 }
154 UserError::AuthenticationFailed => (
155 StatusCode::UNAUTHORIZED,
156 "AUTH_FAILED",
157 "Invalid credentials".to_string(),
158 ),
159 UserError::UserNotActive => (
160 StatusCode::FORBIDDEN,
161 "USER_INACTIVE",
162 "User account is not active".to_string(),
163 ),
164 UserError::UserNotVerified => (
165 StatusCode::FORBIDDEN,
166 "USER_NOT_VERIFIED",
167 "User email is not verified".to_string(),
168 ),
169 UserError::RepositoryError(_) => (
170 StatusCode::INTERNAL_SERVER_ERROR,
171 "INTERNAL_ERROR",
172 "Internal server error".to_string(),
173 ),
174 UserError::HashError(_) => (
175 StatusCode::INTERNAL_SERVER_ERROR,
176 "INTERNAL_ERROR",
177 "Internal server error".to_string(),
178 ),
179 };
180
181 (status, Json(ApiResponse::error(message, code.to_string())))
182}
183
184fn ensure_self_or_admin(
187 claims: &AuthClaims,
188 target: Uuid,
189) -> Result<(), (StatusCode, Json<ApiResponse<()>>)> {
190 if claims.role == UserRole::Admin || claims.user_id == target {
191 Ok(())
192 } else {
193 Err((
194 StatusCode::FORBIDDEN,
195 Json(ApiResponse::error(
196 "Forbidden".to_string(),
197 "FORBIDDEN".to_string(),
198 )),
199 ))
200 }
201}
202
203fn user_to_response(user: &paladin_core::platform::container::user::User) -> UserResponse {
205 UserResponse {
206 id: user.uuid,
207 username: user.username().to_string(),
208 email: user.email().value().to_string(),
209 is_active: user.is_active(),
210 is_verified: user.is_verified(),
211 profile: UserProfileResponse {
212 first_name: user.profile().first_name.clone(),
213 last_name: user.profile().last_name.clone(),
214 bio: user.profile().bio.clone(),
215 avatar_url: user.profile().avatar_url.clone(),
216 timezone: user.profile().timezone.clone(),
217 locale: user.profile().locale.clone(),
218 },
219 created_at: user.created.to_rfc3339(),
220 modified_at: user.modified.to_rfc3339(),
221 }
222}
223
224pub(crate) async fn register_user(
226 State(user_service): State<Arc<dyn UserServiceTrait>>,
227 Json(request): Json<RegisterUserRequest>,
228) -> Result<Json<ApiResponse<UserResponse>>, (StatusCode, Json<ApiResponse<()>>)> {
229 let profile = if request.first_name.is_some()
230 || request.last_name.is_some()
231 || request.bio.is_some()
232 || request.timezone.is_some()
233 || request.locale.is_some()
234 {
235 Some(UserProfile {
236 first_name: request.first_name,
237 last_name: request.last_name,
238 bio: request.bio,
239 avatar_url: None,
240 timezone: request.timezone.or_else(|| Some("UTC".to_string())),
241 locale: request.locale.or_else(|| Some("en-US".to_string())),
242 })
243 } else {
244 None
245 };
246
247 let registration_request = UserRegistrationRequest {
248 username: request.username,
249 email: request.email,
250 password: request.password,
251 profile,
252 };
253
254 match user_service.register_user(registration_request).await {
255 Ok(user) => Ok(Json(ApiResponse::success(user_to_response(&user)))),
256 Err(error) => Err(user_error_to_response(error)),
257 }
258}
259
260pub(crate) async fn login_user(
262 State(user_service): State<Arc<dyn UserServiceTrait>>,
263 Json(request): Json<LoginUserRequest>,
264) -> Result<Json<ApiResponse<LoginResponse>>, (StatusCode, Json<ApiResponse<()>>)> {
265 let login_request = UserLoginRequest {
266 email: request.email,
267 password: request.password,
268 };
269
270 match user_service.login_user(login_request).await {
271 Ok(result) => Ok(Json(ApiResponse::success(LoginResponse {
272 user_id: result.user_id,
273 username: result.username,
274 email: result.email,
275 is_verified: result.is_verified,
276 success: result.success,
277 token: result.token,
278 token_expires_at: result.token_expires_at.map(|t| t.to_rfc3339()),
279 }))),
280 Err(error) => Err(user_error_to_response(error)),
281 }
282}
283
284pub(crate) async fn get_user(
286 State(user_service): State<Arc<dyn UserServiceTrait>>,
287 claims: Option<Extension<AuthClaims>>,
288 Path(user_id): Path<Uuid>,
289) -> Result<Json<ApiResponse<UserResponse>>, (StatusCode, Json<ApiResponse<()>>)> {
290 if let Some(Extension(claims)) = &claims {
291 ensure_self_or_admin(claims, user_id)?;
292 }
293 match user_service.get_user_by_id(user_id).await {
294 Ok(Some(user)) => Ok(Json(ApiResponse::success(user_to_response(&user)))),
295 Ok(None) => Err((
296 StatusCode::NOT_FOUND,
297 Json(ApiResponse::error(
298 "User not found".to_string(),
299 "USER_NOT_FOUND".to_string(),
300 )),
301 )),
302 Err(error) => Err(user_error_to_response(error)),
303 }
304}
305
306pub(crate) async fn update_user_profile(
308 State(user_service): State<Arc<dyn UserServiceTrait>>,
309 claims: Option<Extension<AuthClaims>>,
310 Path(user_id): Path<Uuid>,
311 Json(request): Json<UpdateUserProfileRequest>,
312) -> Result<Json<ApiResponse<UserResponse>>, (StatusCode, Json<ApiResponse<()>>)> {
313 if let Some(Extension(claims)) = &claims {
314 ensure_self_or_admin(claims, user_id)?;
315 }
316 let profile = if request.first_name.is_some()
317 || request.last_name.is_some()
318 || request.bio.is_some()
319 || request.avatar_url.is_some()
320 || request.timezone.is_some()
321 || request.locale.is_some()
322 {
323 let current_user = match user_service.get_user_by_id(user_id).await {
325 Ok(Some(user)) => user,
326 Ok(None) => {
327 return Err((
328 StatusCode::NOT_FOUND,
329 Json(ApiResponse::error(
330 "User not found".to_string(),
331 "USER_NOT_FOUND".to_string(),
332 )),
333 ));
334 }
335 Err(error) => return Err(user_error_to_response(error)),
336 };
337
338 Some(UserProfile {
339 first_name: request
340 .first_name
341 .or(current_user.profile().first_name.clone()),
342 last_name: request
343 .last_name
344 .or(current_user.profile().last_name.clone()),
345 bio: request.bio.or(current_user.profile().bio.clone()),
346 avatar_url: request
347 .avatar_url
348 .or(current_user.profile().avatar_url.clone()),
349 timezone: request.timezone.or(current_user.profile().timezone.clone()),
350 locale: request.locale.or(current_user.profile().locale.clone()),
351 })
352 } else {
353 None
354 };
355
356 let update_request = UserProfileUpdateRequest {
357 user_id,
358 username: request.username,
359 email: request.email,
360 profile,
361 };
362
363 match user_service.update_user_profile(update_request).await {
364 Ok(user) => Ok(Json(ApiResponse::success(user_to_response(&user)))),
365 Err(error) => Err(user_error_to_response(error)),
366 }
367}
368
369async fn activate_user(
371 State(user_service): State<Arc<dyn UserServiceTrait>>,
372 Path(user_id): Path<Uuid>,
373) -> Result<Json<ApiResponse<()>>, (StatusCode, Json<ApiResponse<()>>)> {
374 match user_service.activate_user(user_id).await {
375 Ok(_) => Ok(Json(ApiResponse::success(()))),
376 Err(error) => Err(user_error_to_response(error)),
377 }
378}
379
380async fn deactivate_user(
382 State(user_service): State<Arc<dyn UserServiceTrait>>,
383 Path(user_id): Path<Uuid>,
384) -> Result<Json<ApiResponse<()>>, (StatusCode, Json<ApiResponse<()>>)> {
385 match user_service.deactivate_user(user_id).await {
386 Ok(_) => Ok(Json(ApiResponse::success(()))),
387 Err(error) => Err(user_error_to_response(error)),
388 }
389}
390
391async fn verify_user(
393 State(user_service): State<Arc<dyn UserServiceTrait>>,
394 Path(user_id): Path<Uuid>,
395) -> Result<Json<ApiResponse<()>>, (StatusCode, Json<ApiResponse<()>>)> {
396 match user_service.verify_user(user_id).await {
397 Ok(_) => Ok(Json(ApiResponse::success(()))),
398 Err(error) => Err(user_error_to_response(error)),
399 }
400}
401
402pub(crate) async fn delete_user(
404 State(user_service): State<Arc<dyn UserServiceTrait>>,
405 Path(user_id): Path<Uuid>,
406) -> Result<Json<ApiResponse<()>>, (StatusCode, Json<ApiResponse<()>>)> {
407 match user_service.delete_user(user_id).await {
408 Ok(()) => Ok(Json(ApiResponse::success(()))),
409 Err(error) => Err(user_error_to_response(error)),
410 }
411}
412
413pub(crate) async fn list_users(
415 State(user_service): State<Arc<dyn UserServiceTrait>>,
416) -> Result<Json<ApiResponse<Vec<UserResponse>>>, (StatusCode, Json<ApiResponse<()>>)> {
417 match user_service.list_users().await {
418 Ok(users) => Ok(Json(ApiResponse::success(
419 users.iter().map(user_to_response).collect(),
420 ))),
421 Err(error) => Err(user_error_to_response(error)),
422 }
423}
424
425pub fn create_user_routes(user_service: Arc<dyn UserServiceTrait>) -> Router {
427 Router::new()
428 .route("/users/register", post(register_user))
429 .route("/users/login", post(login_user))
430 .route("/users/:id", get(get_user))
431 .route("/users/:id", put(update_user_profile))
432 .route("/users/:id/activate", post(activate_user))
433 .route("/users/:id/deactivate", post(deactivate_user))
434 .route("/users/:id/verify", post(verify_user))
435 .with_state(user_service)
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use async_trait::async_trait;
442 use chrono;
443 use paladin_core::platform::container::user::{User, UserError, UserProfile};
444 use paladin_core::platform::manager::user_service::UserAuthenticationResult;
445 use std::collections::HashMap;
446 use std::sync::Mutex;
447
448 #[allow(dead_code)]
450 #[derive(Debug, Clone)]
451 struct MockUser {
452 pub uuid: Uuid,
453 pub username: String,
454 pub email: String,
455 pub is_active: bool,
456 pub is_verified: bool,
457 pub profile: UserProfile,
458 pub created: chrono::DateTime<chrono::Utc>,
459 pub modified: chrono::DateTime<chrono::Utc>,
460 }
461
462 impl MockUser {
463 fn new() -> Self {
464 Self {
465 uuid: Uuid::new_v4(),
466 username: "testuser".to_string(),
467 email: "test@example.com".to_string(),
468 is_active: true,
469 is_verified: true,
470 profile: UserProfile {
471 first_name: Some("Test".to_string()),
472 last_name: Some("User".to_string()),
473 bio: Some("Test bio".to_string()),
474 avatar_url: None,
475 timezone: Some("UTC".to_string()),
476 locale: Some("en-US".to_string()),
477 },
478 created: chrono::Utc::now(),
479 modified: chrono::Utc::now(),
480 }
481 }
482 }
483
484 struct MockUserService {
486 users: Mutex<HashMap<Uuid, User>>,
487 login_results: Mutex<HashMap<String, Result<UserAuthenticationResult, UserError>>>,
488 should_fail: Mutex<bool>,
489 }
490
491 impl MockUserService {
492 fn new() -> Self {
493 Self {
494 users: Mutex::new(HashMap::new()),
495 login_results: Mutex::new(HashMap::new()),
496 should_fail: Mutex::new(false),
497 }
498 }
499
500 fn set_should_fail(&self, fail: bool) {
501 *self.should_fail.lock().unwrap() = fail;
502 }
503
504 fn add_user(&self, user: User) {
505 self.users.lock().unwrap().insert(user.uuid, user);
506 }
507
508 #[allow(dead_code)]
509 fn set_login_result(
510 &self,
511 email: &str,
512 result: Result<UserAuthenticationResult, UserError>,
513 ) {
514 self.login_results
515 .lock()
516 .unwrap()
517 .insert(email.to_string(), result);
518 }
519 }
520
521 #[async_trait]
522 impl UserServiceTrait for MockUserService {
523 async fn register_user(&self, request: UserRegistrationRequest) -> Result<User, UserError> {
524 if *self.should_fail.lock().unwrap() {
525 return Err(UserError::RepositoryError("Mock error".to_string()));
526 }
527
528 let user = User::new_user(
529 request.username,
530 paladin_core::platform::container::user::Email::new(request.email).unwrap(),
531 "mock_hash".to_string(),
532 request.profile,
533 );
534 self.add_user(user.clone());
535
536 Ok(user)
537 }
538
539 async fn login_user(
540 &self,
541 request: UserLoginRequest,
542 ) -> Result<UserAuthenticationResult, UserError> {
543 if *self.should_fail.lock().unwrap() {
544 return Err(UserError::AuthenticationFailed);
545 }
546
547 if let Some(result) = self.login_results.lock().unwrap().remove(&request.email) {
548 result
549 } else {
550 Ok(UserAuthenticationResult {
552 user_id: Uuid::new_v4(),
553 username: "testuser".to_string(),
554 email: request.email,
555 is_verified: true,
556 success: true,
557 token: None,
558 token_expires_at: None,
559 })
560 }
561 }
562
563 async fn get_user_by_id(&self, user_id: Uuid) -> Result<Option<User>, UserError> {
564 if *self.should_fail.lock().unwrap() {
565 return Err(UserError::RepositoryError("Mock error".to_string()));
566 }
567
568 if let Some(user) = self.users.lock().unwrap().get(&user_id) {
569 Ok(Some(user.clone()))
570 } else {
571 Ok(None)
572 }
573 }
574
575 async fn update_user_profile(
576 &self,
577 request: UserProfileUpdateRequest,
578 ) -> Result<User, UserError> {
579 if *self.should_fail.lock().unwrap() {
580 return Err(UserError::RepositoryError("Mock error".to_string()));
581 }
582
583 let existing_user =
585 if let Some(existing) = self.users.lock().unwrap().get(&request.user_id) {
586 existing.clone()
587 } else {
588 User::new_user(
589 "testuser".to_string(),
590 paladin_core::platform::container::user::Email::new(
591 "test@example.com".to_string(),
592 )
593 .unwrap(),
594 "mock_hash".to_string(),
595 None,
596 )
597 };
598
599 let updated_profile = request
601 .profile
602 .unwrap_or_else(|| existing_user.profile().clone());
603 let updated_username = request
604 .username
605 .unwrap_or_else(|| existing_user.username().to_string());
606 let updated_email = request
607 .email
608 .unwrap_or_else(|| existing_user.email().value().to_string());
609
610 let updated_user = User::new_user(
611 updated_username,
612 paladin_core::platform::container::user::Email::new(updated_email).unwrap(),
613 "mock_hash".to_string(),
614 Some(updated_profile),
615 );
616
617 self.add_user(updated_user.clone());
618 Ok(updated_user)
619 }
620
621 async fn activate_user(&self, _user_id: Uuid) -> Result<(), UserError> {
622 if *self.should_fail.lock().unwrap() {
623 return Err(UserError::RepositoryError("Mock error".to_string()));
624 }
625 Ok(())
626 }
627
628 async fn deactivate_user(&self, _user_id: Uuid) -> Result<(), UserError> {
629 if *self.should_fail.lock().unwrap() {
630 return Err(UserError::RepositoryError("Mock error".to_string()));
631 }
632 Ok(())
633 }
634
635 async fn verify_user(&self, _user_id: Uuid) -> Result<(), UserError> {
636 if *self.should_fail.lock().unwrap() {
637 return Err(UserError::RepositoryError("Mock error".to_string()));
638 }
639 Ok(())
640 }
641
642 async fn get_user_by_email(&self, email: &str) -> Result<Option<User>, UserError> {
643 if *self.should_fail.lock().unwrap() {
644 return Err(UserError::RepositoryError("Mock error".to_string()));
645 }
646
647 Ok(self
648 .users
649 .lock()
650 .unwrap()
651 .values()
652 .find(|u| u.email().value() == email)
653 .cloned())
654 }
655
656 async fn delete_user(&self, user_id: Uuid) -> Result<(), UserError> {
657 if *self.should_fail.lock().unwrap() {
658 return Err(UserError::RepositoryError("Mock error".to_string()));
659 }
660 self.users.lock().unwrap().remove(&user_id);
661 Ok(())
662 }
663
664 async fn list_users(&self) -> Result<Vec<User>, UserError> {
665 if *self.should_fail.lock().unwrap() {
666 return Err(UserError::RepositoryError("Mock error".to_string()));
667 }
668 Ok(self.users.lock().unwrap().values().cloned().collect())
669 }
670
671 async fn find_by_active_status(&self, is_active: bool) -> Result<Vec<User>, UserError> {
672 if *self.should_fail.lock().unwrap() {
673 return Err(UserError::RepositoryError("Mock error".to_string()));
674 }
675
676 Ok(self
677 .users
678 .lock()
679 .unwrap()
680 .values()
681 .filter(|u| u.is_active() == is_active)
682 .cloned()
683 .collect())
684 }
685
686 async fn find_by_verification_status(
687 &self,
688 is_verified: bool,
689 ) -> Result<Vec<User>, UserError> {
690 if *self.should_fail.lock().unwrap() {
691 return Err(UserError::RepositoryError("Mock error".to_string()));
692 }
693
694 Ok(self
695 .users
696 .lock()
697 .unwrap()
698 .values()
699 .filter(|u| u.is_verified() == is_verified)
700 .cloned()
701 .collect())
702 }
703
704 async fn count_users(&self) -> Result<u64, UserError> {
705 if *self.should_fail.lock().unwrap() {
706 return Err(UserError::RepositoryError("Mock error".to_string()));
707 }
708
709 Ok(self.users.lock().unwrap().len() as u64)
710 }
711 }
712
713 #[test]
714 fn test_user_error_to_response_invalid_email() {
715 let error = UserError::InvalidEmail("Invalid email".to_string());
716 let (status, response) = user_error_to_response(error);
717
718 assert_eq!(status, StatusCode::BAD_REQUEST);
719 assert!(!response.0.success);
720 assert_eq!(response.0.error.as_ref().unwrap().code, "INVALID_EMAIL");
721 }
722
723 #[test]
724 fn test_user_error_to_response_user_not_found() {
725 let error = UserError::UserNotFound(Uuid::new_v4());
726 let (status, response) = user_error_to_response(error);
727
728 assert_eq!(status, StatusCode::NOT_FOUND);
729 assert!(!response.0.success);
730 assert_eq!(response.0.error.as_ref().unwrap().code, "USER_NOT_FOUND");
731 }
732
733 #[test]
734 fn test_user_error_to_response_authentication_failed() {
735 let error = UserError::AuthenticationFailed;
736 let (status, response) = user_error_to_response(error);
737
738 assert_eq!(status, StatusCode::UNAUTHORIZED);
739 assert!(!response.0.success);
740 assert_eq!(response.0.error.as_ref().unwrap().code, "AUTH_FAILED");
741 }
742
743 #[test]
744 fn test_user_to_response() {
745 let mock_user = MockUser::new();
746 let user = User::new_user(
747 mock_user.username.clone(),
748 paladin_core::platform::container::user::Email::new(mock_user.email.clone()).unwrap(),
749 "mock_hash".to_string(),
750 Some(mock_user.profile.clone()),
751 );
752
753 let response = user_to_response(&user);
754
755 assert_eq!(response.username, user.username());
756 assert_eq!(response.email, user.email().value());
757 assert_eq!(response.is_active, user.is_active());
758 assert_eq!(response.is_verified, user.is_verified());
759 assert_eq!(response.profile.first_name, user.profile().first_name);
760 assert_eq!(response.profile.last_name, user.profile().last_name);
761 }
762
763 #[tokio::test]
764 async fn test_register_user_success() {
765 let user_service = Arc::new(MockUserService::new());
766 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
767 let request = RegisterUserRequest {
768 username: "testuser".to_string(),
769 email: "test@example.com".to_string(),
770 password: "password123".to_string(),
771 first_name: Some("Test".to_string()),
772 last_name: Some("User".to_string()),
773 bio: Some("Test bio".to_string()),
774 timezone: Some("UTC".to_string()),
775 locale: Some("en-US".to_string()),
776 };
777
778 let result = register_user(State(service_ref), Json(request)).await;
779
780 assert!(result.is_ok());
781 let response = result.unwrap();
782 assert!(response.0.success);
783 assert!(response.0.data.is_some());
784 }
785
786 #[tokio::test]
787 async fn test_register_user_service_error() {
788 let user_service = Arc::new(MockUserService::new());
789 user_service.set_should_fail(true);
790 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
791
792 let request = RegisterUserRequest {
793 username: "testuser".to_string(),
794 email: "test@example.com".to_string(),
795 password: "password123".to_string(),
796 first_name: None,
797 last_name: None,
798 bio: None,
799 timezone: None,
800 locale: None,
801 };
802
803 let result = register_user(State(service_ref), Json(request)).await;
804
805 assert!(result.is_err());
806 let (status, response) = result.err().unwrap();
807 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
808 assert!(!response.0.success);
809 }
810
811 #[tokio::test]
812 async fn test_login_user_success() {
813 let user_service = Arc::new(MockUserService::new());
814 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
815 let request = LoginUserRequest {
816 email: "test@example.com".to_string(),
817 password: "password123".to_string(),
818 };
819
820 let result = login_user(State(service_ref), Json(request)).await;
821
822 assert!(result.is_ok());
823 let response = result.unwrap();
824 assert!(response.0.success);
825 assert!(response.0.data.is_some());
826 assert_eq!(response.0.data.as_ref().unwrap().email, "test@example.com");
827 assert!(response.0.data.as_ref().unwrap().success);
828 }
829
830 #[tokio::test]
831 async fn test_login_user_auth_failed() {
832 let user_service = Arc::new(MockUserService::new());
833 user_service.set_should_fail(true);
834 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
835
836 let request = LoginUserRequest {
837 email: "test@example.com".to_string(),
838 password: "wrongpassword".to_string(),
839 };
840
841 let result = login_user(State(service_ref), Json(request)).await;
842
843 assert!(result.is_err());
844 let (status, response) = result.err().unwrap();
845 assert_eq!(status, StatusCode::UNAUTHORIZED);
846 assert!(!response.0.success);
847 }
848
849 #[tokio::test]
850 async fn test_get_user_success() {
851 let user_service = Arc::new(MockUserService::new());
852 let mock_user = User::new_user(
853 "testuser".to_string(),
854 paladin_core::platform::container::user::Email::new("test@example.com".to_string())
855 .unwrap(),
856 "mock_hash".to_string(),
857 None,
858 );
859 user_service.add_user(mock_user.clone());
860 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
861
862 let result = get_user(State(service_ref), None, Path(mock_user.uuid)).await;
863
864 assert!(result.is_ok());
865 let response = result.unwrap();
866 assert!(response.0.success);
867 assert!(response.0.data.is_some());
868 assert_eq!(response.0.data.as_ref().unwrap().id, mock_user.uuid);
869 }
870
871 #[tokio::test]
872 async fn test_get_user_not_found() {
873 let user_service = Arc::new(MockUserService::new());
874 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
875 let user_id = Uuid::new_v4();
876
877 let result = get_user(State(service_ref), None, Path(user_id)).await;
878
879 assert!(result.is_err());
880 let (status, response) = result.err().unwrap();
881 assert_eq!(status, StatusCode::NOT_FOUND);
882 assert!(!response.0.success);
883 }
884
885 #[tokio::test]
886 async fn test_activate_user_success() {
887 let user_service = Arc::new(MockUserService::new());
888 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
889 let user_id = Uuid::new_v4();
890
891 let result = activate_user(State(service_ref), Path(user_id)).await;
892
893 assert!(result.is_ok());
894 let response = result.unwrap();
895 assert!(response.0.success);
896 }
897
898 #[tokio::test]
899 async fn test_deactivate_user_success() {
900 let user_service = Arc::new(MockUserService::new());
901 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
902 let user_id = Uuid::new_v4();
903
904 let result = deactivate_user(State(service_ref), Path(user_id)).await;
905
906 assert!(result.is_ok());
907 let response = result.unwrap();
908 assert!(response.0.success);
909 }
910
911 #[tokio::test]
912 async fn test_verify_user_success() {
913 let user_service = Arc::new(MockUserService::new());
914 let service_ref: Arc<dyn UserServiceTrait> = user_service.clone();
915 let user_id = Uuid::new_v4();
916
917 let result = verify_user(State(service_ref), Path(user_id)).await;
918
919 assert!(result.is_ok());
920 let response = result.unwrap();
921 assert!(response.0.success);
922 }
923
924 #[test]
925 fn test_api_response_success() {
926 let data = "test data".to_string();
927 let response: ApiResponse<String> = ApiResponse::success(data.clone());
928
929 assert!(response.success);
930 assert_eq!(response.data, Some(data));
931 assert!(response.error.is_none());
932 }
933
934 #[test]
935 fn test_api_response_error() {
936 let error_msg = "Test error".to_string();
937 let error_code = "TEST_ERROR".to_string();
938 let response: ApiResponse<()> = ApiResponse::error(error_msg.clone(), error_code.clone());
939
940 assert!(!response.success);
941 assert!(response.data.is_none());
942 assert_eq!(response.error.as_ref().unwrap().error, error_msg);
943 assert_eq!(response.error.as_ref().unwrap().code, error_code);
944 }
945}