//! Tests for basic module //! //! Extracted from mod.rs to reduce file size #[cfg(test)] use std::collections::HashMap; use std::time::Duration; // Test script constants from bottest/fixtures/scripts/mod.rs const GREETING_SCRIPT: &str = r#" ' Greeting Flow Script ' Simple greeting and response pattern REM Initialize greeting greeting$ = "Hello! Welcome to our service." TALK greeting$ REM Wait for user response HEAR userInput$ REM Check for specific keywords IF INSTR(UCASE$(userInput$), "HELP") > 0 THEN TALK "I can help you with: Products, Support, or Billing. What would you like to know?" ELSEIF INSTR(UCASE$(userInput$), "BYE") > 0 THEN TALK "Goodbye! Have a great day!" END ELSE TALK "Thank you for your message. How can I assist you today?" END IF "#; const SIMPLE_ECHO_SCRIPT: &str = r#" ' Simple Echo Script ' Echoes back whatever user says TALK "Echo Bot: I will repeat everything you say. Type 'quit' to exit." echo_loop: HEAR input$ IF UCASE$(input$) = "QUIT" THEN TALK "Goodbye!" END END IF TALK "You said: " + input$ GOTO echo_loop "#; const VARIABLES_SCRIPT: &str = r#" ' Variables and Expressions Script ' Demonstrates variable types and operations REM String variables firstName$ = "John" lastName$ = "Doe" fullName$ = firstName$ + " " + lastName$ TALK "Full name: " + fullName$ REM Numeric variables price = 99.99 quantity = 3 subtotal = price * quantity tax = subtotal * 0.08 total = subtotal + tax TALK "Total: $" + STR$(total) "#; fn get_script(name: &str) -> Option<&'static str> { match name { "greeting" => Some(GREETING_SCRIPT), "simple_echo" => Some(SIMPLE_ECHO_SCRIPT), "variables" => Some(VARIABLES_SCRIPT), _ => None, } } fn available_scripts() -> Vec<&'static str> { vec!["greeting", "simple_echo", "variables"] } fn all_scripts() -> HashMap<&'static str, &'static str> { let mut scripts = HashMap::new(); for name in available_scripts() { if let Some(content) = get_script(name) { scripts.insert(name, content); } } scripts } // Runner types from bottest/bot/runner.rs #[derive(Debug, Clone)] pub struct BotRunnerConfig { pub working_dir: std::path::PathBuf, pub timeout: Duration, pub use_mocks: bool, pub env_vars: HashMap, pub capture_logs: bool, pub log_level: LogLevel, } impl BotRunnerConfig { pub const fn log_level(&self) -> LogLevel { self.log_level } } impl Default for BotRunnerConfig { fn default() -> Self { Self { working_dir: std::env::temp_dir().join("bottest"), timeout: Duration::from_secs(30), use_mocks: true, env_vars: HashMap::new(), capture_logs: true, log_level: LogLevel::Info, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum LogLevel { Trace, Debug, #[default] Info, Warn, Error, } #[derive(Debug, Default, Clone)] pub struct RunnerMetrics { pub total_requests: u64, pub successful_requests: u64, pub failed_requests: u64, pub total_latency_ms: u64, pub min_latency_ms: u64, pub max_latency_ms: u64, pub script_executions: u64, pub transfer_to_human_count: u64, } impl RunnerMetrics { pub const fn avg_latency_ms(&self) -> u64 { if self.total_requests > 0 { self.total_latency_ms / self.total_requests } else { 0 } } pub fn success_rate(&self) -> f64 { if self.total_requests > 0 { (self.successful_requests as f64 / self.total_requests as f64) * 100.0 } else { 0.0 } } pub const fn min_latency(&self) -> u64 { self.min_latency_ms } pub const fn max_latency(&self) -> u64 { self.max_latency_ms } pub const fn latency_range(&self) -> u64 { self.max_latency_ms.saturating_sub(self.min_latency_ms) } } // Tests #[test] fn test_get_script() { assert!(get_script("greeting").is_some()); assert!(get_script("simple_echo").is_some()); assert!(get_script("nonexistent").is_none()); } #[test] fn test_available_scripts() { let scripts = available_scripts(); assert!(!scripts.is_empty()); assert!(scripts.contains(&"greeting")); } #[test] fn test_all_scripts() { let scripts = all_scripts(); assert_eq!(scripts.len(), available_scripts().len()); } #[test] fn test_greeting_script_content() { let script = get_script("greeting").unwrap(); assert!(script.contains("TALK")); assert!(script.contains("HEAR")); assert!(script.contains("greeting")); } #[test] fn test_simple_echo_script_content() { let script = get_script("simple_echo").unwrap(); assert!(script.contains("HEAR")); assert!(script.contains("TALK")); assert!(script.contains("GOTO")); } #[test] fn test_variables_script_content() { let script = get_script("variables").unwrap(); assert!(script.contains("firstName$")); assert!(script.contains("price")); assert!(script.contains("STR$")); } #[test] fn test_bot_runner_config_default() { let config = BotRunnerConfig::default(); assert_eq!(config.timeout, Duration::from_secs(30)); assert!(config.use_mocks); assert!(config.capture_logs); } #[test] fn test_runner_metrics_avg_latency() { let metrics = RunnerMetrics { total_requests: 10, total_latency_ms: 1000, ..RunnerMetrics::default() }; assert_eq!(metrics.avg_latency_ms(), 100); } #[test] fn test_runner_metrics_success_rate() { let metrics = RunnerMetrics { total_requests: 100, successful_requests: 95, ..RunnerMetrics::default() }; assert!((metrics.success_rate() - 95.0).abs() < f64::EPSILON); } #[test] fn test_runner_metrics_zero_requests() { let metrics = RunnerMetrics::default(); assert_eq!(metrics.avg_latency_ms(), 0); assert!(metrics.success_rate().abs() < f64::EPSILON); } #[test] fn test_log_level_default() { let level = LogLevel::default(); assert_eq!(level, LogLevel::Info); } #[test] fn test_runner_config_env_vars() { let mut env_vars = HashMap::new(); env_vars.insert("API_KEY".to_string(), "test123".to_string()); env_vars.insert("DEBUG".to_string(), "true".to_string()); let config = BotRunnerConfig { env_vars, ..BotRunnerConfig::default() }; assert_eq!(config.env_vars.get("API_KEY"), Some(&"test123".to_string())); assert_eq!(config.env_vars.get("DEBUG"), Some(&"true".to_string())); } #[test] fn test_runner_config_timeout() { let config = BotRunnerConfig { timeout: Duration::from_secs(60), ..BotRunnerConfig::default() }; assert_eq!(config.timeout, Duration::from_secs(60)); } #[test] fn test_metrics_tracking() { let metrics = RunnerMetrics { total_requests: 50, successful_requests: 45, failed_requests: 5, total_latency_ms: 5000, min_latency_ms: 10, max_latency_ms: 500, ..RunnerMetrics::default() }; assert_eq!(metrics.avg_latency_ms(), 100); assert!((metrics.success_rate() - 90.0).abs() < f64::EPSILON); assert_eq!( metrics.total_requests, metrics.successful_requests + metrics.failed_requests ); assert_eq!(metrics.min_latency(), 10); assert_eq!(metrics.max_latency(), 500); assert_eq!(metrics.latency_range(), 490); } #[test] fn test_script_execution_tracking() { let metrics = RunnerMetrics { script_executions: 25, transfer_to_human_count: 3, ..RunnerMetrics::default() }; assert_eq!(metrics.script_executions, 25); assert_eq!(metrics.transfer_to_human_count, 3); } #[test] fn test_log_level_accessor() { let config = BotRunnerConfig::default(); assert_eq!(config.log_level(), LogLevel::Info); } #[test] fn test_log_levels() { assert!(matches!(LogLevel::Trace, LogLevel::Trace)); assert!(matches!(LogLevel::Debug, LogLevel::Debug)); assert!(matches!(LogLevel::Info, LogLevel::Info)); assert!(matches!(LogLevel::Warn, LogLevel::Warn)); assert!(matches!(LogLevel::Error, LogLevel::Error)); } #[test] fn test_script_contains_basic_keywords() { for name in available_scripts() { if let Some(script) = get_script(name) { // All scripts should have some form of output let has_output = script.contains("TALK") || script.contains("PRINT"); assert!(has_output, "Script {} should have output keyword", name); } } } #[test] fn test_runner_config_working_dir() { let config = BotRunnerConfig::default(); assert!(config.working_dir.to_str().unwrap_or_default().contains("bottest")); }