use crate::scenarios::ScenarioType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum Protocol {
Smtp,
Imap,
Jmap,
Pop3,
Mixed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageSize {
Fixed(usize),
Random { min: usize, max: usize },
}
impl MessageSize {
pub fn get(&self) -> (usize, usize) {
match self {
MessageSize::Fixed(size) => (*size, *size),
MessageSize::Random { min, max } => (*min, *max),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MessageContent {
Random,
Template,
RealWorld,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadTestConfig {
pub target_host: String,
pub target_port: u16,
pub protocol: Protocol,
pub scenario: ScenarioType,
pub duration_secs: u64,
pub concurrency: usize,
pub message_rate: u64,
pub ramp_up_secs: u64,
pub message_size: MessageSize,
pub message_content: MessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_size_min: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_size_max: Option<usize>,
pub output_json: Option<String>,
pub output_html: Option<String>,
pub output_csv: Option<String>,
pub prometheus_export: bool,
pub prometheus_port: u16,
pub mixed_weights: Option<(u8, u8, u8, u8)>,
}
impl LoadTestConfig {
pub fn ramp_up_duration(&self) -> Duration {
Duration::from_secs(self.ramp_up_secs)
}
pub fn test_duration(&self) -> Duration {
Duration::from_secs(self.duration_secs)
}
pub fn message_size_range(&self) -> (usize, usize) {
self.message_size.get()
}
}
impl Default for LoadTestConfig {
fn default() -> Self {
Self {
target_host: "localhost".to_string(),
target_port: 25,
protocol: Protocol::Smtp,
scenario: ScenarioType::SmtpThroughput,
duration_secs: 60,
concurrency: 10,
message_rate: 100,
ramp_up_secs: 0,
message_size: MessageSize::Random {
min: 1024,
max: 102400,
},
message_content: MessageContent::Random,
message_size_min: None,
message_size_max: None,
output_json: None,
output_html: None,
output_csv: None,
prometheus_export: false,
prometheus_port: 9090,
mixed_weights: None,
}
}
}
impl LoadTestConfig {
pub fn validate(&self) -> Result<(), String> {
if self.target_host.is_empty() {
return Err("Target host cannot be empty".to_string());
}
if self.target_port == 0 {
return Err("Target port must be greater than 0".to_string());
}
if self.duration_secs == 0 {
return Err("Duration must be greater than 0".to_string());
}
if self.concurrency == 0 {
return Err("Concurrency must be greater than 0".to_string());
}
match &self.message_size {
MessageSize::Fixed(size) if *size == 0 => {
return Err("Message size must be greater than 0".to_string());
}
MessageSize::Random { min, max } if min > max => {
return Err("Min message size cannot be greater than max".to_string());
}
MessageSize::Random { min, .. } if *min == 0 => {
return Err("Min message size must be greater than 0".to_string());
}
_ => {}
}
if self.protocol == Protocol::Mixed && self.mixed_weights.is_none() {
return Err("Mixed protocol requires weights (smtp, imap, jmap, pop3)".to_string());
}
if let Some((smtp, imap, jmap, pop3)) = self.mixed_weights {
if smtp + imap + jmap + pop3 == 0 {
return Err("At least one protocol weight must be non-zero".to_string());
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_is_valid() {
let config = LoadTestConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_empty_host_is_invalid() {
let config = LoadTestConfig {
target_host: "".to_string(),
..LoadTestConfig::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_zero_port_is_invalid() {
let config = LoadTestConfig {
target_port: 0,
..LoadTestConfig::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_zero_duration_is_invalid() {
let config = LoadTestConfig {
duration_secs: 0,
..LoadTestConfig::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_zero_concurrency_is_invalid() {
let config = LoadTestConfig {
concurrency: 0,
..LoadTestConfig::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_invalid_message_sizes() {
let config = LoadTestConfig {
message_size: MessageSize::Random {
min: 10000,
max: 1000,
},
..LoadTestConfig::default()
};
assert!(config.validate().is_err());
}
}