use chrono::Utc; use pinakes_core::storage::{SessionData, StorageBackend}; use tempfile::TempDir; async fn setup_sqlite_storage() -> pinakes_core::storage::sqlite::SqliteBackend { let temp_dir = TempDir::new().unwrap(); let db_path = temp_dir .path() .join(format!("test_{}.db", uuid::Uuid::now_v7())); let storage = pinakes_core::storage::sqlite::SqliteBackend::new(&db_path).unwrap(); storage.run_migrations().await.unwrap(); // Keep temp_dir alive by leaking it (tests are short-lived anyway) std::mem::forget(temp_dir); storage } #[tokio::test] async fn test_create_and_get_session() { let storage = setup_sqlite_storage().await; let now = Utc::now(); let session = SessionData { session_token: "test_token_123".to_string(), user_id: Some("user_1".to_string()), username: "testuser".to_string(), role: "admin".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; // Create session storage.create_session(&session).await.unwrap(); // Get session let retrieved = storage.get_session("test_token_123").await.unwrap(); assert!(retrieved.is_some()); let retrieved = retrieved.unwrap(); assert_eq!(retrieved.session_token, "test_token_123"); assert_eq!(retrieved.username, "testuser"); assert_eq!(retrieved.role, "admin"); } #[tokio::test] async fn test_get_nonexistent_session() { let storage = setup_sqlite_storage().await; let result = storage.get_session("nonexistent").await.unwrap(); assert!(result.is_none()); } #[tokio::test] async fn test_touch_session() { let storage = setup_sqlite_storage().await; let now = Utc::now(); let session = SessionData { session_token: "test_token_456".to_string(), user_id: None, username: "testuser".to_string(), role: "viewer".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&session).await.unwrap(); // Wait a bit tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; // Touch session storage.touch_session("test_token_456").await.unwrap(); // Verify last_accessed was updated let updated = storage .get_session("test_token_456") .await .unwrap() .unwrap(); assert!(updated.last_accessed > now); } #[tokio::test] async fn test_delete_session() { let storage = setup_sqlite_storage().await; let now = Utc::now(); let session = SessionData { session_token: "delete_me".to_string(), user_id: None, username: "testuser".to_string(), role: "editor".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&session).await.unwrap(); assert!(storage.get_session("delete_me").await.unwrap().is_some()); // Delete session storage.delete_session("delete_me").await.unwrap(); // Verify it's gone assert!(storage.get_session("delete_me").await.unwrap().is_none()); } #[tokio::test] async fn test_delete_user_sessions() { let storage = setup_sqlite_storage().await; let now = Utc::now(); // Create multiple sessions for the same user for i in 0..3 { let session = SessionData { session_token: format!("token_{i}"), user_id: None, username: "testuser".to_string(), role: "viewer".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&session).await.unwrap(); } // Create session for different user let other_session = SessionData { session_token: "other_token".to_string(), user_id: None, username: "otheruser".to_string(), role: "viewer".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&other_session).await.unwrap(); // Delete all sessions for testuser let deleted = storage.delete_user_sessions("testuser").await.unwrap(); assert_eq!(deleted, 3); // Verify testuser sessions are gone for i in 0..3 { assert!( storage .get_session(&format!("token_{i}")) .await .unwrap() .is_none() ); } // Verify otheruser session still exists assert!(storage.get_session("other_token").await.unwrap().is_some()); } #[tokio::test] async fn test_delete_expired_sessions() { let storage = setup_sqlite_storage().await; let now = Utc::now(); // Create expired session let expired = SessionData { session_token: "expired_token".to_string(), user_id: None, username: "testuser".to_string(), role: "viewer".to_string(), created_at: now - chrono::Duration::hours(25), expires_at: now - chrono::Duration::hours(1), // Expired 1 hour ago last_accessed: now - chrono::Duration::hours(2), }; storage.create_session(&expired).await.unwrap(); // Create valid session let valid = SessionData { session_token: "valid_token".to_string(), user_id: None, username: "testuser".to_string(), role: "viewer".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&valid).await.unwrap(); // Delete expired sessions let deleted = storage.delete_expired_sessions().await.unwrap(); assert_eq!(deleted, 1); // Verify expired is gone, valid remains assert!( storage .get_session("expired_token") .await .unwrap() .is_none() ); assert!(storage.get_session("valid_token").await.unwrap().is_some()); } #[tokio::test] async fn test_list_active_sessions() { let storage = setup_sqlite_storage().await; let now = Utc::now(); // Create active sessions for different users for i in 0..3 { let session = SessionData { session_token: format!("user1_token_{i}"), user_id: None, username: "user1".to_string(), role: "viewer".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&session).await.unwrap(); } for i in 0..2 { let session = SessionData { session_token: format!("user2_token_{i}"), user_id: None, username: "user2".to_string(), role: "admin".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&session).await.unwrap(); } // Create expired session let expired = SessionData { session_token: "expired".to_string(), user_id: None, username: "user1".to_string(), role: "viewer".to_string(), created_at: now - chrono::Duration::hours(25), expires_at: now - chrono::Duration::hours(1), last_accessed: now - chrono::Duration::hours(2), }; storage.create_session(&expired).await.unwrap(); // List all active sessions let all_active = storage.list_active_sessions(None).await.unwrap(); assert_eq!(all_active.len(), 5); // 3 + 2, expired not included // List active sessions for user1 let user1_active = storage.list_active_sessions(Some("user1")).await.unwrap(); assert_eq!(user1_active.len(), 3); // List active sessions for user2 let user2_active = storage.list_active_sessions(Some("user2")).await.unwrap(); assert_eq!(user2_active.len(), 2); } #[tokio::test] async fn test_concurrent_session_operations() { let storage = setup_sqlite_storage().await; let now = Utc::now(); let storage = std::sync::Arc::new(storage); // Create sessions concurrently let mut handles = vec![]; for i in 0..10 { let storage = storage.clone(); let handle = tokio::spawn(async move { let session = SessionData { session_token: format!("concurrent_{i}"), user_id: None, username: format!("user{i}"), role: "viewer".to_string(), created_at: now, expires_at: now + chrono::Duration::hours(24), last_accessed: now, }; storage.create_session(&session).await.unwrap(); }); handles.push(handle); } // Wait for all to complete for handle in handles { handle.await.unwrap(); } // Verify all sessions were created for i in 0..10 { assert!( storage .get_session(&format!("concurrent_{i}")) .await .unwrap() .is_some() ); } }