use crate::api::CreateSessionRequest;
use crate::session::manager::DEFAULT_NAMESPACE;
use crate::utils::{ChainError, UuidGenerator};
pub use optionstratlib::simulation::WalkType as SimulationMethod;
use optionstratlib::utils::TimeFrame;
use optionstratlib::{Positive, pos};
use rand::Rng;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::SystemTime;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum SessionState {
Initialized,
InProgress,
Modified,
Reinitialized,
Completed,
Error,
}
impl fmt::Display for SessionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SessionState::Initialized => write!(f, "Initialized"),
SessionState::InProgress => write!(f, "In Progress"),
SessionState::Modified => write!(f, "Modified"),
SessionState::Reinitialized => write!(f, "Reinitialized"),
SessionState::Completed => write!(f, "Completed"),
SessionState::Error => write!(f, "Error"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimulationParameters {
pub symbol: String,
pub steps: usize,
pub initial_price: Positive,
pub days_to_expiration: Positive,
pub volatility: Positive,
pub risk_free_rate: Decimal,
pub dividend_yield: Positive,
pub method: SimulationMethod,
pub time_frame: TimeFrame,
pub chain_size: Option<usize>,
pub strike_interval: Option<Positive>,
pub skew_slope: Option<Decimal>,
pub smile_curve: Option<Decimal>,
pub spread: Option<Positive>,
#[serde(default)]
pub seed: Option<u64>,
}
impl fmt::Display for SimulationParameters {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
write!(f, "{}", json)
}
}
impl From<CreateSessionRequest> for SimulationParameters {
fn from(req: CreateSessionRequest) -> Self {
Self {
symbol: req.symbol,
steps: req.steps,
initial_price: pos!(req.initial_price),
days_to_expiration: pos!(req.days_to_expiration),
volatility: pos!(req.volatility),
risk_free_rate: Decimal::try_from(req.risk_free_rate).unwrap_or_default(),
dividend_yield: pos!(req.dividend_yield),
method: req.method.into(),
time_frame: req.time_frame.into(),
chain_size: req.chain_size,
strike_interval: req.strike_interval.map(|v| pos!(v)),
skew_slope: req
.skew_slope
.map(|v| Decimal::try_from(v).unwrap_or_default()),
smile_curve: req
.smile_curve
.map(|v| Decimal::try_from(v).unwrap_or_default()),
spread: req.spread.map(|v| pos!(v)),
seed: Some(req.seed.unwrap_or_else(|| rand::rng().random())),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub id: Uuid,
pub created_at: SystemTime,
pub updated_at: SystemTime,
pub parameters: SimulationParameters,
pub current_step: usize,
pub total_steps: usize,
pub state: SessionState,
}
impl Session {
pub fn new_with_generator(
parameters: SimulationParameters,
uuid_generator: &UuidGenerator,
) -> Self {
let now = SystemTime::now();
Self {
id: uuid_generator.next(),
created_at: now,
updated_at: now,
current_step: 0,
total_steps: parameters.steps,
parameters,
state: SessionState::Initialized,
}
}
pub fn new(parameters: SimulationParameters, uuid_generator: &UuidGenerator) -> Self {
Self::new_with_generator(parameters, uuid_generator)
}
pub fn advance_step(&mut self) -> Result<(), ChainError> {
if self.current_step >= self.total_steps {
return Err(ChainError::SessionError(
"Session has completed all steps".to_string(),
));
}
self.current_step += 1;
self.updated_at = SystemTime::now();
if self.current_step == self.total_steps {
self.state = SessionState::Completed;
} else if self.state == SessionState::Initialized || self.state == SessionState::Modified {
self.state = SessionState::InProgress;
}
Ok(())
}
pub fn modify_parameters(&mut self, new_params: SimulationParameters) {
self.parameters = new_params;
self.updated_at = SystemTime::now();
self.state = SessionState::Modified;
}
pub fn reinitialize(&mut self, new_params: SimulationParameters) {
self.total_steps = new_params.steps;
self.parameters = new_params;
self.current_step = 0;
self.updated_at = SystemTime::now();
self.state = SessionState::Reinitialized;
}
pub fn is_active(&self) -> bool {
self.state != SessionState::Completed && self.state != SessionState::Error
}
}
impl Default for Session {
fn default() -> Self {
let namespace =
Uuid::parse_str(DEFAULT_NAMESPACE).expect("Failed to parse default UUID namespace");
let uuid_generator = UuidGenerator::new(namespace);
let default_params = SimulationParameters {
symbol: "AAPL".to_string(),
steps: 20,
initial_price: pos!(100.0),
days_to_expiration: pos!(30.0),
volatility: pos!(0.2),
risk_free_rate: Decimal::new(3, 2),
dividend_yield: Positive::ZERO,
method: SimulationMethod::GeometricBrownian {
dt: pos!(1.0 / 252.0),
drift: Decimal::ZERO,
volatility: pos!(0.2),
},
time_frame: TimeFrame::Day,
chain_size: Some(30),
strike_interval: Some(pos!(5.0)),
skew_slope: Some(dec!(-0.2)),
smile_curve: Some(dec!(0.4)),
spread: Some(pos!(0.02)),
seed: None,
};
Self {
id: uuid_generator.next(),
created_at: SystemTime::now(),
updated_at: SystemTime::now(),
parameters: default_params,
current_step: 0,
total_steps: 20,
state: SessionState::Initialized,
}
}
}
impl fmt::Display for Session {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
write!(f, "{}", json)
}
}
#[cfg(test)]
mod tests_seed_conversion {
use super::*;
#[test]
fn test_explicit_seed_is_preserved() {
let req = CreateSessionRequest {
symbol: "AAPL".to_string(),
seed: Some(42),
..Default::default()
};
let params: SimulationParameters = req.into();
assert_eq!(params.seed, Some(42));
}
#[test]
fn test_missing_seed_generates_effective_seed() {
let req = CreateSessionRequest {
symbol: "AAPL".to_string(),
seed: None,
..Default::default()
};
let params: SimulationParameters = req.into();
assert!(params.seed.is_some());
}
#[test]
fn test_seed_survives_serialization_round_trip() {
let req = CreateSessionRequest {
symbol: "AAPL".to_string(),
seed: Some(7),
..Default::default()
};
let params: SimulationParameters = req.into();
let json = serde_json::to_string(¶ms).unwrap();
let restored: SimulationParameters = serde_json::from_str(&json).unwrap();
assert_eq!(restored.seed, Some(7));
}
#[test]
fn test_deserialization_without_seed_defaults_to_none() {
let json = r#"{
"symbol": "TSLA",
"steps": 50,
"initial_price": 240.35,
"days_to_expiration": 60,
"volatility": 0.35,
"risk_free_rate": "0.045",
"dividend_yield": 0,
"method": {
"Brownian": {
"dt": 0.0027,
"drift": "0.02",
"volatility": 0.35
}
},
"time_frame": "Day"
}"#;
let params: SimulationParameters = serde_json::from_str(json).unwrap();
assert_eq!(params.seed, None);
}
}
#[cfg(test)]
mod tests_simulation_parameters_serialization {
use super::*;
use crate::session::SimulationParameters;
use optionstratlib::pos;
use rust_decimal_macros::dec;
use serde_json::{Value, from_str, to_string};
#[test]
fn test_simulation_parameters_serialization() {
let params = SimulationParameters {
symbol: "AAPL".to_string(),
steps: 30,
initial_price: pos!(150.75),
days_to_expiration: pos!(45.0),
volatility: pos!(0.25),
risk_free_rate: dec!(0.04),
dividend_yield: pos!(0.015),
method: SimulationMethod::GeometricBrownian {
dt: pos!(0.0027),
drift: dec!(0.05),
volatility: pos!(0.25),
},
time_frame: TimeFrame::Day,
chain_size: Some(15),
strike_interval: Some(pos!(5.0)),
skew_slope: Some(dec!(-0.2)),
smile_curve: Some(dec!(0.5)),
spread: Some(pos!(0.02)),
seed: None,
};
let json = to_string(¶ms).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["symbol"], "AAPL");
assert_eq!(value["steps"], 30);
assert_eq!(value["initial_price"], 150.75);
assert_eq!(value["days_to_expiration"], 45.0);
assert_eq!(value["volatility"], 0.25);
assert_eq!(value["risk_free_rate"], "0.04");
assert_eq!(value["dividend_yield"], 0.015);
assert_eq!(value["time_frame"], "Day");
assert_eq!(value["chain_size"], 15);
assert_eq!(value["strike_interval"], 5.0);
assert_eq!(value["smile_curve"], "0.5");
assert_eq!(value["spread"], 0.02);
assert!(value["method"].is_object());
assert!(
value["method"]
.as_object()
.unwrap()
.contains_key("GeometricBrownian")
);
assert_eq!(value["method"]["GeometricBrownian"]["dt"], 0.0027);
assert_eq!(value["method"]["GeometricBrownian"]["drift"], "0.05");
assert_eq!(value["method"]["GeometricBrownian"]["volatility"], 0.25);
let deserialized: SimulationParameters = from_str(&json).unwrap();
assert_eq!(deserialized.symbol, "AAPL");
assert_eq!(deserialized.initial_price, pos!(150.75));
assert_eq!(deserialized.chain_size, Some(15));
match deserialized.method {
SimulationMethod::GeometricBrownian {
dt,
drift,
volatility,
} => {
assert_eq!(dt, pos!(0.0027));
assert_eq!(drift, dec!(0.05));
assert_eq!(volatility, pos!(0.25));
}
_ => panic!("Wrong simulation method variant deserialized"),
}
}
#[test]
fn test_simulation_parameters_with_optional_fields_none() {
let params = SimulationParameters {
symbol: "SPY".to_string(),
steps: 20,
initial_price: pos!(420.50),
days_to_expiration: pos!(30.0),
volatility: pos!(0.18),
risk_free_rate: dec!(0.035),
dividend_yield: pos!(0.01),
method: SimulationMethod::Brownian {
dt: pos!(0.0027),
drift: dec!(0.0),
volatility: pos!(0.18),
},
time_frame: TimeFrame::Day,
chain_size: None,
strike_interval: None,
skew_slope: None,
smile_curve: None,
spread: None,
seed: None,
};
let json = to_string(¶ms).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert!(value.get("chain_size").is_none() || value["chain_size"].is_null());
assert!(value.get("strike_interval").is_none() || value["strike_interval"].is_null());
assert!(value.get("smile_curve").is_none() || value["smile_curve"].is_null());
assert!(value.get("spread").is_none() || value["spread"].is_null());
let deserialized: SimulationParameters = from_str(&json).unwrap();
assert_eq!(deserialized.chain_size, None);
assert_eq!(deserialized.strike_interval, None);
assert_eq!(deserialized.smile_curve, None);
assert_eq!(deserialized.spread, None);
}
#[test]
fn test_deserialization_from_json_string() {
let json = r#"{
"symbol": "TSLA",
"steps": 50,
"initial_price": 240.35,
"days_to_expiration": 60,
"volatility": 0.35,
"risk_free_rate": "0.045",
"dividend_yield": 0,
"method": {
"Brownian": {
"dt": 0.0027,
"drift": "0.02",
"volatility": 0.35
}
},
"time_frame": "Day",
"chain_size": 20,
"strike_interval": 10.0,
"smile_curve": "0.001",
"spread": 0.025
}"#;
let params: SimulationParameters = from_str(json).unwrap();
assert_eq!(params.symbol, "TSLA");
assert_eq!(params.steps, 50);
assert_eq!(params.initial_price, pos!(240.35));
assert_eq!(params.days_to_expiration, pos!(60.0));
assert_eq!(params.volatility, pos!(0.35));
assert_eq!(params.risk_free_rate, dec!(0.045));
assert_eq!(params.dividend_yield, pos!(0.0));
assert_eq!(params.time_frame, TimeFrame::Day);
assert_eq!(params.chain_size, Some(20));
assert_eq!(params.strike_interval, Some(pos!(10.0)));
assert_eq!(params.smile_curve, Some(dec!(0.001)));
assert_eq!(params.spread, Some(pos!(0.025)));
match params.method {
SimulationMethod::Brownian {
dt,
drift,
volatility,
} => {
assert_eq!(dt, pos!(0.0027));
assert_eq!(drift, dec!(0.02));
assert_eq!(volatility, pos!(0.35));
}
_ => panic!("Wrong simulation method variant deserialized"),
}
}
#[test]
fn test_different_simulation_methods() {
let params_mr = SimulationParameters {
symbol: "GLD".to_string(),
steps: 40,
initial_price: pos!(1950.0),
days_to_expiration: pos!(90.0),
volatility: pos!(0.15),
risk_free_rate: dec!(0.04),
dividend_yield: pos!(0.0),
method: SimulationMethod::MeanReverting {
dt: pos!(0.0027),
volatility: pos!(0.15),
speed: pos!(0.5),
mean: pos!(2000.0),
},
time_frame: TimeFrame::Day,
chain_size: Some(25),
strike_interval: Some(pos!(25.0)),
skew_slope: None,
smile_curve: None,
spread: Some(pos!(0.01)),
seed: None,
};
let json_mr = to_string(¶ms_mr).unwrap();
let deserialized_mr: SimulationParameters = from_str(&json_mr).unwrap();
match deserialized_mr.method {
SimulationMethod::MeanReverting {
dt,
volatility,
speed,
mean,
} => {
assert_eq!(dt, pos!(0.0027));
assert_eq!(volatility, pos!(0.15));
assert_eq!(speed, pos!(0.5));
assert_eq!(mean, pos!(2000.0));
}
_ => panic!("Wrong simulation method variant deserialized"),
}
let params_hist = SimulationParameters {
symbol: "OIL".to_string(),
steps: 30,
initial_price: pos!(75.0),
days_to_expiration: pos!(30.0),
volatility: pos!(0.25),
risk_free_rate: dec!(0.035),
dividend_yield: pos!(0.0),
method: SimulationMethod::Historical {
timeframe: TimeFrame::Day,
prices: vec![pos!(75.0), pos!(76.2), pos!(74.8), pos!(77.5), pos!(78.1)],
symbol: None,
},
time_frame: TimeFrame::Day,
chain_size: Some(15),
strike_interval: Some(pos!(5.0)),
skew_slope: None,
smile_curve: None,
spread: None,
seed: None,
};
let json_hist = to_string(¶ms_hist).unwrap();
let deserialized_hist: SimulationParameters = from_str(&json_hist).unwrap();
match deserialized_hist.method {
SimulationMethod::Historical {
timeframe, prices, ..
} => {
assert_eq!(timeframe, TimeFrame::Day);
assert_eq!(prices.len(), 5);
assert_eq!(prices[0], pos!(75.0));
assert_eq!(prices[4], pos!(78.1));
}
_ => panic!("Wrong simulation method variant deserialized"),
}
}
#[test]
fn test_timeframe_serialization() {
let timeframes = vec![
(TimeFrame::Minute, "Minute"),
(TimeFrame::Hour, "Hour"),
(TimeFrame::Day, "Day"),
(TimeFrame::Week, "Week"),
(TimeFrame::Month, "Month"),
];
for (tf, expected_str) in timeframes {
let params = SimulationParameters {
symbol: "TEST".to_string(),
steps: 10,
initial_price: pos!(100.0),
days_to_expiration: pos!(30.0),
volatility: pos!(0.2),
risk_free_rate: dec!(0.03),
dividend_yield: pos!(0.01),
method: SimulationMethod::GeometricBrownian {
dt: pos!(0.0027),
drift: dec!(0.0),
volatility: pos!(0.2),
},
time_frame: tf,
chain_size: None,
strike_interval: None,
skew_slope: None,
smile_curve: None,
spread: None,
seed: None,
};
let json = to_string(¶ms).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["time_frame"].as_str().unwrap(), expected_str);
let deserialized: SimulationParameters = from_str(&json).unwrap();
assert_eq!(deserialized.time_frame, tf);
}
}
#[test]
fn test_negative_values() {
let params = SimulationParameters {
symbol: "INDEX".to_string(),
steps: 25,
initial_price: pos!(1000.0),
days_to_expiration: pos!(30.0),
volatility: pos!(0.2),
risk_free_rate: dec!(-0.01), dividend_yield: pos!(0.02),
method: SimulationMethod::JumpDiffusion {
dt: pos!(0.0027),
drift: dec!(-0.02), volatility: pos!(0.2),
intensity: pos!(2.0),
jump_mean: dec!(-0.05), jump_volatility: pos!(0.1),
},
time_frame: TimeFrame::Day,
chain_size: Some(10),
strike_interval: Some(pos!(10.0)),
skew_slope: Some(dec!(-0.2)),
smile_curve: Some(dec!(-0.5)), spread: Some(pos!(0.015)),
seed: None,
};
let json = to_string(¶ms).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["risk_free_rate"], "-0.01");
assert_eq!(value["method"]["JumpDiffusion"]["drift"], "-0.02");
assert_eq!(value["method"]["JumpDiffusion"]["jump_mean"], "-0.05");
assert_eq!(value["smile_curve"], "-0.5");
let deserialized: SimulationParameters = from_str(&json).unwrap();
assert_eq!(deserialized.risk_free_rate, dec!(-0.01));
assert_eq!(deserialized.smile_curve, Some(dec!(-0.5)));
match deserialized.method {
SimulationMethod::JumpDiffusion {
drift, jump_mean, ..
} => {
assert_eq!(drift, dec!(-0.02));
assert_eq!(jump_mean, dec!(-0.05));
}
_ => panic!("Wrong simulation method variant deserialized"),
}
}
}
#[cfg(test)]
mod tests_session_state {
use super::*;
use serde_json;
#[test]
fn test_session_state_display() {
assert_eq!(SessionState::Initialized.to_string(), "Initialized");
assert_eq!(SessionState::InProgress.to_string(), "In Progress");
assert_eq!(SessionState::Modified.to_string(), "Modified");
assert_eq!(SessionState::Reinitialized.to_string(), "Reinitialized");
assert_eq!(SessionState::Completed.to_string(), "Completed");
assert_eq!(SessionState::Error.to_string(), "Error");
}
#[test]
fn test_session_state_serialization() {
let state = SessionState::Initialized;
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"Initialized\"");
let state = SessionState::InProgress;
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"InProgress\"");
let state = SessionState::Modified;
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"Modified\"");
let state = SessionState::Reinitialized;
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"Reinitialized\"");
let state = SessionState::Completed;
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"Completed\"");
let state = SessionState::Error;
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"Error\"");
}
#[test]
fn test_session_state_deserialization() {
let deserialized: SessionState = serde_json::from_str("\"Initialized\"").unwrap();
assert_eq!(deserialized, SessionState::Initialized);
let deserialized: SessionState = serde_json::from_str("\"InProgress\"").unwrap();
assert_eq!(deserialized, SessionState::InProgress);
let deserialized: SessionState = serde_json::from_str("\"Modified\"").unwrap();
assert_eq!(deserialized, SessionState::Modified);
let deserialized: SessionState = serde_json::from_str("\"Reinitialized\"").unwrap();
assert_eq!(deserialized, SessionState::Reinitialized);
let deserialized: SessionState = serde_json::from_str("\"Completed\"").unwrap();
assert_eq!(deserialized, SessionState::Completed);
let deserialized: SessionState = serde_json::from_str("\"Error\"").unwrap();
assert_eq!(deserialized, SessionState::Error);
}
#[test]
fn test_session_state_equality() {
assert_eq!(SessionState::Initialized, SessionState::Initialized);
assert_ne!(SessionState::Initialized, SessionState::InProgress);
assert_ne!(SessionState::Error, SessionState::Completed);
}
}
#[cfg(test)]
mod tests_session {
use super::*;
use optionstratlib::spos;
use rust_decimal_macros::dec;
use std::time::Duration;
use uuid::Uuid;
fn create_test_parameters() -> SimulationParameters {
SimulationParameters {
symbol: "TEST".to_string(),
steps: 10,
initial_price: pos!(100.0),
days_to_expiration: pos!(30.0),
volatility: pos!(0.25),
risk_free_rate: Decimal::new(5, 2), dividend_yield: pos!(0.02), method: SimulationMethod::Brownian {
dt: pos!(0.0027),
drift: dec!(0.0),
volatility: pos!(0.25),
},
time_frame: TimeFrame::Day,
chain_size: Some(10),
strike_interval: spos!(5.0),
skew_slope: None,
smile_curve: Some(Decimal::new(1, 1)), spread: spos!(0.2), seed: None,
}
}
#[test]
fn test_new_with_generator() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let session = Session::new_with_generator(params.clone(), &uuid_gen);
assert_eq!(session.current_step, 0);
assert_eq!(session.total_steps, params.steps);
assert_eq!(session.parameters.symbol, params.symbol);
assert_eq!(session.state, SessionState::Initialized);
let time_diff = session
.updated_at
.duration_since(session.created_at)
.unwrap_or(Duration::from_secs(1));
assert!(time_diff.as_millis() < 100); }
#[test]
fn test_new_delegates_to_new_with_generator() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let session = Session::new(params.clone(), &uuid_gen);
assert_eq!(session.current_step, 0);
assert_eq!(session.total_steps, params.steps);
assert_eq!(session.state, SessionState::Initialized);
}
#[test]
fn test_advance_step_from_initialized() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(params, &uuid_gen);
let original_time = session.updated_at;
std::thread::sleep(Duration::from_millis(10));
let result = session.advance_step();
assert!(result.is_ok());
assert_eq!(session.current_step, 1);
assert_eq!(session.state, SessionState::InProgress);
assert!(session.updated_at > original_time);
}
#[test]
fn test_advance_step_from_modified() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(params.clone(), &uuid_gen);
session.state = SessionState::Modified;
let result = session.advance_step();
assert!(result.is_ok());
assert_eq!(session.current_step, 1);
assert_eq!(session.state, SessionState::InProgress);
}
#[test]
fn test_advance_step_in_progress() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(params.clone(), &uuid_gen);
session.state = SessionState::InProgress;
session.current_step = 1;
let result = session.advance_step();
assert!(result.is_ok());
assert_eq!(session.current_step, 2);
assert_eq!(session.state, SessionState::InProgress);
}
#[test]
fn test_advance_step_to_completion() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(params.clone(), &uuid_gen);
session.state = SessionState::InProgress;
session.current_step = session.total_steps - 1;
let result = session.advance_step();
assert!(result.is_ok());
assert_eq!(session.current_step, session.total_steps);
assert_eq!(session.state, SessionState::Completed);
}
#[test]
fn test_advance_step_already_completed() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(params.clone(), &uuid_gen);
session.current_step = session.total_steps;
session.state = SessionState::Completed;
let result = session.advance_step();
assert!(result.is_err());
match result {
Err(ChainError::SessionError(msg)) => {
assert_eq!(msg, "Session has completed all steps");
}
_ => panic!("Expected SessionError but got {:?}", result),
}
}
#[test]
fn test_modify_parameters() {
let original_params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(original_params, &uuid_gen);
let original_time = session.updated_at;
let mut new_params = create_test_parameters();
new_params.symbol = "MODIFIED".to_string();
new_params.steps = 20;
std::thread::sleep(Duration::from_millis(10));
session.modify_parameters(new_params.clone());
assert_eq!(session.parameters.symbol, "MODIFIED");
assert_eq!(session.parameters.steps, 20);
assert_eq!(session.state, SessionState::Modified);
assert!(session.updated_at > original_time);
}
#[test]
fn test_reinitialize() {
let original_params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(original_params, &uuid_gen);
session.current_step = 5;
session.state = SessionState::InProgress;
let original_time = session.updated_at;
let mut new_params = create_test_parameters();
new_params.symbol = "REINITIALIZED".to_string();
new_params.steps = 15;
std::thread::sleep(Duration::from_millis(10));
session.reinitialize(new_params.clone());
assert_eq!(session.parameters.symbol, "REINITIALIZED");
assert_eq!(session.current_step, 0);
assert_eq!(session.total_steps, 15);
assert_eq!(session.state, SessionState::Reinitialized);
assert!(session.updated_at > original_time);
}
#[test]
fn test_is_active() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let mut session = Session::new_with_generator(params.clone(), &uuid_gen);
session.state = SessionState::Initialized;
assert!(session.is_active());
session.state = SessionState::InProgress;
assert!(session.is_active());
session.state = SessionState::Modified;
assert!(session.is_active());
session.state = SessionState::Reinitialized;
assert!(session.is_active());
session.state = SessionState::Completed;
assert!(!session.is_active());
session.state = SessionState::Error;
assert!(!session.is_active());
}
#[test]
fn test_display_implementation() {
let params = create_test_parameters();
let uuid_gen = UuidGenerator::new(Uuid::new_v4());
let session = Session::new_with_generator(params, &uuid_gen);
let display_string = format!("{}", session);
let parsed_result = serde_json::from_str::<Session>(&display_string);
assert!(parsed_result.is_ok());
let parsed_session = parsed_result.unwrap();
assert_eq!(parsed_session.id, session.id);
assert_eq!(parsed_session.current_step, session.current_step);
assert_eq!(parsed_session.total_steps, session.total_steps);
assert_eq!(parsed_session.state, session.state);
}
}