use crate::core::models::Metadata;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserSession {
#[serde(flatten)]
pub metadata: Metadata,
pub user_id: Uuid,
#[serde(skip_serializing)]
pub token: String,
pub session_type: SessionType,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub expires_at: chrono::DateTime<chrono::Utc>,
pub last_activity: chrono::DateTime<chrono::Utc>,
pub data: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SessionType {
Web,
Api,
Mobile,
Cli,
}
impl UserSession {
pub fn new(
user_id: Uuid,
token: String,
session_type: SessionType,
expires_at: chrono::DateTime<chrono::Utc>,
) -> Self {
Self {
metadata: Metadata::new(),
user_id,
token,
session_type,
ip_address: None,
user_agent: None,
expires_at,
last_activity: chrono::Utc::now(),
data: HashMap::new(),
}
}
pub fn is_expired(&self) -> bool {
chrono::Utc::now() > self.expires_at
}
pub fn update_activity(&mut self) {
self.last_activity = chrono::Utc::now();
}
pub fn set_data<K: Into<String>, V: Into<serde_json::Value>>(&mut self, key: K, value: V) {
self.data.insert(key.into(), value.into());
}
pub fn get_data(&self, key: &str) -> Option<&serde_json::Value> {
self.data.get(key)
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration, Utc};
#[test]
fn test_session_type_web() {
let session_type = SessionType::Web;
let json = serde_json::to_string(&session_type).unwrap();
assert_eq!(json, "\"web\"");
}
#[test]
fn test_session_type_api() {
let session_type = SessionType::Api;
let json = serde_json::to_string(&session_type).unwrap();
assert_eq!(json, "\"api\"");
}
#[test]
fn test_session_type_mobile() {
let session_type = SessionType::Mobile;
let json = serde_json::to_string(&session_type).unwrap();
assert_eq!(json, "\"mobile\"");
}
#[test]
fn test_session_type_cli() {
let session_type = SessionType::Cli;
let json = serde_json::to_string(&session_type).unwrap();
assert_eq!(json, "\"cli\"");
}
#[test]
fn test_session_type_deserialize() {
let web: SessionType = serde_json::from_str("\"web\"").unwrap();
assert!(matches!(web, SessionType::Web));
let api: SessionType = serde_json::from_str("\"api\"").unwrap();
assert!(matches!(api, SessionType::Api));
}
#[test]
fn test_session_type_clone() {
let original = SessionType::Mobile;
let cloned = original.clone();
let json1 = serde_json::to_string(&original).unwrap();
let json2 = serde_json::to_string(&cloned).unwrap();
assert_eq!(json1, json2);
}
#[test]
fn test_session_type_debug() {
let session_type = SessionType::Cli;
let debug_str = format!("{:?}", session_type);
assert!(debug_str.contains("Cli"));
}
#[test]
fn test_user_session_new() {
let user_id = Uuid::new_v4();
let token = "test_token_123".to_string();
let expires_at = Utc::now() + Duration::hours(24);
let session = UserSession::new(user_id, token.clone(), SessionType::Web, expires_at);
assert_eq!(session.user_id, user_id);
assert_eq!(session.token, token);
assert!(matches!(session.session_type, SessionType::Web));
assert_eq!(session.expires_at, expires_at);
assert!(session.ip_address.is_none());
assert!(session.user_agent.is_none());
assert!(session.data.is_empty());
}
#[test]
fn test_user_session_new_with_api_type() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::days(30);
let session = UserSession::new(
user_id,
"api_token".to_string(),
SessionType::Api,
expires_at,
);
assert!(matches!(session.session_type, SessionType::Api));
}
#[test]
fn test_user_session_new_with_mobile_type() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::days(7);
let session = UserSession::new(
user_id,
"mobile_token".to_string(),
SessionType::Mobile,
expires_at,
);
assert!(matches!(session.session_type, SessionType::Mobile));
}
#[test]
fn test_user_session_new_with_cli_type() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(1);
let session = UserSession::new(
user_id,
"cli_token".to_string(),
SessionType::Cli,
expires_at,
);
assert!(matches!(session.session_type, SessionType::Cli));
}
#[test]
fn test_user_session_not_expired() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let session = UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
assert!(!session.is_expired());
}
#[test]
fn test_user_session_expired() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() - Duration::hours(1);
let session = UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
assert!(session.is_expired());
}
#[test]
fn test_user_session_just_expired() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() - Duration::seconds(1);
let session = UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
assert!(session.is_expired());
}
#[test]
fn test_user_session_update_activity() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let mut session =
UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
let initial_activity = session.last_activity;
std::thread::sleep(std::time::Duration::from_millis(10));
session.update_activity();
assert!(session.last_activity >= initial_activity);
}
#[test]
fn test_user_session_set_data() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let mut session =
UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
session.set_data("key1", "value1");
session.set_data("key2", 42);
session.set_data("key3", true);
assert_eq!(session.data.len(), 3);
}
#[test]
fn test_user_session_get_data() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let mut session =
UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
session.set_data("test_key", "test_value");
let value = session.get_data("test_key");
assert!(value.is_some());
assert_eq!(value.unwrap(), "test_value");
}
#[test]
fn test_user_session_get_data_missing() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let session = UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
assert!(session.get_data("nonexistent").is_none());
}
#[test]
fn test_user_session_set_data_overwrite() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let mut session =
UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
session.set_data("key", "original");
session.set_data("key", "updated");
let value = session.get_data("key");
assert_eq!(value.unwrap(), "updated");
}
#[test]
fn test_user_session_set_data_various_types() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let mut session =
UserSession::new(user_id, "token".to_string(), SessionType::Web, expires_at);
session.set_data("string", "hello");
session.set_data("number", 123);
session.set_data("float", 1.234);
session.set_data("bool", true);
session.set_data("null", serde_json::Value::Null);
assert_eq!(session.data.len(), 5);
}
#[test]
fn test_user_session_serialize() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let session = UserSession::new(
user_id,
"secret_token".to_string(),
SessionType::Web,
expires_at,
);
let json = serde_json::to_string(&session).unwrap();
assert!(!json.contains("secret_token"));
assert!(json.contains(&user_id.to_string()));
assert!(json.contains("\"session_type\":\"web\""));
}
#[test]
fn test_user_session_serialize_with_optional_fields() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let mut session =
UserSession::new(user_id, "token".to_string(), SessionType::Api, expires_at);
session.ip_address = Some("192.168.1.1".to_string());
session.user_agent = Some("Mozilla/5.0".to_string());
let json = serde_json::to_string(&session).unwrap();
assert!(json.contains("192.168.1.1"));
assert!(json.contains("Mozilla/5.0"));
}
#[test]
fn test_user_session_clone() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let mut session = UserSession::new(
user_id,
"token".to_string(),
SessionType::Mobile,
expires_at,
);
session.ip_address = Some("10.0.0.1".to_string());
session.set_data("key", "value");
let cloned = session.clone();
assert_eq!(session.user_id, cloned.user_id);
assert_eq!(session.token, cloned.token);
assert_eq!(session.ip_address, cloned.ip_address);
assert_eq!(session.data.len(), cloned.data.len());
}
#[test]
fn test_user_session_debug() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let session = UserSession::new(user_id, "token".to_string(), SessionType::Cli, expires_at);
let debug_str = format!("{:?}", session);
assert!(debug_str.contains("UserSession"));
assert!(debug_str.contains("Cli"));
}
#[test]
fn test_user_session_empty_token() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let session = UserSession::new(user_id, "".to_string(), SessionType::Web, expires_at);
assert!(session.token.is_empty());
}
#[test]
fn test_user_session_long_token() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::hours(24);
let long_token = "a".repeat(1000);
let session = UserSession::new(user_id, long_token.clone(), SessionType::Web, expires_at);
assert_eq!(session.token.len(), 1000);
}
#[test]
fn test_user_session_far_future_expiry() {
let user_id = Uuid::new_v4();
let expires_at = Utc::now() + Duration::days(365 * 10);
let session = UserSession::new(user_id, "token".to_string(), SessionType::Api, expires_at);
assert!(!session.is_expired());
}
}