Skip to main content

auth_framework/testing/
utilities.rs

1/// Mock authentication method for testing.
2///
3/// # Example
4/// ```rust,ignore
5/// let mock = MockAuthMethod::new_success();
6/// assert!(mock.should_succeed);
7/// ```
8#[derive(Debug, Clone)]
9pub struct MockAuthMethod {
10    /// Whether authentication should succeed
11    pub should_succeed: bool,
12    /// Simulated user profiles to return
13    pub user_profiles: HashMap<String, ProviderProfile>,
14    /// Simulated delay for authentication
15    pub delay: Option<Duration>,
16}
17
18impl MockAuthMethod {
19    /// Create a new mock authentication method that always succeeds.
20    ///
21    /// # Example
22    /// ```rust,ignore
23    /// let mock = MockAuthMethod::new_success();
24    /// assert!(mock.should_succeed);
25    /// ```
26    pub fn new_success() -> Self {
27        MockAuthMethod {
28            should_succeed: true,
29            user_profiles: HashMap::new(),
30            delay: None,
31        }
32    }
33
34    /// Add a user profile for a specific user ID.
35    ///
36    /// # Example
37    /// ```rust,ignore
38    /// let mock = MockAuthMethod::new_success()
39    ///     .with_user("user-1", ProviderProfile::new().with_id("user-1"));
40    /// ```
41    pub fn with_user(mut self, user_id: impl Into<String>, profile: ProviderProfile) -> Self {
42        self.user_profiles.insert(user_id.into(), profile);
43        self
44    }
45
46    /// Set a delay for authentication (useful for testing timeouts).
47    ///
48    /// # Example
49    /// ```rust,ignore
50    /// let mock = MockAuthMethod::new_success()
51    ///     .with_delay(Duration::from_millis(100));
52    /// ```
53    pub fn with_delay(mut self, delay: Duration) -> Self {
54        self.delay = Some(delay);
55        self
56    }
57}
58use crate::authentication::credentials::{Credential, CredentialMetadata};
59use crate::errors::{AuthError, Result};
60use crate::methods::{AuthMethod, MethodResult};
61use crate::providers::ProviderProfile;
62use crate::storage::AuthStorage;
63use crate::storage::core::SessionData;
64use crate::tokens::AuthToken;
65use dashmap::DashMap;
66use std::collections::HashMap;
67use std::sync::Arc;
68use std::time::Duration;
69use uuid::Uuid;
70// Ensure all top-level impls are closed before the test module
71#[cfg(test)]
72// use crate::security::SecurityConfig;
73#[tokio::test]
74async fn test_mock_storage() {
75    use crate::testing::test_infrastructure::TestEnvironmentGuard;
76    let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
77
78    let storage = MockStorage::new();
79    let token = helpers::create_test_token("testuser");
80    storage.store_token(&token).await.unwrap();
81    let retrieved = storage.get_token(&token.token_id).await.unwrap();
82    assert!(retrieved.is_some());
83    assert_eq!(retrieved.unwrap().token_id, token.token_id);
84}
85
86#[tokio::test]
87async fn test_failing_mock_storage() {
88    use crate::testing::test_infrastructure::TestEnvironmentGuard;
89    let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
90
91    let storage = MockStorage::new_failing();
92    let token = helpers::create_test_token("testuser");
93    let result = storage.store_token(&token).await;
94    assert!(result.is_err());
95}
96
97#[test]
98fn test_secret_loading_from_env() {
99    use crate::auth::AuthFramework;
100    use crate::config::AuthConfig;
101    use crate::testing::test_infrastructure::TestEnvironmentGuard;
102
103    let _env = TestEnvironmentGuard::new().with_jwt_secret("env_secret_value");
104
105    let config = AuthConfig::default().secret("config_secret_value");
106    let framework = AuthFramework::new(config.clone());
107    let token = framework
108        .token_manager()
109        .create_jwt_token("user", vec!["read".to_string()], None);
110    assert!(token.is_ok());
111}
112
113#[test]
114fn test_secret_loading_from_config() {
115    use crate::auth::AuthFramework;
116    use crate::config::AuthConfig;
117    use crate::testing::test_infrastructure::TestEnvironmentGuard;
118
119    // Ensure JWT_SECRET is not set in environment for this test
120    let _env = TestEnvironmentGuard::new();
121
122    let config = AuthConfig::default().secret("config_secret_value");
123    let framework = AuthFramework::new(config.clone());
124    let token = framework
125        .token_manager()
126        .create_jwt_token("user", vec!["read".to_string()], None);
127    assert!(token.is_ok());
128}
129
130#[test]
131fn test_secret_missing_returns_error() {
132    use crate::auth::AuthFramework;
133    use crate::config::AuthConfig;
134    use crate::testing::test_infrastructure::TestEnvironmentGuard;
135
136    // Guard clears JWT_SECRET and sets RUST_TEST=1 for proper isolation.
137    // force_production_mode() on the config drives production validation without
138    // touching the global ENVIRONMENT variable, eliminating the parallel-test race.
139    let _env = TestEnvironmentGuard::new();
140
141    let config = AuthConfig::default().force_production_mode();
142    match AuthFramework::new_validated(config) {
143        Err(e) => {
144            // Should fail with proper error message about JWT secret
145            assert!(
146                e.to_string().contains("JWT secret"),
147                "Expected JWT secret error, got: {e}"
148            );
149        }
150        Ok(_) => panic!("Expected error when JWT_SECRET is missing in production"),
151    }
152}
153
154impl AuthMethod for MockAuthMethod {
155    type MethodResult = MethodResult;
156    type AuthToken = AuthToken;
157
158    fn name(&self) -> &str {
159        "mock"
160    }
161
162    fn validate_config(&self) -> Result<()> {
163        Ok(())
164    }
165
166    async fn authenticate(
167        &self,
168        credential: Credential,
169        _metadata: CredentialMetadata,
170    ) -> Result<Self::MethodResult> {
171        // Simulate delay if configured
172        if let Some(delay) = self.delay {
173            tokio::time::sleep(delay).await;
174        }
175
176        if !self.should_succeed {
177            return Ok(MethodResult::Failure {
178                reason: "Mock authentication failed".to_string(),
179            });
180        }
181
182        // Extract user ID based on credential type
183        let user_id = match credential {
184            Credential::Password { username, .. } => username.clone(),
185            Credential::ApiKey { key } => format!("api_user_{}", &key[..8.min(key.len())]),
186            Credential::OAuth { .. } => "oauth_user".to_string(),
187            Credential::DeviceCode { .. } => "device_user".to_string(),
188            _ => "test_user".to_string(),
189        };
190
191        // Create a mock token
192        let token = AuthToken {
193            token_id: Uuid::new_v4().to_string(),
194            user_id: user_id.clone(),
195            access_token: format!("mock_token_{}", Uuid::new_v4()),
196            refresh_token: Some(format!("refresh_{}", Uuid::new_v4())),
197            token_type: Some("Bearer".to_string()),
198            expires_at: chrono::Utc::now() + chrono::Duration::seconds(3600),
199            scopes: vec!["read".to_string(), "write".to_string()].into(),
200            issued_at: chrono::Utc::now(),
201            auth_method: "mock".to_string(),
202            subject: Some(user_id.clone()),
203            issuer: Some("mock".to_string()),
204            user_profile: None,
205            client_id: Some("test_client".to_string()),
206            permissions: vec!["read:all".to_string(), "write:all".to_string()].into(),
207            roles: vec!["mock_user".to_string()].into(),
208            metadata: crate::tokens::TokenMetadata::default(),
209        };
210
211        Ok(MethodResult::Success(Box::new(token)))
212    }
213
214    async fn refresh_token(&self, _refresh_token: String) -> Result<Self::AuthToken> {
215        if !self.should_succeed {
216            return Err(AuthError::auth_method("mock", "Refresh failed"));
217        }
218
219        Ok(AuthToken {
220            token_id: Uuid::new_v4().to_string(),
221            user_id: "refreshed_user".to_string(),
222            access_token: "mock_refreshed_token".to_string(),
223            refresh_token: Some("mock_new_refresh_token".to_string()),
224            token_type: Some("Bearer".to_string()),
225            expires_at: chrono::Utc::now() + chrono::Duration::seconds(3600),
226            scopes: vec!["read".to_string(), "write".to_string()].into(),
227            issued_at: chrono::Utc::now(),
228            auth_method: "mock".to_string(),
229            client_id: Some("test_client".to_string()),
230            metadata: crate::tokens::TokenMetadata::default(),
231            subject: Some("refreshed_user".to_string()),
232            issuer: Some("mock".to_string()),
233            user_profile: None,
234            permissions: vec!["read:all".to_string(), "write:all".to_string()].into(),
235            roles: vec!["refreshed_user".to_string()].into(),
236        })
237    }
238}
239
240/// Mock storage implementation for testing with DashMap for deadlock-free operations.
241///
242/// # Example
243/// ```rust,ignore
244/// let storage = MockStorage::new();
245/// storage.store_token(&token).await.unwrap();
246/// ```
247#[derive(Debug, Clone)]
248pub struct MockStorage {
249    tokens: Arc<DashMap<String, AuthToken>>,
250    sessions: Arc<DashMap<String, SessionData>>,
251    kv_store: Arc<DashMap<String, Vec<u8>>>,
252    should_fail: bool,
253}
254
255impl MockStorage {
256    /// Create a new mock storage with DashMap for deadlock-free operations.
257    ///
258    /// # Example
259    /// ```rust,ignore
260    /// let storage = MockStorage::new();
261    /// ```
262    pub fn new() -> Self {
263        Self {
264            tokens: Arc::new(DashMap::new()),
265            sessions: Arc::new(DashMap::new()),
266            kv_store: Arc::new(DashMap::new()),
267            should_fail: false,
268        }
269    }
270
271    /// Create a mock storage that fails operations.
272    ///
273    /// # Example
274    /// ```rust,ignore
275    /// let storage = MockStorage::new_failing();
276    /// assert!(storage.store_token(&token).await.is_err());
277    /// ```
278    pub fn new_failing() -> Self {
279        let mut storage = Self::new();
280        storage.should_fail = true;
281        storage
282    }
283
284    /// Preset a token in storage.
285    ///
286    /// # Example
287    /// ```rust,ignore
288    /// let storage = MockStorage::new();
289    /// storage.with_token(token).unwrap();
290    /// ```
291    pub fn with_token(&self, token: AuthToken) -> Result<()> {
292        if self.should_fail {
293            return Err(AuthError::internal("Mock storage configured to fail"));
294        }
295
296        // Use DashMap deadlock-free insertion
297        self.tokens.insert(token.access_token.clone(), token);
298        Ok(())
299    }
300
301    /// Clear all storage using DashMap atomic operations.
302    ///
303    /// # Example
304    /// ```rust,ignore
305    /// let storage = MockStorage::new();
306    /// storage.clear();
307    /// ```
308    pub fn clear(&self) {
309        self.tokens.clear();
310        self.sessions.clear();
311        self.kv_store.clear();
312    }
313}
314
315impl Default for MockStorage {
316    fn default() -> Self {
317        Self::new()
318    }
319}
320
321#[async_trait::async_trait]
322impl AuthStorage for MockStorage {
323    async fn store_token(&self, token: &AuthToken) -> Result<()> {
324        if self.should_fail {
325            return Err(AuthError::internal("Mock storage configured to fail"));
326        }
327
328        // Use DashMap deadlock-free insertion
329        self.tokens
330            .insert(token.access_token.clone(), token.clone());
331        Ok(())
332    }
333
334    async fn get_token(&self, token_id: &str) -> Result<Option<AuthToken>> {
335        if self.should_fail {
336            return Err(AuthError::internal("Mock storage configured to fail"));
337        }
338
339        // Use DashMap deadlock-free iteration with immediate value extraction
340        for entry in self.tokens.iter() {
341            if entry.value().token_id == token_id {
342                return Ok(Some(entry.value().clone()));
343            }
344        }
345        Ok(None)
346    }
347
348    async fn get_token_by_access_token(&self, access_token: &str) -> Result<Option<AuthToken>> {
349        if self.should_fail {
350            return Err(AuthError::internal("Mock storage configured to fail"));
351        }
352
353        // Use DashMap deadlock-free get with immediate value extraction
354        Ok(self
355            .tokens
356            .get(access_token)
357            .map(|entry| entry.value().clone()))
358    }
359
360    async fn update_token(&self, token: &AuthToken) -> Result<()> {
361        if self.should_fail {
362            return Err(AuthError::internal("Mock storage configured to fail"));
363        }
364
365        // Use DashMap deadlock-free update
366        self.tokens
367            .insert(token.access_token.clone(), token.clone());
368        Ok(())
369    }
370
371    async fn delete_token(&self, token_id: &str) -> Result<()> {
372        if self.should_fail {
373            return Err(AuthError::internal("Mock storage configured to fail"));
374        }
375
376        // Use DashMap deadlock-free removal with retain-like operation
377        self.tokens.retain(|_, token| token.token_id != token_id);
378        Ok(())
379    }
380
381    async fn list_user_tokens(&self, user_id: &str) -> Result<Vec<AuthToken>> {
382        if self.should_fail {
383            return Err(AuthError::internal("Mock storage configured to fail"));
384        }
385
386        // Use DashMap with manual iteration to avoid API issues
387        let mut tokens = Vec::new();
388        for entry in self.tokens.iter() {
389            if entry.value().user_id == user_id {
390                tokens.push(entry.value().clone());
391            }
392        }
393        Ok(tokens)
394    }
395
396    async fn store_session(&self, session_id: &str, data: &SessionData) -> Result<()> {
397        if self.should_fail {
398            return Err(AuthError::internal("Mock storage configured to fail"));
399        }
400
401        // Use DashMap deadlock-free insertion
402        self.sessions.insert(session_id.to_string(), data.clone());
403        Ok(())
404    }
405
406    async fn get_session(&self, session_id: &str) -> Result<Option<SessionData>> {
407        if self.should_fail {
408            return Err(AuthError::internal("Mock storage configured to fail"));
409        }
410
411        // Use DashMap deadlock-free get with immediate value extraction
412        Ok(self
413            .sessions
414            .get(session_id)
415            .map(|entry| entry.value().clone()))
416    }
417
418    async fn delete_session(&self, session_id: &str) -> Result<()> {
419        if self.should_fail {
420            return Err(AuthError::internal("Mock storage configured to fail"));
421        }
422
423        // Use DashMap deadlock-free removal
424        self.sessions.remove(session_id);
425        Ok(())
426    }
427
428    async fn list_user_sessions(&self, user_id: &str) -> Result<Vec<SessionData>> {
429        if self.should_fail {
430            return Err(AuthError::internal("Mock storage configured to fail"));
431        }
432
433        // Use DashMap with manual iteration to avoid API issues
434        let mut sessions = Vec::new();
435        for entry in self.sessions.iter() {
436            if entry.value().user_id == user_id && !entry.value().is_expired() {
437                sessions.push(entry.value().clone());
438            }
439        }
440        Ok(sessions)
441    }
442
443    async fn store_kv(&self, key: &str, value: &[u8], _ttl: Option<Duration>) -> Result<()> {
444        if self.should_fail {
445            return Err(AuthError::internal("Mock storage configured to fail"));
446        }
447
448        // Use DashMap deadlock-free insertion
449        self.kv_store.insert(key.to_string(), value.to_vec());
450        Ok(())
451    }
452
453    async fn get_kv(&self, key: &str) -> Result<Option<Vec<u8>>> {
454        if self.should_fail {
455            return Err(AuthError::internal("Mock storage configured to fail"));
456        }
457
458        // Use DashMap deadlock-free get with immediate value extraction
459        Ok(self.kv_store.get(key).map(|entry| entry.value().clone()))
460    }
461
462    async fn delete_kv(&self, key: &str) -> Result<()> {
463        if self.should_fail {
464            return Err(AuthError::internal("Mock storage configured to fail"));
465        }
466
467        // Use DashMap deadlock-free removal
468        self.kv_store.remove(key);
469        Ok(())
470    }
471
472    async fn cleanup_expired(&self) -> Result<()> {
473        if self.should_fail {
474            return Err(AuthError::internal("Mock storage configured to fail"));
475        }
476
477        let now = chrono::Utc::now();
478
479        // Use DashMap deadlock-free retain operation
480        self.tokens.retain(|_, token| token.expires_at > now);
481
482        Ok(())
483    }
484
485    async fn count_active_sessions(&self) -> Result<u64> {
486        if self.should_fail {
487            return Err(AuthError::internal("Mock storage configured to fail"));
488        }
489
490        // Count non-expired sessions using DashMap with manual iteration
491        let mut count = 0u64;
492        for entry in self.sessions.iter() {
493            if !entry.value().is_expired() {
494                count += 1;
495            }
496        }
497        Ok(count)
498    }
499}
500
501/// Test helper functions
502pub mod helpers {
503    use super::*;
504    // use std::sync::Arc;  // Temporarily unused
505
506    /// Create a test user profile.
507    ///
508    /// # Example
509    /// ```rust,ignore
510    /// let profile = helpers::create_test_user_profile("user-1");
511    /// ```
512    pub fn create_test_user_profile(user_id: &str) -> ProviderProfile {
513        ProviderProfile::new()
514            .with_id(user_id)
515            .with_provider("test")
516            .with_name(Some(format!("Test User {}", user_id)))
517            .with_email(Some(format!("{}@test.com", user_id)))
518            .with_email_verified(true)
519    }
520
521    /// Create a test auth token.
522    ///
523    /// # Example
524    /// ```rust,ignore
525    /// let token = helpers::create_test_token("user-1");
526    /// assert_eq!(token.user_id, "user-1");
527    /// ```
528    pub fn create_test_token(user_id: &str) -> AuthToken {
529        let now = chrono::Utc::now();
530        AuthToken {
531            token_id: Uuid::new_v4().to_string(),
532            user_id: user_id.to_string(),
533            access_token: format!("test_token_{}", Uuid::new_v4()),
534            refresh_token: Some(format!("refresh_token_{}", Uuid::new_v4())),
535            token_type: Some("Bearer".to_string()),
536            expires_at: now + chrono::Duration::seconds(3600),
537            scopes: vec!["read".to_string(), "write".to_string()].into(),
538            issued_at: now,
539            auth_method: "test".to_string(),
540            client_id: Some("test_client".to_string()),
541            metadata: crate::tokens::TokenMetadata::default(),
542            subject: Some(user_id.to_string()),
543            issuer: Some("test".to_string()),
544            user_profile: None,
545            permissions: vec!["read:all".to_string(), "write:all".to_string()].into(),
546            roles: vec!["test_user".to_string()].into(),
547        }
548    }
549
550    /// Create test credentials.
551    ///
552    /// # Example
553    /// ```rust,ignore
554    /// let creds = helpers::create_test_credentials();
555    /// assert_eq!(creds.len(), 5);
556    /// ```
557    pub fn create_test_credentials() -> Vec<Credential> {
558        vec![
559            Credential::password("testuser", "testpass"),
560            Credential::api_key("test_api_key"),
561            Credential::oauth_code("test_auth_code"),
562            Credential::device_code("test_device_code", "test_client_id"),
563            Credential::jwt("test.jwt.token"),
564        ]
565    }
566}
567
568// ── Edge-case tests ──────────────────────────────────────────────────────
569
570#[cfg(test)]
571mod edge_tests {
572    use super::*;
573    use crate::testing::test_infrastructure::TestEnvironmentGuard;
574
575    // --- MockAuthMethod authenticate edge cases ---
576
577    #[tokio::test]
578    async fn test_mock_auth_failure_mode() {
579        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
580        let mock = MockAuthMethod {
581            should_succeed: false,
582            user_profiles: HashMap::new(),
583            delay: None,
584        };
585        let cred = Credential::password("user", "pass");
586        let meta = CredentialMetadata::default();
587        let result = mock.authenticate(cred, meta).await.unwrap();
588        match result {
589            MethodResult::Failure { reason } => {
590                assert!(reason.contains("failed"), "Expected failure: {reason}");
591            }
592            _ => panic!("Expected Failure variant"),
593        }
594    }
595
596    #[tokio::test]
597    async fn test_mock_auth_api_key_empty() {
598        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
599        let mock = MockAuthMethod::new_success();
600        let cred = Credential::api_key("");
601        let meta = CredentialMetadata::default();
602        // Should not panic with empty API key — &key[..0] is valid
603        let result = mock.authenticate(cred, meta).await.unwrap();
604        match result {
605            MethodResult::Success(token) => {
606                assert!(token.user_id.starts_with("api_user_"));
607            }
608            _ => panic!("Expected success"),
609        }
610    }
611
612    #[tokio::test]
613    async fn test_mock_auth_api_key_short() {
614        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
615        let mock = MockAuthMethod::new_success();
616        let cred = Credential::api_key("abc");
617        let meta = CredentialMetadata::default();
618        let result = mock.authenticate(cred, meta).await.unwrap();
619        match result {
620            MethodResult::Success(token) => {
621                assert_eq!(token.user_id, "api_user_abc");
622            }
623            _ => panic!("Expected success"),
624        }
625    }
626
627    #[tokio::test]
628    async fn test_mock_auth_catch_all_credential() {
629        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
630        let mock = MockAuthMethod::new_success();
631        let cred = Credential::jwt("some.jwt.token");
632        let meta = CredentialMetadata::default();
633        let result = mock.authenticate(cred, meta).await.unwrap();
634        match result {
635            MethodResult::Success(token) => {
636                assert_eq!(token.user_id, "test_user");
637            }
638            _ => panic!("Expected success"),
639        }
640    }
641
642    // --- MockAuthMethod refresh edge cases ---
643
644    #[tokio::test]
645    async fn test_mock_refresh_success() {
646        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
647        let mock = MockAuthMethod::new_success();
648        let token = mock
649            .refresh_token("old_refresh".to_string())
650            .await
651            .unwrap();
652        assert_eq!(token.user_id, "refreshed_user");
653        assert!(token.refresh_token.is_some());
654    }
655
656    #[tokio::test]
657    async fn test_mock_refresh_failure() {
658        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
659        let mock = MockAuthMethod {
660            should_succeed: false,
661            user_profiles: HashMap::new(),
662            delay: None,
663        };
664        let result = mock.refresh_token("old_refresh".to_string()).await;
665        assert!(result.is_err());
666    }
667
668    // --- MockStorage token operations ---
669
670    #[tokio::test]
671    async fn test_storage_get_token_by_access_token() {
672        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
673        let storage = MockStorage::new();
674        let token = helpers::create_test_token("user1");
675        let access = token.access_token.clone();
676        storage.store_token(&token).await.unwrap();
677        let found = storage
678            .get_token_by_access_token(&access)
679            .await
680            .unwrap();
681        assert!(found.is_some());
682        assert_eq!(found.unwrap().user_id, "user1");
683    }
684
685    #[tokio::test]
686    async fn test_storage_get_token_by_access_token_not_found() {
687        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
688        let storage = MockStorage::new();
689        let found = storage
690            .get_token_by_access_token("nonexistent")
691            .await
692            .unwrap();
693        assert!(found.is_none());
694    }
695
696    #[tokio::test]
697    async fn test_storage_update_token() {
698        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
699        let storage = MockStorage::new();
700        let mut token = helpers::create_test_token("user1");
701        storage.store_token(&token).await.unwrap();
702        token.user_id = "updated_user".to_string();
703        storage.update_token(&token).await.unwrap();
704        let found = storage
705            .get_token_by_access_token(&token.access_token)
706            .await
707            .unwrap()
708            .unwrap();
709        assert_eq!(found.user_id, "updated_user");
710    }
711
712    #[tokio::test]
713    async fn test_storage_delete_token() {
714        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
715        let storage = MockStorage::new();
716        let token = helpers::create_test_token("user1");
717        let tid = token.token_id.clone();
718        storage.store_token(&token).await.unwrap();
719        storage.delete_token(&tid).await.unwrap();
720        let found = storage.get_token(&tid).await.unwrap();
721        assert!(found.is_none());
722    }
723
724    #[tokio::test]
725    async fn test_storage_list_user_tokens_filters_by_user() {
726        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
727        let storage = MockStorage::new();
728        let t1 = helpers::create_test_token("alice");
729        let t2 = helpers::create_test_token("alice");
730        let t3 = helpers::create_test_token("bob");
731        storage.store_token(&t1).await.unwrap();
732        storage.store_token(&t2).await.unwrap();
733        storage.store_token(&t3).await.unwrap();
734        let alice_tokens = storage.list_user_tokens("alice").await.unwrap();
735        assert_eq!(alice_tokens.len(), 2);
736        let bob_tokens = storage.list_user_tokens("bob").await.unwrap();
737        assert_eq!(bob_tokens.len(), 1);
738    }
739
740    // --- MockStorage session operations ---
741
742    #[tokio::test]
743    async fn test_storage_session_crud() {
744        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
745        let storage = MockStorage::new();
746        let session = SessionData {
747            session_id: "sess1".to_string(),
748            user_id: "user1".to_string(),
749            created_at: chrono::Utc::now(),
750            expires_at: chrono::Utc::now() + chrono::Duration::seconds(3600),
751            last_activity: chrono::Utc::now(),
752            ip_address: Some("127.0.0.1".to_string()),
753            user_agent: None,
754            data: Default::default(),
755        };
756        storage.store_session("sess1", &session).await.unwrap();
757        let found = storage.get_session("sess1").await.unwrap();
758        assert!(found.is_some());
759        assert_eq!(found.unwrap().user_id, "user1");
760
761        storage.delete_session("sess1").await.unwrap();
762        let gone = storage.get_session("sess1").await.unwrap();
763        assert!(gone.is_none());
764    }
765
766    #[tokio::test]
767    async fn test_storage_list_user_sessions_filters_expired() {
768        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
769        let storage = MockStorage::new();
770        let active = SessionData {
771            session_id: "active".to_string(),
772            user_id: "user1".to_string(),
773            created_at: chrono::Utc::now(),
774            expires_at: chrono::Utc::now() + chrono::Duration::seconds(3600),
775            last_activity: chrono::Utc::now(),
776            ip_address: None,
777            user_agent: None,
778            data: Default::default(),
779        };
780        let expired = SessionData {
781            session_id: "expired".to_string(),
782            user_id: "user1".to_string(),
783            created_at: chrono::Utc::now() - chrono::Duration::seconds(7200),
784            expires_at: chrono::Utc::now() - chrono::Duration::seconds(3600),
785            last_activity: chrono::Utc::now() - chrono::Duration::seconds(7200),
786            ip_address: None,
787            user_agent: None,
788            data: Default::default(),
789        };
790        storage.store_session("active", &active).await.unwrap();
791        storage.store_session("expired", &expired).await.unwrap();
792        let sessions = storage.list_user_sessions("user1").await.unwrap();
793        assert_eq!(sessions.len(), 1);
794        assert_eq!(sessions[0].session_id, "active");
795    }
796
797    // --- MockStorage KV operations ---
798
799    #[tokio::test]
800    async fn test_storage_kv_crud() {
801        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
802        let storage = MockStorage::new();
803        storage
804            .store_kv("key1", b"value1", None)
805            .await
806            .unwrap();
807        let val = storage.get_kv("key1").await.unwrap();
808        assert_eq!(val.unwrap(), b"value1");
809
810        storage.delete_kv("key1").await.unwrap();
811        let gone = storage.get_kv("key1").await.unwrap();
812        assert!(gone.is_none());
813    }
814
815    #[tokio::test]
816    async fn test_storage_kv_not_found() {
817        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
818        let storage = MockStorage::new();
819        let val = storage.get_kv("nonexistent").await.unwrap();
820        assert!(val.is_none());
821    }
822
823    // --- MockStorage cleanup ---
824
825    #[tokio::test]
826    async fn test_storage_cleanup_expired_tokens() {
827        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
828        let storage = MockStorage::new();
829        let mut expired_token = helpers::create_test_token("user1");
830        expired_token.expires_at = chrono::Utc::now() - chrono::Duration::seconds(10);
831        let valid_token = helpers::create_test_token("user2");
832        storage.store_token(&expired_token).await.unwrap();
833        storage.store_token(&valid_token).await.unwrap();
834        storage.cleanup_expired().await.unwrap();
835        // Expired token should be gone
836        let found_expired = storage
837            .get_token_by_access_token(&expired_token.access_token)
838            .await
839            .unwrap();
840        assert!(found_expired.is_none());
841        // Valid token should remain
842        let found_valid = storage
843            .get_token_by_access_token(&valid_token.access_token)
844            .await
845            .unwrap();
846        assert!(found_valid.is_some());
847    }
848
849    // --- helpers::create_test_credentials ---
850
851    #[test]
852    fn test_create_test_credentials_all_variants() {
853        let creds = helpers::create_test_credentials();
854        assert_eq!(creds.len(), 5);
855        assert!(matches!(&creds[0], Credential::Password { .. }));
856        assert!(matches!(&creds[1], Credential::ApiKey { .. }));
857        assert!(matches!(&creds[2], Credential::OAuth { .. }));
858        assert!(matches!(&creds[3], Credential::DeviceCode { .. }));
859        assert!(matches!(&creds[4], Credential::Jwt { .. }));
860    }
861
862    // --- MockStorage count_active_sessions ---
863
864    #[tokio::test]
865    async fn test_storage_count_active_sessions() {
866        let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
867        let storage = MockStorage::new();
868        let active = SessionData {
869            session_id: "active1".to_string(),
870            user_id: "user1".to_string(),
871            created_at: chrono::Utc::now(),
872            expires_at: chrono::Utc::now() + chrono::Duration::seconds(3600),
873            last_activity: chrono::Utc::now(),
874            ip_address: None,
875            user_agent: None,
876            data: Default::default(),
877        };
878        let expired = SessionData {
879            session_id: "expired1".to_string(),
880            user_id: "user2".to_string(),
881            created_at: chrono::Utc::now() - chrono::Duration::seconds(7200),
882            expires_at: chrono::Utc::now() - chrono::Duration::seconds(3600),
883            last_activity: chrono::Utc::now() - chrono::Duration::seconds(7200),
884            ip_address: None,
885            user_agent: None,
886            data: Default::default(),
887        };
888        storage.store_session("active1", &active).await.unwrap();
889        storage.store_session("expired1", &expired).await.unwrap();
890        let count = storage.count_active_sessions().await.unwrap();
891        assert_eq!(count, 1);
892    }
893}