Skip to main content

paladin_web/
user_controller.rs

1/*
2User Controller
3
4REST API endpoints for user operations. This handles HTTP requests and responses,
5delegating business logic to the UserService.
6*/
7
8use 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/// User registration request DTO
26#[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/// User login request DTO
39#[derive(Debug, Deserialize)]
40pub struct LoginUserRequest {
41    pub email: String,
42    pub password: String,
43}
44
45/// User profile update request DTO
46#[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/// User response DTO
59#[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/// User profile response DTO
72#[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/// Login response DTO
83#[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    /// Opaque bearer token issued on successful login, when configured.
91    pub token: Option<String>,
92    /// RFC 3339 expiry of the issued token, paired with `token`.
93    pub token_expires_at: Option<String>,
94}
95
96/// Error response DTO
97#[derive(Debug, Serialize)]
98pub struct ErrorResponse {
99    pub error: String,
100    pub code: String,
101}
102
103/// API Response wrapper
104#[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
129/// Convert UserError to HTTP status code and error response
130fn 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
184/// Authorize access to a user-scoped resource: admins may access any user,
185/// non-admins may only access their own `user_id`.
186fn 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
203/// Convert User to UserResponse
204fn 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
224/// Register a new user
225pub(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
260/// User login
261pub(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
284/// Get user by ID
285pub(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
306/// Update user profile
307pub(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        // First, get current user to preserve existing profile values
324        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
369/// Activate user account
370async 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
380/// Deactivate user account
381async 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
391/// Verify user email
392async 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
402/// Delete a user by ID (admin-only; enforced by route-level middleware)
403pub(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
413/// List all users (admin-only; enforced by route-level middleware)
414pub(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
425/// Create the user routes
426pub 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    // Mock types for testing
449    #[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    // Mock UserService for testing
485    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                // Default successful login
551                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            // Get existing user or create mock
584            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            // Apply updates
600            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}