Skip to main content

mockforge_vbr/
session.rs

1//! Session integration
2//!
3//! This module integrates with existing SessionManager from mockforge-core/intelligent_behavior
4//! for session-scoped data and session expiration handling.
5
6use crate::config::StorageBackend;
7use crate::database::{create_database, VirtualDatabase};
8use crate::Result;
9use mockforge_core::intelligent_behavior::session::SessionManager;
10use std::collections::HashMap;
11use std::sync::Arc;
12use tokio::sync::RwLock;
13
14/// Session-scoped data manager
15///
16/// Manages per-session virtual databases when session-scoped data is enabled.
17/// Each session gets its own isolated database instance.
18pub struct SessionDataManager {
19    /// Session manager from mockforge-core
20    pub session_manager: Arc<SessionManager>,
21    /// Storage backend configuration for session databases
22    #[allow(dead_code)]
23    storage_backend: StorageBackend,
24    /// Per-session database instances (in-memory by default)
25    session_databases: Arc<RwLock<HashMap<String, Arc<dyn VirtualDatabase + Send + Sync>>>>,
26}
27
28impl SessionDataManager {
29    /// Create a new session data manager
30    pub fn new(session_manager: Arc<SessionManager>, storage_backend: StorageBackend) -> Self {
31        Self {
32            session_manager,
33            storage_backend,
34            session_databases: Arc::new(RwLock::new(HashMap::new())),
35        }
36    }
37
38    /// Get or create session-scoped database instance
39    ///
40    /// If session-scoped data is enabled, each session gets its own isolated
41    /// database. This allows different users/sessions to have separate data.
42    pub async fn get_session_database(
43        &self,
44        session_id: &str,
45    ) -> Result<Arc<dyn VirtualDatabase + Send + Sync>> {
46        // Check if session exists in SessionManager
47        let session_state = self.session_manager.get_session(session_id).await;
48        if session_state.is_none() {
49            return Err(crate::Error::generic(format!("Session '{}' not found", session_id)));
50        }
51
52        // Check if we already have a database for this session
53        let databases = self.session_databases.read().await;
54        if let Some(db) = databases.get(session_id) {
55            return Ok(Arc::clone(db));
56        }
57        drop(databases);
58
59        // Create a new in-memory database for this session
60        // Sessions always use in-memory storage for isolation
61        let db_arc = create_database(&StorageBackend::Memory).await?;
62        // Note: initialize() is called during create_database, so we don't need to call it again
63        let db_clone = Arc::clone(&db_arc);
64
65        // Store it for future use
66        let mut databases = self.session_databases.write().await;
67        databases.insert(session_id.to_string(), db_arc);
68
69        Ok(db_clone)
70    }
71
72    /// Clean up database for a session
73    pub async fn cleanup_session_database(&self, session_id: &str) -> Result<()> {
74        let mut databases = self.session_databases.write().await;
75        databases.remove(session_id);
76        // Database will be dropped when Arc is dropped
77        Ok(())
78    }
79
80    /// Clean up databases for expired sessions
81    pub async fn cleanup_expired_sessions(&self) -> Result<usize> {
82        // Get expired sessions from SessionManager
83        let _expired_count = self.session_manager.cleanup_expired_sessions().await;
84
85        // Get list of active sessions
86        let active_sessions = self.session_manager.list_sessions().await;
87        let active_set: std::collections::HashSet<String> = active_sessions.into_iter().collect();
88
89        // Remove databases for sessions that are no longer active
90        let mut databases = self.session_databases.write().await;
91        let mut removed = 0;
92        let expired_db_sessions: Vec<String> = databases
93            .keys()
94            .filter(|session_id| !active_set.contains(*session_id))
95            .cloned()
96            .collect();
97
98        for session_id in expired_db_sessions {
99            databases.remove(&session_id);
100            removed += 1;
101        }
102
103        Ok(removed)
104    }
105
106    /// Get the session manager
107    pub fn session_manager(&self) -> &Arc<SessionManager> {
108        &self.session_manager
109    }
110
111    /// Get session state (returns session if it exists)
112    pub async fn get_session_state(
113        &self,
114        session_id: &str,
115    ) -> Option<mockforge_core::intelligent_behavior::types::SessionState> {
116        self.session_manager.get_session(session_id).await
117    }
118
119    /// Update session state
120    pub async fn update_session_state(
121        &self,
122        session_id: &str,
123        state: mockforge_core::intelligent_behavior::types::SessionState,
124    ) -> Result<()> {
125        self.session_manager.update_session(session_id, state).await
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use mockforge_core::intelligent_behavior::session::SessionTracking;
133
134    fn create_test_session_manager() -> Arc<SessionManager> {
135        let config = SessionTracking::default();
136        Arc::new(SessionManager::new(config, 3600))
137    }
138
139    #[tokio::test]
140    async fn test_session_data_manager_new() {
141        let session_manager = create_test_session_manager();
142        let manager = SessionDataManager::new(session_manager.clone(), StorageBackend::Memory);
143
144        assert!(Arc::ptr_eq(&manager.session_manager, &session_manager));
145    }
146
147    #[tokio::test]
148    async fn test_get_session_database_no_session() {
149        let session_manager = create_test_session_manager();
150        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
151
152        let result = manager.get_session_database("nonexistent-session").await;
153        assert!(result.is_err());
154        match result {
155            Err(err) => {
156                assert!(err.to_string().contains("Session"));
157                assert!(err.to_string().contains("not found"));
158            }
159            Ok(_) => panic!("Expected an error"),
160        }
161    }
162
163    #[tokio::test]
164    async fn test_get_session_database_creates_new() {
165        let session_manager = create_test_session_manager();
166
167        // Create a session first
168        let _ = session_manager.get_or_create_session(Some("test-session".to_string())).await;
169
170        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
171        let result = manager.get_session_database("test-session").await;
172
173        assert!(result.is_ok());
174    }
175
176    #[tokio::test]
177    async fn test_get_session_database_returns_same() {
178        let session_manager = create_test_session_manager();
179
180        // Create a session first
181        let _ = session_manager.get_or_create_session(Some("test-session".to_string())).await;
182
183        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
184
185        // Get database twice
186        let db1 = manager.get_session_database("test-session").await.unwrap();
187        let db2 = manager.get_session_database("test-session").await.unwrap();
188
189        // Should return the same Arc
190        assert!(Arc::ptr_eq(&db1, &db2));
191    }
192
193    #[tokio::test]
194    async fn test_cleanup_session_database() {
195        let session_manager = create_test_session_manager();
196
197        // Create a session first
198        let _ = session_manager.get_or_create_session(Some("test-session".to_string())).await;
199
200        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
201
202        // Get a database to create the entry
203        let _db = manager.get_session_database("test-session").await.unwrap();
204
205        // Clean up
206        let result = manager.cleanup_session_database("test-session").await;
207        assert!(result.is_ok());
208    }
209
210    #[tokio::test]
211    async fn test_cleanup_nonexistent_session() {
212        let session_manager = create_test_session_manager();
213        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
214
215        // Should not error even if session doesn't exist
216        let result = manager.cleanup_session_database("nonexistent").await;
217        assert!(result.is_ok());
218    }
219
220    #[tokio::test]
221    async fn test_session_manager_getter() {
222        let session_manager = create_test_session_manager();
223        let manager = SessionDataManager::new(session_manager.clone(), StorageBackend::Memory);
224
225        assert!(Arc::ptr_eq(manager.session_manager(), &session_manager));
226    }
227
228    #[tokio::test]
229    async fn test_get_session_state_exists() {
230        let session_manager = create_test_session_manager();
231
232        // Create a session first
233        let _ = session_manager.get_or_create_session(Some("test-session".to_string())).await;
234
235        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
236        let state = manager.get_session_state("test-session").await;
237
238        assert!(state.is_some());
239    }
240
241    #[tokio::test]
242    async fn test_get_session_state_not_exists() {
243        let session_manager = create_test_session_manager();
244        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
245
246        let state = manager.get_session_state("nonexistent").await;
247        assert!(state.is_none());
248    }
249
250    #[tokio::test]
251    async fn test_multiple_sessions_isolated() {
252        let session_manager = create_test_session_manager();
253
254        // Create two sessions
255        let _ = session_manager.get_or_create_session(Some("session-1".to_string())).await;
256        let _ = session_manager.get_or_create_session(Some("session-2".to_string())).await;
257
258        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
259
260        let db1 = manager.get_session_database("session-1").await.unwrap();
261        let db2 = manager.get_session_database("session-2").await.unwrap();
262
263        // Should be different databases
264        assert!(!Arc::ptr_eq(&db1, &db2));
265    }
266
267    #[tokio::test]
268    async fn test_cleanup_expired_sessions() {
269        let session_manager = create_test_session_manager();
270        let manager = SessionDataManager::new(session_manager, StorageBackend::Memory);
271
272        let result = manager.cleanup_expired_sessions().await;
273        assert!(result.is_ok());
274    }
275}