Skip to main content

turbomcp_protocol/
session.rs

1//! Session management for `TurboMCP` applications
2//!
3//! Provides comprehensive session tracking, client management, and request analytics
4//! for MCP servers that need to manage multiple clients and track usage patterns.
5//!
6//! ## Features
7//!
8//! - **LRU Eviction**: Automatically evicts least-recently-used sessions when capacity is reached
9//! - **Request Analytics**: Track request patterns, success rates, and client behavior
10//! - **Sensitive Data Protection**: Automatic sanitization of passwords, tokens, and secrets
11//! - **Elicitation Management**: Track pending elicitations and their states
12//! - **Completion Tracking**: Monitor active completions across sessions
13//!
14//! ## Example
15//!
16//! ```rust
17//! use turbomcp_protocol::{SessionManager, SessionConfig};
18//! use chrono::Duration;
19//!
20//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
21//! // Configure session management with custom settings
22//! let config = SessionConfig {
23//!     max_sessions: 1000,                    // Maximum concurrent sessions
24//!     session_timeout: Duration::hours(24), // Session lifetime
25//!     max_request_history: 10000,           // Request analytics depth
26//!     max_requests_per_session: Some(10000), // Rate limiting per session
27//!     cleanup_interval: std::time::Duration::from_secs(300),
28//!     enable_analytics: true,
29//! };
30//!
31//! let manager = SessionManager::new(config);
32//! manager.start(); // Begin background cleanup task
33//!
34//! // Create and authenticate sessions
35//! let session = manager.get_or_create_session(
36//!     "client-123".to_string(),
37//!     "websocket".to_string()
38//! );
39//!
40//! manager.authenticate_client(
41//!     "client-123",
42//!     Some("MyApp/1.0".to_string()),
43//!     Some("auth-token".to_string())
44//! );
45//!
46//! // Track request analytics
47//! use turbomcp_protocol::RequestInfo;
48//! let request_info = RequestInfo::new(
49//!     "client-123".to_string(),
50//!     "tools/list".to_string(),
51//!     serde_json::json!({}),
52//! ).complete_success(150); // 150ms execution time
53//!
54//! manager.record_request(request_info);
55//!
56//! // Get analytics
57//! let analytics = manager.get_analytics();
58//! println!("Active sessions: {}", analytics.active_sessions);
59//! println!("Success rate: {:.1}%",
60//!     (analytics.successful_requests as f64 /
61//!      analytics.total_requests as f64) * 100.0
62//! );
63//! # Ok(())
64//! # }
65//! ```
66
67use std::collections::{HashMap, VecDeque};
68use std::sync::Arc;
69use std::time::Duration as StdDuration;
70
71use chrono::{DateTime, Duration, Utc};
72use dashmap::DashMap;
73use parking_lot::RwLock;
74use serde::{Deserialize, Serialize};
75use tokio::time::{Interval, interval};
76
77use crate::context::{
78    ClientIdExtractor, ClientSession, CompletionContext, ElicitationContext, RequestInfo,
79};
80
81/// Configuration for session management
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct SessionConfig {
84    /// Maximum number of sessions to track
85    pub max_sessions: usize,
86    /// Session timeout (inactive sessions will be removed)
87    pub session_timeout: Duration,
88    /// Maximum request history to keep per session
89    pub max_request_history: usize,
90    /// Optional hard cap on requests per individual session
91    pub max_requests_per_session: Option<usize>,
92    /// Cleanup interval for expired sessions
93    pub cleanup_interval: StdDuration,
94    /// Whether to track request analytics
95    pub enable_analytics: bool,
96}
97
98impl Default for SessionConfig {
99    fn default() -> Self {
100        Self {
101            max_sessions: 1000,
102            session_timeout: Duration::hours(24),
103            max_request_history: 1000,
104            max_requests_per_session: None,
105            cleanup_interval: StdDuration::from_secs(300), // 5 minutes
106            enable_analytics: true,
107        }
108    }
109}
110
111/// Session analytics and usage statistics
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct SessionAnalytics {
114    /// Total number of sessions created
115    pub total_sessions: usize,
116    /// Currently active sessions
117    pub active_sessions: usize,
118    /// Total requests processed
119    pub total_requests: usize,
120    /// Total successful requests
121    pub successful_requests: usize,
122    /// Total failed requests
123    pub failed_requests: usize,
124    /// Average session duration
125    pub avg_session_duration: Duration,
126    /// Most active clients (top 10)
127    pub top_clients: Vec<(String, usize)>,
128    /// Most used tools/methods (top 10)
129    pub top_methods: Vec<(String, usize)>,
130    /// Request rate (requests per minute)
131    pub requests_per_minute: f64,
132}
133
134/// Comprehensive session manager for MCP applications
135#[derive(Debug)]
136pub struct SessionManager {
137    /// Configuration
138    config: SessionConfig,
139    /// Active client sessions
140    sessions: Arc<DashMap<String, ClientSession>>,
141    /// Client ID extractor for authentication
142    client_extractor: Arc<ClientIdExtractor>,
143    /// Request history for analytics
144    request_history: Arc<RwLock<VecDeque<RequestInfo>>>,
145    /// Session creation history for analytics
146    session_history: Arc<RwLock<VecDeque<SessionEvent>>>,
147    /// Cleanup timer
148    cleanup_timer: Arc<RwLock<Option<Interval>>>,
149    /// Global statistics
150    stats: Arc<RwLock<SessionStats>>,
151    /// Pending elicitations by client ID
152    pending_elicitations: Arc<DashMap<String, Vec<ElicitationContext>>>,
153    /// Active completions by client ID
154    active_completions: Arc<DashMap<String, Vec<CompletionContext>>>,
155}
156
157/// Internal statistics tracking
158#[derive(Debug, Default)]
159struct SessionStats {
160    total_sessions: usize,
161    total_requests: usize,
162    successful_requests: usize,
163    failed_requests: usize,
164    total_session_duration: Duration,
165}
166
167/// Session lifecycle events
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct SessionEvent {
170    /// Event timestamp
171    pub timestamp: DateTime<Utc>,
172    /// Client ID
173    pub client_id: String,
174    /// Event type
175    pub event_type: SessionEventType,
176    /// Additional metadata
177    pub metadata: HashMap<String, serde_json::Value>,
178}
179
180/// Types of session events
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub enum SessionEventType {
183    /// Session created
184    Created,
185    /// Session authenticated
186    Authenticated,
187    /// Session updated (activity)
188    Updated,
189    /// Session expired
190    Expired,
191    /// Session terminated
192    Terminated,
193}
194
195impl SessionManager {
196    /// Create a new session manager
197    #[must_use]
198    pub fn new(config: SessionConfig) -> Self {
199        Self {
200            config,
201            sessions: Arc::new(DashMap::new()),
202            client_extractor: Arc::new(ClientIdExtractor::new()),
203            request_history: Arc::new(RwLock::new(VecDeque::new())),
204            session_history: Arc::new(RwLock::new(VecDeque::new())),
205            cleanup_timer: Arc::new(RwLock::new(None)),
206            stats: Arc::new(RwLock::new(SessionStats::default())),
207            pending_elicitations: Arc::new(DashMap::new()),
208            active_completions: Arc::new(DashMap::new()),
209        }
210    }
211
212    /// Start the session manager (begin cleanup task).
213    ///
214    /// Idempotent: subsequent calls after the first are no-ops. The
215    /// `cleanup_timer` write-guard guards both the field assignment and
216    /// the `tokio::spawn`, so a second `start()` cannot leak a parallel
217    /// cleanup loop on the same `DashMap`.
218    pub fn start(&self) {
219        let mut timer_guard = self.cleanup_timer.write();
220        if timer_guard.is_some() {
221            // Another caller already started the loop.
222            return;
223        }
224        *timer_guard = Some(interval(self.config.cleanup_interval));
225        drop(timer_guard);
226
227        // Start cleanup task
228        let sessions = self.sessions.clone();
229        let config = self.config.clone();
230        let session_history = self.session_history.clone();
231        let stats = self.stats.clone();
232        let pending_elicitations = self.pending_elicitations.clone();
233        let active_completions = self.active_completions.clone();
234
235        tokio::spawn(async move {
236            let mut timer = interval(config.cleanup_interval);
237            loop {
238                timer.tick().await;
239                Self::cleanup_expired_sessions(
240                    &sessions,
241                    &config,
242                    &session_history,
243                    &stats,
244                    &pending_elicitations,
245                    &active_completions,
246                );
247            }
248        });
249    }
250
251    /// Create or get existing session for a client
252    #[must_use]
253    pub fn get_or_create_session(
254        &self,
255        client_id: String,
256        transport_type: String,
257    ) -> ClientSession {
258        self.sessions.get(&client_id).map_or_else(
259            || {
260                // Enforce capacity before inserting a new session
261                self.enforce_capacity();
262
263                let session = ClientSession::new(client_id.clone(), transport_type);
264                self.sessions.insert(client_id.clone(), session.clone());
265
266                // Record session creation
267                let mut stats = self.stats.write();
268                stats.total_sessions += 1;
269                drop(stats);
270
271                self.record_session_event(client_id, SessionEventType::Created, HashMap::new());
272
273                session
274            },
275            |session| session.clone(),
276        )
277    }
278
279    /// Update client activity
280    pub fn update_client_activity(&self, client_id: &str) {
281        if let Some(mut session) = self.sessions.get_mut(client_id) {
282            session.update_activity();
283
284            // Optional: enforce per-session request cap by early termination
285            if let Some(cap) = self.config.max_requests_per_session
286                && session.request_count > cap
287            {
288                // Terminate the session when the cap is exceeded
289                // This is a conservative protection to prevent abusive sessions
290                drop(session);
291                let _ = self.terminate_session(client_id);
292            }
293        }
294    }
295
296    /// Authenticate a client session
297    #[must_use]
298    pub fn authenticate_client(
299        &self,
300        client_id: &str,
301        client_name: Option<String>,
302        token: Option<String>,
303    ) -> bool {
304        if let Some(mut session) = self.sessions.get_mut(client_id) {
305            session.authenticate(client_name.clone());
306
307            if let Some(token) = token {
308                self.client_extractor
309                    .register_token(token, client_id.to_string());
310            }
311
312            let mut metadata = HashMap::new();
313            if let Some(name) = client_name {
314                metadata.insert("client_name".to_string(), serde_json::json!(name));
315            }
316
317            self.record_session_event(
318                client_id.to_string(),
319                SessionEventType::Authenticated,
320                metadata,
321            );
322
323            return true;
324        }
325        false
326    }
327
328    /// Record a request for analytics
329    pub fn record_request(&self, mut request_info: RequestInfo) {
330        if !self.config.enable_analytics {
331            return;
332        }
333
334        // Update session activity
335        self.update_client_activity(&request_info.client_id);
336
337        // Update statistics
338        let mut stats = self.stats.write();
339        stats.total_requests += 1;
340        if request_info.success {
341            stats.successful_requests += 1;
342        } else {
343            stats.failed_requests += 1;
344        }
345        drop(stats);
346
347        // Add to request history
348        let mut history = self.request_history.write();
349        if history.len() >= self.config.max_request_history {
350            history.pop_front();
351        }
352
353        // Sanitize sensitive data before storing
354        request_info.parameters = self.sanitize_parameters(request_info.parameters);
355        history.push_back(request_info);
356    }
357
358    /// Get session analytics
359    #[must_use]
360    pub fn get_analytics(&self) -> SessionAnalytics {
361        let sessions = self.sessions.clone();
362
363        // Calculate active sessions
364        let active_sessions = sessions.len();
365
366        // Calculate average session duration
367        let total_duration = sessions
368            .iter()
369            .map(|entry| entry.session_duration())
370            .reduce(|acc, dur| acc + dur)
371            .unwrap_or_else(Duration::zero);
372
373        let avg_session_duration = if active_sessions > 0 {
374            total_duration / active_sessions as i32
375        } else {
376            Duration::zero()
377        };
378
379        // Calculate top clients by request count
380        let mut client_requests: HashMap<String, usize> = HashMap::new();
381        let mut method_requests: HashMap<String, usize> = HashMap::new();
382
383        let (recent_requests, top_clients, top_methods) = {
384            let history = self.request_history.read();
385            for request in history.iter() {
386                *client_requests
387                    .entry(request.client_id.clone())
388                    .or_insert(0) += 1;
389                *method_requests
390                    .entry(request.method_name.clone())
391                    .or_insert(0) += 1;
392            }
393
394            let mut top_clients: Vec<(String, usize)> = client_requests.into_iter().collect();
395            top_clients.sort_by_key(|entry| std::cmp::Reverse(entry.1));
396            top_clients.truncate(10);
397
398            let mut top_methods: Vec<(String, usize)> = method_requests.into_iter().collect();
399            top_methods.sort_by_key(|entry| std::cmp::Reverse(entry.1));
400            top_methods.truncate(10);
401
402            // Calculate request rate (requests per minute over last hour)
403            let one_hour_ago = Utc::now() - Duration::hours(1);
404            let recent_requests = history
405                .iter()
406                .filter(|req| req.timestamp > one_hour_ago)
407                .count();
408            drop(history);
409
410            (recent_requests, top_clients, top_methods)
411        };
412        let requests_per_minute = recent_requests as f64 / 60.0;
413
414        let stats = self.stats.read();
415        SessionAnalytics {
416            total_sessions: stats.total_sessions,
417            active_sessions,
418            total_requests: stats.total_requests,
419            successful_requests: stats.successful_requests,
420            failed_requests: stats.failed_requests,
421            avg_session_duration,
422            top_clients,
423            top_methods,
424            requests_per_minute,
425        }
426    }
427
428    /// Get all active sessions
429    #[must_use]
430    pub fn get_active_sessions(&self) -> Vec<ClientSession> {
431        self.sessions
432            .iter()
433            .map(|entry| entry.value().clone())
434            .collect()
435    }
436
437    /// Get session by client ID
438    #[must_use]
439    pub fn get_session(&self, client_id: &str) -> Option<ClientSession> {
440        self.sessions.get(client_id).map(|session| session.clone())
441    }
442
443    /// Get client ID extractor
444    #[must_use]
445    pub fn client_extractor(&self) -> Arc<ClientIdExtractor> {
446        self.client_extractor.clone()
447    }
448
449    /// Terminate a session
450    #[must_use]
451    pub fn terminate_session(&self, client_id: &str) -> bool {
452        if let Some((_, session)) = self.sessions.remove(client_id) {
453            let mut stats = self.stats.write();
454            stats.total_session_duration += session.session_duration();
455            drop(stats);
456
457            // Clean up associated elicitations and completions
458            self.pending_elicitations.remove(client_id);
459            self.active_completions.remove(client_id);
460
461            self.record_session_event(
462                client_id.to_string(),
463                SessionEventType::Terminated,
464                HashMap::new(),
465            );
466
467            true
468        } else {
469            false
470        }
471    }
472
473    /// Get request history
474    #[must_use]
475    pub fn get_request_history(&self, limit: Option<usize>) -> Vec<RequestInfo> {
476        let history = self.request_history.read();
477        let limit = limit.unwrap_or(100);
478
479        history.iter().rev().take(limit).cloned().collect()
480    }
481
482    /// Get session events
483    #[must_use]
484    pub fn get_session_events(&self, limit: Option<usize>) -> Vec<SessionEvent> {
485        let events = self.session_history.read();
486        let limit = limit.unwrap_or(100);
487
488        events.iter().rev().take(limit).cloned().collect()
489    }
490
491    // Elicitation Management
492
493    /// Add a pending elicitation for a client
494    pub fn add_pending_elicitation(&self, client_id: String, elicitation: ElicitationContext) {
495        self.pending_elicitations
496            .entry(client_id)
497            .or_default()
498            .push(elicitation);
499    }
500
501    /// Get all pending elicitations for a client
502    #[must_use]
503    pub fn get_pending_elicitations(&self, client_id: &str) -> Vec<ElicitationContext> {
504        self.pending_elicitations
505            .get(client_id)
506            .map(|entry| entry.clone())
507            .unwrap_or_default()
508    }
509
510    /// Update an elicitation state
511    pub fn update_elicitation_state(
512        &self,
513        client_id: &str,
514        elicitation_id: &str,
515        state: crate::context::ElicitationState,
516    ) -> bool {
517        if let Some(mut elicitations) = self.pending_elicitations.get_mut(client_id) {
518            for elicitation in elicitations.iter_mut() {
519                if elicitation.elicitation_id == elicitation_id {
520                    elicitation.set_state(state);
521                    return true;
522                }
523            }
524        }
525        false
526    }
527
528    /// Remove completed elicitations for a client
529    pub fn remove_completed_elicitations(&self, client_id: &str) {
530        if let Some(mut elicitations) = self.pending_elicitations.get_mut(client_id) {
531            elicitations.retain(|e| !e.is_complete());
532        }
533    }
534
535    /// Clear all elicitations for a client
536    pub fn clear_elicitations(&self, client_id: &str) {
537        self.pending_elicitations.remove(client_id);
538    }
539
540    // Completion Management
541
542    /// Add an active completion for a client
543    pub fn add_active_completion(&self, client_id: String, completion: CompletionContext) {
544        self.active_completions
545            .entry(client_id)
546            .or_default()
547            .push(completion);
548    }
549
550    /// Get all active completions for a client
551    #[must_use]
552    pub fn get_active_completions(&self, client_id: &str) -> Vec<CompletionContext> {
553        self.active_completions
554            .get(client_id)
555            .map(|entry| entry.clone())
556            .unwrap_or_default()
557    }
558
559    /// Remove a specific completion
560    pub fn remove_completion(&self, client_id: &str, completion_id: &str) -> bool {
561        if let Some(mut completions) = self.active_completions.get_mut(client_id) {
562            let original_len = completions.len();
563            completions.retain(|c| c.completion_id != completion_id);
564            return completions.len() < original_len;
565        }
566        false
567    }
568
569    /// Clear all completions for a client
570    pub fn clear_completions(&self, client_id: &str) {
571        self.active_completions.remove(client_id);
572    }
573
574    /// Get session statistics including elicitations and completions
575    #[must_use]
576    pub fn get_enhanced_analytics(&self) -> SessionAnalytics {
577        let analytics = self.get_analytics();
578
579        // Add elicitation and completion counts
580        let mut _total_elicitations = 0;
581        let mut _pending_elicitations = 0;
582        let mut _total_completions = 0;
583
584        for entry in self.pending_elicitations.iter() {
585            let elicitations = entry.value();
586            _total_elicitations += elicitations.len();
587            _pending_elicitations += elicitations.iter().filter(|e| !e.is_complete()).count();
588        }
589
590        for entry in self.active_completions.iter() {
591            _total_completions += entry.value().len();
592        }
593
594        // Additional analytics can be added to SessionAnalytics struct when extended
595        // Currently tracking: _total_elicitations, _pending_elicitations, _total_completions
596
597        analytics
598    }
599
600    // Private helper methods
601
602    fn cleanup_expired_sessions(
603        sessions: &Arc<DashMap<String, ClientSession>>,
604        config: &SessionConfig,
605        session_history: &Arc<RwLock<VecDeque<SessionEvent>>>,
606        stats: &Arc<RwLock<SessionStats>>,
607        pending_elicitations: &Arc<DashMap<String, Vec<ElicitationContext>>>,
608        active_completions: &Arc<DashMap<String, Vec<CompletionContext>>>,
609    ) {
610        let cutoff_time = Utc::now() - config.session_timeout;
611        let mut expired_sessions = Vec::new();
612
613        for entry in sessions.iter() {
614            if entry.last_activity < cutoff_time {
615                expired_sessions.push(entry.client_id.clone());
616            }
617        }
618
619        for client_id in expired_sessions {
620            if let Some((_, session)) = sessions.remove(&client_id) {
621                // Update stats
622                let mut stats_guard = stats.write();
623                stats_guard.total_session_duration += session.session_duration();
624                drop(stats_guard);
625
626                // Clean up associated elicitations and completions
627                pending_elicitations.remove(&client_id);
628                active_completions.remove(&client_id);
629
630                // Record event
631                let event = SessionEvent {
632                    timestamp: Utc::now(),
633                    client_id,
634                    event_type: SessionEventType::Expired,
635                    metadata: HashMap::new(),
636                };
637
638                let mut history = session_history.write();
639                if history.len() >= 1000 {
640                    history.pop_front();
641                }
642                history.push_back(event);
643            }
644        }
645    }
646
647    fn record_session_event(
648        &self,
649        client_id: String,
650        event_type: SessionEventType,
651        metadata: HashMap<String, serde_json::Value>,
652    ) {
653        let event = SessionEvent {
654            timestamp: Utc::now(),
655            client_id,
656            event_type,
657            metadata,
658        };
659
660        let mut history = self.session_history.write();
661        if history.len() >= 1000 {
662            history.pop_front();
663        }
664        history.push_back(event);
665    }
666
667    /// Ensure the number of active sessions does not exceed `max_sessions`.
668    /// This uses an LRU-like policy (evict least recently active sessions first).
669    fn enforce_capacity(&self) {
670        let target = self.config.max_sessions;
671        // Fast path
672        if self.sessions.len() < target {
673            return;
674        }
675
676        // Collect sessions sorted by last_activity ascending (least recent first)
677        let mut entries: Vec<_> = self
678            .sessions
679            .iter()
680            .map(|entry| (entry.key().clone(), entry.last_activity))
681            .collect();
682        entries.sort_by_key(|(_, ts)| *ts);
683
684        // Evict until under capacity
685        let mut to_evict = self.sessions.len().saturating_sub(target) + 1; // make room for 1 new
686        for (client_id, _) in entries {
687            if to_evict == 0 {
688                break;
689            }
690            if let Some((_, session)) = self.sessions.remove(&client_id) {
691                let mut stats = self.stats.write();
692                stats.total_session_duration += session.session_duration();
693                drop(stats);
694
695                // Record eviction as termination event
696                let event = SessionEvent {
697                    timestamp: Utc::now(),
698                    client_id: client_id.clone(),
699                    event_type: SessionEventType::Terminated,
700                    metadata: {
701                        let mut m = HashMap::new();
702                        m.insert("reason".to_string(), serde_json::json!("capacity_eviction"));
703                        m
704                    },
705                };
706                {
707                    let mut history = self.session_history.write();
708                    if history.len() >= 1000 {
709                        history.pop_front();
710                    }
711                    history.push_back(event);
712                } // Drop history lock early
713                to_evict = to_evict.saturating_sub(1);
714            }
715        }
716    }
717
718    fn sanitize_parameters(&self, mut params: serde_json::Value) -> serde_json::Value {
719        let _ = self; // Currently unused, may use config in future
720        // Remove or mask sensitive fields
721        if let Some(obj) = params.as_object_mut() {
722            let sensitive_keys = &["password", "token", "api_key", "secret", "auth"];
723            for key in sensitive_keys {
724                if obj.contains_key(*key) {
725                    obj.insert(
726                        (*key).to_string(),
727                        serde_json::Value::String("[REDACTED]".to_string()),
728                    );
729                }
730            }
731        }
732        params
733    }
734}
735
736impl Default for SessionManager {
737    fn default() -> Self {
738        Self::new(SessionConfig::default())
739    }
740}
741
742#[cfg(test)]
743mod tests {
744    use super::*;
745
746    #[tokio::test]
747    async fn test_session_creation() {
748        let manager = SessionManager::new(SessionConfig::default());
749
750        let session = manager.get_or_create_session("client-1".to_string(), "http".to_string());
751
752        assert_eq!(session.client_id, "client-1");
753        assert_eq!(session.transport_type, "http");
754        assert!(!session.authenticated);
755
756        let analytics = manager.get_analytics();
757        assert_eq!(analytics.total_sessions, 1);
758        assert_eq!(analytics.active_sessions, 1);
759    }
760
761    #[tokio::test]
762    async fn test_session_authentication() {
763        let manager = SessionManager::new(SessionConfig::default());
764
765        let session = manager.get_or_create_session("client-1".to_string(), "http".to_string());
766        assert!(!session.authenticated);
767
768        let success = manager.authenticate_client(
769            "client-1",
770            Some("Test Client".to_string()),
771            Some("token123".to_string()),
772        );
773
774        assert!(success);
775
776        let updated_session = manager.get_session("client-1").unwrap();
777        assert!(updated_session.authenticated);
778        assert_eq!(updated_session.client_name, Some("Test Client".to_string()));
779    }
780
781    #[tokio::test]
782    async fn test_request_recording() {
783        let mut manager = SessionManager::new(SessionConfig::default());
784        manager.config.enable_analytics = true;
785
786        let request = RequestInfo::new(
787            "client-1".to_string(),
788            "test_method".to_string(),
789            serde_json::json!({"param": "value"}),
790        )
791        .complete_success(100);
792
793        manager.record_request(request);
794
795        let analytics = manager.get_analytics();
796        assert_eq!(analytics.total_requests, 1);
797        assert_eq!(analytics.successful_requests, 1);
798        assert_eq!(analytics.failed_requests, 0);
799
800        let history = manager.get_request_history(Some(10));
801        assert_eq!(history.len(), 1);
802        assert_eq!(history[0].method_name, "test_method");
803    }
804
805    #[tokio::test]
806    async fn test_session_termination() {
807        let manager = SessionManager::new(SessionConfig::default());
808
809        let _ = manager.get_or_create_session("client-1".to_string(), "http".to_string());
810        assert!(manager.get_session("client-1").is_some());
811
812        let terminated = manager.terminate_session("client-1");
813        assert!(terminated);
814        assert!(manager.get_session("client-1").is_none());
815
816        let analytics = manager.get_analytics();
817        assert_eq!(analytics.active_sessions, 0);
818    }
819
820    #[tokio::test]
821    async fn test_parameter_sanitization() {
822        let manager = SessionManager::new(SessionConfig::default());
823
824        let sensitive_params = serde_json::json!({
825            "username": "testuser",
826            "password": "secret123",
827            "api_key": "key456",
828            "data": "normal_data"
829        });
830
831        let sanitized = manager.sanitize_parameters(sensitive_params);
832        let obj = sanitized.as_object().unwrap();
833
834        assert_eq!(
835            obj["username"],
836            serde_json::Value::String("testuser".to_string())
837        );
838        assert_eq!(
839            obj["password"],
840            serde_json::Value::String("[REDACTED]".to_string())
841        );
842        assert_eq!(
843            obj["api_key"],
844            serde_json::Value::String("[REDACTED]".to_string())
845        );
846        assert_eq!(
847            obj["data"],
848            serde_json::Value::String("normal_data".to_string())
849        );
850    }
851}