auth_framework/testing/
utilities.rs

1#[derive(Debug, Clone)]
2pub struct MockAuthMethod {
3    /// Whether authentication should succeed
4    pub should_succeed: bool,
5    /// Simulated user profiles to return
6    pub user_profiles: HashMap<String, UserProfile>,
7    /// Simulated delay for authentication
8    pub delay: Option<Duration>,
9}
10
11impl MockAuthMethod {
12    /// Create a new mock authentication method that always succeeds
13    pub fn new_success() -> Self {
14        MockAuthMethod {
15            should_succeed: true,
16            user_profiles: HashMap::new(),
17            delay: None,
18        }
19    }
20
21    /// Add a user profile for a specific user ID
22    pub fn with_user(mut self, user_id: impl Into<String>, profile: UserProfile) -> Self {
23        self.user_profiles.insert(user_id.into(), profile);
24        self
25    }
26
27    /// Set a delay for authentication (useful for testing timeouts)
28    pub fn with_delay(mut self, delay: Duration) -> Self {
29        self.delay = Some(delay);
30        self
31    }
32}
33use crate::authentication::credentials::{Credential, CredentialMetadata};
34use crate::errors::{AuthError, Result};
35use crate::methods::{AuthMethod, MethodResult};
36use crate::providers::UserProfile;
37use crate::storage::AuthStorage;
38use crate::storage::core::SessionData;
39use crate::tokens::AuthToken;
40use dashmap::DashMap;
41use std::collections::HashMap;
42use std::sync::Arc;
43use std::time::Duration;
44use uuid::Uuid;
45// Ensure all top-level impls are closed before the test module
46#[cfg(test)]
47// use crate::security::SecurityConfig;
48#[tokio::test]
49async fn test_mock_storage() {
50    use crate::testing::test_infrastructure::TestEnvironmentGuard;
51    let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
52
53    let storage = MockStorage::new();
54    let token = helpers::create_test_token("testuser");
55    storage.store_token(&token).await.unwrap();
56    let retrieved = storage.get_token(&token.token_id).await.unwrap();
57    assert!(retrieved.is_some());
58    assert_eq!(retrieved.unwrap().token_id, token.token_id);
59}
60
61#[tokio::test]
62async fn test_failing_mock_storage() {
63    use crate::testing::test_infrastructure::TestEnvironmentGuard;
64    let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
65
66    let storage = MockStorage::new_failing();
67    let token = helpers::create_test_token("testuser");
68    let result = storage.store_token(&token).await;
69    assert!(result.is_err());
70}
71
72#[test]
73fn test_secret_loading_from_env() {
74    use crate::auth::AuthFramework;
75    use crate::config::AuthConfig;
76    use crate::testing::test_infrastructure::TestEnvironmentGuard;
77
78    let _env = TestEnvironmentGuard::new().with_jwt_secret("env_secret_value");
79
80    let config = AuthConfig::default().secret("config_secret_value");
81    let framework = AuthFramework::new(config.clone());
82    let token = framework
83        .token_manager()
84        .create_jwt_token("user", vec!["read".to_string()], None);
85    assert!(token.is_ok());
86}
87
88#[test]
89fn test_secret_loading_from_config() {
90    use crate::auth::AuthFramework;
91    use crate::config::AuthConfig;
92    use crate::testing::test_infrastructure::TestEnvironmentGuard;
93
94    // Ensure JWT_SECRET is not set in environment for this test
95    let _env = TestEnvironmentGuard::new();
96
97    let config = AuthConfig::default().secret("config_secret_value");
98    let framework = AuthFramework::new(config.clone());
99    let token = framework
100        .token_manager()
101        .create_jwt_token("user", vec!["read".to_string()], None);
102    assert!(token.is_ok());
103}
104
105#[test]
106fn test_secret_missing_returns_error() {
107    use crate::auth::AuthFramework;
108    use crate::config::AuthConfig;
109
110    // Ensure JWT_SECRET is not set for this test
111    unsafe {
112        std::env::remove_var("JWT_SECRET");
113    }
114
115    // In production mode, should return error instead of panic
116    unsafe {
117        std::env::set_var("ENVIRONMENT", "production");
118
119        let config = AuthConfig::default();
120        match AuthFramework::new_validated(config) {
121            Err(e) => {
122                // Should fail with proper error message about JWT secret
123                assert!(e.to_string().contains("JWT secret"));
124            }
125            Ok(_) => panic!("Expected error when JWT_SECRET is missing in production"),
126        }
127
128        // Clean up
129        std::env::remove_var("ENVIRONMENT");
130    }
131}
132
133impl AuthMethod for MockAuthMethod {
134    type MethodResult = MethodResult;
135    type AuthToken = AuthToken;
136
137    fn name(&self) -> &str {
138        "mock"
139    }
140
141    fn validate_config(&self) -> Result<()> {
142        Ok(())
143    }
144
145    async fn authenticate(
146        &self,
147        credential: Credential,
148        _metadata: CredentialMetadata,
149    ) -> Result<Self::MethodResult> {
150        // Simulate delay if configured
151        if let Some(delay) = self.delay {
152            tokio::time::sleep(delay).await;
153        }
154
155        if !self.should_succeed {
156            return Ok(MethodResult::Failure {
157                reason: "Mock authentication failed".to_string(),
158            });
159        }
160
161        // Extract user ID based on credential type
162        let user_id = match credential {
163            Credential::Password { username, .. } => username.clone(),
164            Credential::ApiKey { key } => format!("api_user_{}", &key[..8.min(key.len())]),
165            Credential::OAuth { .. } => "oauth_user".to_string(),
166            Credential::DeviceCode { .. } => "device_user".to_string(),
167            _ => "test_user".to_string(),
168        };
169
170        // Create a mock token
171        let token = AuthToken {
172            token_id: Uuid::new_v4().to_string(),
173            user_id: user_id.clone(),
174            access_token: format!("mock_token_{}", Uuid::new_v4()),
175            refresh_token: Some(format!("refresh_{}", Uuid::new_v4())),
176            token_type: Some("Bearer".to_string()),
177            expires_at: chrono::Utc::now() + chrono::Duration::seconds(3600),
178            scopes: vec!["read".to_string(), "write".to_string()],
179            issued_at: chrono::Utc::now(),
180            auth_method: "mock".to_string(),
181            subject: Some(user_id.clone()),
182            issuer: Some("mock".to_string()),
183            user_profile: None,
184            client_id: Some("test_client".to_string()),
185            permissions: vec!["read:all".to_string(), "write:all".to_string()],
186            roles: vec!["mock_user".to_string()],
187            metadata: crate::tokens::TokenMetadata::default(),
188        };
189
190        Ok(MethodResult::Success(Box::new(token)))
191    }
192
193    async fn refresh_token(&self, _refresh_token: String) -> Result<Self::AuthToken> {
194        if !self.should_succeed {
195            return Err(AuthError::auth_method("mock", "Refresh failed"));
196        }
197
198        Ok(AuthToken {
199            token_id: Uuid::new_v4().to_string(),
200            user_id: "refreshed_user".to_string(),
201            access_token: "mock_refreshed_token".to_string(),
202            refresh_token: Some("mock_new_refresh_token".to_string()),
203            token_type: Some("Bearer".to_string()),
204            expires_at: chrono::Utc::now() + chrono::Duration::seconds(3600),
205            scopes: vec!["read".to_string(), "write".to_string()],
206            issued_at: chrono::Utc::now(),
207            auth_method: "mock".to_string(),
208            client_id: Some("test_client".to_string()),
209            metadata: crate::tokens::TokenMetadata::default(),
210            subject: Some("refreshed_user".to_string()),
211            issuer: Some("mock".to_string()),
212            user_profile: None,
213            permissions: vec!["read:all".to_string(), "write:all".to_string()],
214            roles: vec!["refreshed_user".to_string()],
215        })
216    }
217}
218
219/// Mock storage implementation for testing with DashMap for deadlock-free operations.
220#[derive(Debug, Clone)]
221pub struct MockStorage {
222    tokens: Arc<DashMap<String, AuthToken>>,
223    sessions: Arc<DashMap<String, SessionData>>,
224    kv_store: Arc<DashMap<String, Vec<u8>>>,
225    should_fail: bool,
226}
227
228impl MockStorage {
229    /// Create a new mock storage with DashMap for deadlock-free operations
230    pub fn new() -> Self {
231        Self {
232            tokens: Arc::new(DashMap::new()),
233            sessions: Arc::new(DashMap::new()),
234            kv_store: Arc::new(DashMap::new()),
235            should_fail: false,
236        }
237    }
238
239    /// Create a mock storage that fails operations
240    pub fn new_failing() -> Self {
241        let mut storage = Self::new();
242        storage.should_fail = true;
243        storage
244    }
245
246    /// Preset a token in storage
247    pub fn with_token(&self, token: AuthToken) -> Result<()> {
248        if self.should_fail {
249            return Err(AuthError::internal("Mock storage configured to fail"));
250        }
251
252        // Use DashMap deadlock-free insertion
253        self.tokens.insert(token.access_token.clone(), token);
254        Ok(())
255    }
256
257    /// Clear all storage using DashMap atomic operations
258    pub fn clear(&self) {
259        self.tokens.clear();
260        self.sessions.clear();
261        self.kv_store.clear();
262    }
263}
264
265impl Default for MockStorage {
266    fn default() -> Self {
267        Self::new()
268    }
269}
270
271#[async_trait::async_trait]
272impl AuthStorage for MockStorage {
273    async fn store_token(&self, token: &AuthToken) -> Result<()> {
274        if self.should_fail {
275            return Err(AuthError::internal("Mock storage configured to fail"));
276        }
277
278        // Use DashMap deadlock-free insertion
279        self.tokens
280            .insert(token.access_token.clone(), token.clone());
281        Ok(())
282    }
283
284    async fn get_token(&self, token_id: &str) -> Result<Option<AuthToken>> {
285        if self.should_fail {
286            return Err(AuthError::internal("Mock storage configured to fail"));
287        }
288
289        // Use DashMap deadlock-free iteration with immediate value extraction
290        for entry in self.tokens.iter() {
291            if entry.value().token_id == token_id {
292                return Ok(Some(entry.value().clone()));
293            }
294        }
295        Ok(None)
296    }
297
298    async fn get_token_by_access_token(&self, access_token: &str) -> Result<Option<AuthToken>> {
299        if self.should_fail {
300            return Err(AuthError::internal("Mock storage configured to fail"));
301        }
302
303        // Use DashMap deadlock-free get with immediate value extraction
304        Ok(self
305            .tokens
306            .get(access_token)
307            .map(|entry| entry.value().clone()))
308    }
309
310    async fn update_token(&self, token: &AuthToken) -> Result<()> {
311        if self.should_fail {
312            return Err(AuthError::internal("Mock storage configured to fail"));
313        }
314
315        // Use DashMap deadlock-free update
316        self.tokens
317            .insert(token.access_token.clone(), token.clone());
318        Ok(())
319    }
320
321    async fn delete_token(&self, token_id: &str) -> Result<()> {
322        if self.should_fail {
323            return Err(AuthError::internal("Mock storage configured to fail"));
324        }
325
326        // Use DashMap deadlock-free removal with retain-like operation
327        self.tokens.retain(|_, token| token.token_id != token_id);
328        Ok(())
329    }
330
331    async fn list_user_tokens(&self, user_id: &str) -> Result<Vec<AuthToken>> {
332        if self.should_fail {
333            return Err(AuthError::internal("Mock storage configured to fail"));
334        }
335
336        // Use DashMap with manual iteration to avoid API issues
337        let mut tokens = Vec::new();
338        for entry in self.tokens.iter() {
339            if entry.value().user_id == user_id {
340                tokens.push(entry.value().clone());
341            }
342        }
343        Ok(tokens)
344    }
345
346    async fn store_session(&self, session_id: &str, data: &SessionData) -> Result<()> {
347        if self.should_fail {
348            return Err(AuthError::internal("Mock storage configured to fail"));
349        }
350
351        // Use DashMap deadlock-free insertion
352        self.sessions.insert(session_id.to_string(), data.clone());
353        Ok(())
354    }
355
356    async fn get_session(&self, session_id: &str) -> Result<Option<SessionData>> {
357        if self.should_fail {
358            return Err(AuthError::internal("Mock storage configured to fail"));
359        }
360
361        // Use DashMap deadlock-free get with immediate value extraction
362        Ok(self
363            .sessions
364            .get(session_id)
365            .map(|entry| entry.value().clone()))
366    }
367
368    async fn delete_session(&self, session_id: &str) -> Result<()> {
369        if self.should_fail {
370            return Err(AuthError::internal("Mock storage configured to fail"));
371        }
372
373        // Use DashMap deadlock-free removal
374        self.sessions.remove(session_id);
375        Ok(())
376    }
377
378    async fn list_user_sessions(&self, user_id: &str) -> Result<Vec<SessionData>> {
379        if self.should_fail {
380            return Err(AuthError::internal("Mock storage configured to fail"));
381        }
382
383        // Use DashMap with manual iteration to avoid API issues
384        let mut sessions = Vec::new();
385        for entry in self.sessions.iter() {
386            if entry.value().user_id == user_id && !entry.value().is_expired() {
387                sessions.push(entry.value().clone());
388            }
389        }
390        Ok(sessions)
391    }
392
393    async fn store_kv(&self, key: &str, value: &[u8], _ttl: Option<Duration>) -> Result<()> {
394        if self.should_fail {
395            return Err(AuthError::internal("Mock storage configured to fail"));
396        }
397
398        // Use DashMap deadlock-free insertion
399        self.kv_store.insert(key.to_string(), value.to_vec());
400        Ok(())
401    }
402
403    async fn get_kv(&self, key: &str) -> Result<Option<Vec<u8>>> {
404        if self.should_fail {
405            return Err(AuthError::internal("Mock storage configured to fail"));
406        }
407
408        // Use DashMap deadlock-free get with immediate value extraction
409        Ok(self.kv_store.get(key).map(|entry| entry.value().clone()))
410    }
411
412    async fn delete_kv(&self, key: &str) -> Result<()> {
413        if self.should_fail {
414            return Err(AuthError::internal("Mock storage configured to fail"));
415        }
416
417        // Use DashMap deadlock-free removal
418        self.kv_store.remove(key);
419        Ok(())
420    }
421
422    async fn cleanup_expired(&self) -> Result<()> {
423        if self.should_fail {
424            return Err(AuthError::internal("Mock storage configured to fail"));
425        }
426
427        let now = chrono::Utc::now();
428
429        // Use DashMap deadlock-free retain operation
430        self.tokens.retain(|_, token| token.expires_at > now);
431
432        Ok(())
433    }
434
435    async fn count_active_sessions(&self) -> Result<u64> {
436        if self.should_fail {
437            return Err(AuthError::internal("Mock storage configured to fail"));
438        }
439
440        // Count non-expired sessions using DashMap with manual iteration
441        let mut count = 0u64;
442        for entry in self.sessions.iter() {
443            if !entry.value().is_expired() {
444                count += 1;
445            }
446        }
447        Ok(count)
448    }
449}
450
451/// Test helper functions
452pub mod helpers {
453    use super::*;
454    // use std::sync::Arc;  // Temporarily unused
455
456    /// Create a test user profile
457    pub fn create_test_user_profile(user_id: &str) -> UserProfile {
458        UserProfile::new()
459            .with_id(user_id)
460            .with_provider("test")
461            .with_name(Some(format!("Test User {}", user_id)))
462            .with_email(Some(format!("{}@test.com", user_id)))
463            .with_email_verified(true)
464    }
465
466    /// Create a test auth token
467    pub fn create_test_token(user_id: &str) -> AuthToken {
468        let now = chrono::Utc::now();
469        AuthToken {
470            token_id: Uuid::new_v4().to_string(),
471            user_id: user_id.to_string(),
472            access_token: format!("test_token_{}", Uuid::new_v4()),
473            refresh_token: Some(format!("refresh_token_{}", Uuid::new_v4())),
474            token_type: Some("Bearer".to_string()),
475            expires_at: now + chrono::Duration::seconds(3600),
476            scopes: vec!["read".to_string(), "write".to_string()],
477            issued_at: now,
478            auth_method: "test".to_string(),
479            client_id: Some("test_client".to_string()),
480            metadata: crate::tokens::TokenMetadata::default(),
481            subject: Some(user_id.to_string()),
482            issuer: Some("test".to_string()),
483            user_profile: None,
484            permissions: vec!["read:all".to_string(), "write:all".to_string()],
485            roles: vec!["test_user".to_string()],
486        }
487    }
488
489    /// Create test credentials
490    pub fn create_test_credentials() -> Vec<Credential> {
491        vec![
492            Credential::password("testuser", "testpass"),
493            Credential::api_key("test_api_key"),
494            Credential::oauth_code("test_auth_code"),
495            Credential::device_code("test_device_code", "test_client_id"),
496            Credential::jwt("test.jwt.token"),
497        ]
498    }
499}
500
501