mockforge_chaos/
advanced_orchestration.rs

1//! Advanced orchestration features
2//!
3//! Provides conditional logic, variables, hooks, assertions, and reporting
4//! for complex chaos engineering orchestrations.
5
6use crate::scenario_orchestrator::{OrchestratedScenario, ScenarioStep};
7use chrono::{DateTime, Utc};
8use parking_lot::RwLock;
9use serde::{Deserialize, Serialize};
10use serde_json::Value as JsonValue;
11use std::collections::HashMap;
12use std::sync::Arc;
13use thiserror::Error;
14
15/// Orchestration errors
16#[derive(Error, Debug)]
17pub enum OrchestrationError {
18    #[error("Assertion failed: {0}")]
19    AssertionFailed(String),
20
21    #[error("Hook execution failed: {0}")]
22    HookFailed(String),
23
24    #[error("Variable not found: {0}")]
25    VariableNotFound(String),
26
27    #[error("Condition evaluation failed: {0}")]
28    ConditionFailed(String),
29
30    #[error("Serialization error: {0}")]
31    SerializationError(String),
32}
33
34/// Conditional expression for if/then logic
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(tag = "type", rename_all = "snake_case")]
37pub enum Condition {
38    /// Variable equals value
39    Equals { variable: String, value: JsonValue },
40    /// Variable not equals value
41    NotEquals { variable: String, value: JsonValue },
42    /// Variable greater than value
43    GreaterThan { variable: String, value: f64 },
44    /// Variable less than value
45    LessThan { variable: String, value: f64 },
46    /// Variable greater than or equal to value
47    GreaterThanOrEqual { variable: String, value: f64 },
48    /// Variable less than or equal to value
49    LessThanOrEqual { variable: String, value: f64 },
50    /// Variable exists
51    Exists { variable: String },
52    /// AND condition
53    And { conditions: Vec<Condition> },
54    /// OR condition
55    Or { conditions: Vec<Condition> },
56    /// NOT condition
57    Not { condition: Box<Condition> },
58    /// Previous step succeeded
59    PreviousStepSucceeded,
60    /// Previous step failed
61    PreviousStepFailed,
62    /// Metric threshold
63    MetricThreshold {
64        metric_name: String,
65        operator: ComparisonOperator,
66        threshold: f64,
67    },
68}
69
70/// Comparison operator
71#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
72#[serde(rename_all = "snake_case")]
73pub enum ComparisonOperator {
74    Equals,
75    NotEquals,
76    GreaterThan,
77    LessThan,
78    GreaterThanOrEqual,
79    LessThanOrEqual,
80}
81
82impl Condition {
83    /// Evaluate the condition
84    pub fn evaluate(&self, context: &ExecutionContext) -> Result<bool, OrchestrationError> {
85        match self {
86            Condition::Equals { variable, value } => {
87                let var_value = context.get_variable(variable)?;
88                Ok(var_value == value)
89            }
90            Condition::NotEquals { variable, value } => {
91                let var_value = context.get_variable(variable)?;
92                Ok(var_value != value)
93            }
94            Condition::GreaterThan { variable, value } => {
95                let var_value = context.get_variable(variable)?;
96                if let Some(num) = var_value.as_f64() {
97                    Ok(num > *value)
98                } else {
99                    Err(OrchestrationError::ConditionFailed(format!(
100                        "Variable {} is not a number",
101                        variable
102                    )))
103                }
104            }
105            Condition::LessThan { variable, value } => {
106                let var_value = context.get_variable(variable)?;
107                if let Some(num) = var_value.as_f64() {
108                    Ok(num < *value)
109                } else {
110                    Err(OrchestrationError::ConditionFailed(format!(
111                        "Variable {} is not a number",
112                        variable
113                    )))
114                }
115            }
116            Condition::GreaterThanOrEqual { variable, value } => {
117                let var_value = context.get_variable(variable)?;
118                if let Some(num) = var_value.as_f64() {
119                    Ok(num >= *value)
120                } else {
121                    Err(OrchestrationError::ConditionFailed(format!(
122                        "Variable {} is not a number",
123                        variable
124                    )))
125                }
126            }
127            Condition::LessThanOrEqual { variable, value } => {
128                let var_value = context.get_variable(variable)?;
129                if let Some(num) = var_value.as_f64() {
130                    Ok(num <= *value)
131                } else {
132                    Err(OrchestrationError::ConditionFailed(format!(
133                        "Variable {} is not a number",
134                        variable
135                    )))
136                }
137            }
138            Condition::Exists { variable } => Ok(context.variables.contains_key(variable)),
139            Condition::And { conditions } => {
140                for cond in conditions {
141                    if !cond.evaluate(context)? {
142                        return Ok(false);
143                    }
144                }
145                Ok(true)
146            }
147            Condition::Or { conditions } => {
148                for cond in conditions {
149                    if cond.evaluate(context)? {
150                        return Ok(true);
151                    }
152                }
153                Ok(false)
154            }
155            Condition::Not { condition } => Ok(!condition.evaluate(context)?),
156            Condition::PreviousStepSucceeded => Ok(context.last_step_success),
157            Condition::PreviousStepFailed => Ok(!context.last_step_success),
158            Condition::MetricThreshold {
159                metric_name,
160                operator,
161                threshold,
162            } => {
163                if let Some(value) = context.metrics.get(metric_name) {
164                    Ok(match operator {
165                        ComparisonOperator::Equals => (value - threshold).abs() < f64::EPSILON,
166                        ComparisonOperator::NotEquals => (value - threshold).abs() >= f64::EPSILON,
167                        ComparisonOperator::GreaterThan => value > threshold,
168                        ComparisonOperator::LessThan => value < threshold,
169                        ComparisonOperator::GreaterThanOrEqual => value >= threshold,
170                        ComparisonOperator::LessThanOrEqual => value <= threshold,
171                    })
172                } else {
173                    Err(OrchestrationError::ConditionFailed(format!(
174                        "Metric {} not found",
175                        metric_name
176                    )))
177                }
178            }
179        }
180    }
181}
182
183/// Conditional step with if/then/else logic
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ConditionalStep {
186    /// Step name
187    pub name: String,
188    /// Condition to evaluate
189    pub condition: Condition,
190    /// Steps to execute if condition is true
191    pub then_steps: Vec<AdvancedScenarioStep>,
192    /// Steps to execute if condition is false (optional)
193    pub else_steps: Vec<AdvancedScenarioStep>,
194}
195
196/// Hook type
197#[derive(Debug, Clone, Serialize, Deserialize)]
198#[serde(rename_all = "snake_case")]
199pub enum HookType {
200    /// Execute before step
201    PreStep,
202    /// Execute after step
203    PostStep,
204    /// Execute before orchestration
205    PreOrchestration,
206    /// Execute after orchestration
207    PostOrchestration,
208}
209
210/// Hook action
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(tag = "type", rename_all = "snake_case")]
213pub enum HookAction {
214    /// Set variable
215    SetVariable { name: String, value: JsonValue },
216    /// Log message
217    Log { message: String, level: LogLevel },
218    /// HTTP request
219    HttpRequest {
220        url: String,
221        method: String,
222        body: Option<String>,
223    },
224    /// Execute command
225    Command { command: String, args: Vec<String> },
226    /// Record metric
227    RecordMetric { name: String, value: f64 },
228}
229
230/// Log level
231#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
232#[serde(rename_all = "lowercase")]
233pub enum LogLevel {
234    Trace,
235    Debug,
236    Info,
237    Warn,
238    Error,
239}
240
241/// Hook definition
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct Hook {
244    /// Hook name
245    pub name: String,
246    /// Hook type
247    pub hook_type: HookType,
248    /// Actions to perform
249    pub actions: Vec<HookAction>,
250    /// Condition (optional)
251    pub condition: Option<Condition>,
252}
253
254impl Hook {
255    /// Execute the hook
256    pub async fn execute(&self, context: &mut ExecutionContext) -> Result<(), OrchestrationError> {
257        // Check condition if present
258        if let Some(condition) = &self.condition {
259            if !condition.evaluate(context)? {
260                return Ok(());
261            }
262        }
263
264        for action in &self.actions {
265            self.execute_action(action, context).await?;
266        }
267
268        Ok(())
269    }
270
271    /// Execute a single action
272    async fn execute_action(
273        &self,
274        action: &HookAction,
275        context: &mut ExecutionContext,
276    ) -> Result<(), OrchestrationError> {
277        match action {
278            HookAction::SetVariable { name, value } => {
279                context.set_variable(name.clone(), value.clone());
280                Ok(())
281            }
282            HookAction::Log { message, level } => {
283                use tracing::{debug, error, info, trace, warn};
284                match level {
285                    LogLevel::Trace => trace!("[Hook: {}] {}", self.name, message),
286                    LogLevel::Debug => debug!("[Hook: {}] {}", self.name, message),
287                    LogLevel::Info => info!("[Hook: {}] {}", self.name, message),
288                    LogLevel::Warn => warn!("[Hook: {}] {}", self.name, message),
289                    LogLevel::Error => error!("[Hook: {}] {}", self.name, message),
290                }
291                Ok(())
292            }
293            HookAction::HttpRequest { url, method, body } => {
294                // In production, this would make actual HTTP requests
295                // For now, just log
296                tracing::info!("[Hook: {}] HTTP {} {} (body: {:?})", self.name, method, url, body);
297                Ok(())
298            }
299            HookAction::Command { command, args } => {
300                // In production, this would execute commands
301                // For now, just log
302                tracing::info!("[Hook: {}] Execute: {} {:?}", self.name, command, args);
303                Ok(())
304            }
305            HookAction::RecordMetric { name, value } => {
306                context.record_metric(name.clone(), *value);
307                Ok(())
308            }
309        }
310    }
311}
312
313/// Assertion for validating expected outcomes
314#[derive(Debug, Clone, Serialize, Deserialize)]
315#[serde(tag = "type", rename_all = "snake_case")]
316pub enum Assertion {
317    /// Variable equals value
318    VariableEquals {
319        variable: String,
320        expected: JsonValue,
321    },
322    /// Metric within range
323    MetricInRange { metric: String, min: f64, max: f64 },
324    /// Step completed successfully
325    StepSucceeded { step_name: String },
326    /// Step failed
327    StepFailed { step_name: String },
328    /// Custom condition
329    Condition { condition: Condition },
330}
331
332impl Assertion {
333    /// Validate the assertion
334    pub fn validate(&self, context: &ExecutionContext) -> Result<bool, OrchestrationError> {
335        match self {
336            Assertion::VariableEquals { variable, expected } => {
337                let value = context.get_variable(variable)?;
338                Ok(value == expected)
339            }
340            Assertion::MetricInRange { metric, min, max } => {
341                if let Some(value) = context.metrics.get(metric) {
342                    Ok(*value >= *min && *value <= *max)
343                } else {
344                    Ok(false)
345                }
346            }
347            Assertion::StepSucceeded { step_name } => {
348                if let Some(result) = context.step_results.get(step_name) {
349                    Ok(result.success)
350                } else {
351                    Ok(false)
352                }
353            }
354            Assertion::StepFailed { step_name } => {
355                if let Some(result) = context.step_results.get(step_name) {
356                    Ok(!result.success)
357                } else {
358                    Ok(false)
359                }
360            }
361            Assertion::Condition { condition } => condition.evaluate(context),
362        }
363    }
364}
365
366/// Advanced scenario step with conditionals, variables, hooks, and assertions
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct AdvancedScenarioStep {
369    /// Base step
370    #[serde(flatten)]
371    pub base: ScenarioStep,
372
373    /// Condition to execute this step (optional)
374    pub condition: Option<Condition>,
375
376    /// Pre-step hooks
377    pub pre_hooks: Vec<Hook>,
378
379    /// Post-step hooks
380    pub post_hooks: Vec<Hook>,
381
382    /// Assertions to validate after execution
383    pub assertions: Vec<Assertion>,
384
385    /// Variables to set before execution
386    pub variables: HashMap<String, JsonValue>,
387
388    /// Timeout in seconds (overrides duration)
389    pub timeout_seconds: Option<u64>,
390
391    /// Retry configuration
392    pub retry: Option<RetryConfig>,
393}
394
395/// Retry configuration
396#[derive(Debug, Clone, Serialize, Deserialize)]
397pub struct RetryConfig {
398    /// Maximum retry attempts
399    pub max_attempts: usize,
400    /// Delay between retries (seconds)
401    pub delay_seconds: u64,
402    /// Exponential backoff
403    pub exponential_backoff: bool,
404}
405
406/// Step execution result
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct StepResult {
409    /// Step name
410    pub step_name: String,
411    /// Success status
412    pub success: bool,
413    /// Start time
414    pub start_time: DateTime<Utc>,
415    /// End time
416    pub end_time: DateTime<Utc>,
417    /// Duration in seconds
418    pub duration_seconds: f64,
419    /// Error message if failed
420    pub error: Option<String>,
421    /// Assertion results
422    pub assertion_results: Vec<AssertionResult>,
423    /// Metrics collected during step
424    pub metrics: HashMap<String, f64>,
425}
426
427/// Assertion result
428#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct AssertionResult {
430    /// Assertion description
431    pub description: String,
432    /// Passed or failed
433    pub passed: bool,
434    /// Error message if failed
435    pub error: Option<String>,
436}
437
438/// Execution context with variables and state
439#[derive(Debug, Clone)]
440pub struct ExecutionContext {
441    /// Variables
442    pub variables: HashMap<String, JsonValue>,
443    /// Metrics
444    pub metrics: HashMap<String, f64>,
445    /// Step results
446    pub step_results: HashMap<String, StepResult>,
447    /// Last step success status
448    pub last_step_success: bool,
449    /// Current iteration (for loops)
450    pub iteration: usize,
451}
452
453impl ExecutionContext {
454    /// Create a new execution context
455    pub fn new() -> Self {
456        Self {
457            variables: HashMap::new(),
458            metrics: HashMap::new(),
459            step_results: HashMap::new(),
460            last_step_success: true,
461            iteration: 0,
462        }
463    }
464
465    /// Set a variable
466    pub fn set_variable(&mut self, name: String, value: JsonValue) {
467        self.variables.insert(name, value);
468    }
469
470    /// Get a variable
471    pub fn get_variable(&self, name: &str) -> Result<&JsonValue, OrchestrationError> {
472        self.variables
473            .get(name)
474            .ok_or_else(|| OrchestrationError::VariableNotFound(name.to_string()))
475    }
476
477    /// Record a metric
478    pub fn record_metric(&mut self, name: String, value: f64) {
479        self.metrics.insert(name, value);
480    }
481
482    /// Record step result
483    pub fn record_step_result(&mut self, result: StepResult) {
484        self.last_step_success = result.success;
485        self.step_results.insert(result.step_name.clone(), result);
486    }
487}
488
489impl Default for ExecutionContext {
490    fn default() -> Self {
491        Self::new()
492    }
493}
494
495/// Advanced orchestrated scenario
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct AdvancedOrchestratedScenario {
498    /// Base orchestration
499    #[serde(flatten)]
500    pub base: OrchestratedScenario,
501
502    /// Advanced steps
503    pub advanced_steps: Vec<AdvancedScenarioStep>,
504
505    /// Conditional steps
506    pub conditional_steps: Vec<ConditionalStep>,
507
508    /// Global hooks
509    pub hooks: Vec<Hook>,
510
511    /// Global assertions
512    pub assertions: Vec<Assertion>,
513
514    /// Initial variables
515    pub variables: HashMap<String, JsonValue>,
516
517    /// Enable detailed reporting
518    pub enable_reporting: bool,
519
520    /// Report output path
521    pub report_path: Option<String>,
522}
523
524impl AdvancedOrchestratedScenario {
525    /// Create from base orchestration
526    pub fn from_base(base: OrchestratedScenario) -> Self {
527        Self {
528            base,
529            advanced_steps: Vec::new(),
530            conditional_steps: Vec::new(),
531            hooks: Vec::new(),
532            assertions: Vec::new(),
533            variables: HashMap::new(),
534            enable_reporting: false,
535            report_path: None,
536        }
537    }
538
539    /// Add variable
540    pub fn with_variable(mut self, name: String, value: JsonValue) -> Self {
541        self.variables.insert(name, value);
542        self
543    }
544
545    /// Add hook
546    pub fn with_hook(mut self, hook: Hook) -> Self {
547        self.hooks.push(hook);
548        self
549    }
550
551    /// Add assertion
552    pub fn with_assertion(mut self, assertion: Assertion) -> Self {
553        self.assertions.push(assertion);
554        self
555    }
556
557    /// Enable reporting
558    pub fn with_reporting(mut self, path: Option<String>) -> Self {
559        self.enable_reporting = true;
560        self.report_path = path;
561        self
562    }
563
564    /// Export to JSON
565    pub fn to_json(&self) -> Result<String, OrchestrationError> {
566        serde_json::to_string_pretty(self)
567            .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
568    }
569
570    /// Export to YAML
571    pub fn to_yaml(&self) -> Result<String, OrchestrationError> {
572        serde_yaml::to_string(self)
573            .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
574    }
575
576    /// Import from JSON
577    pub fn from_json(json: &str) -> Result<Self, OrchestrationError> {
578        serde_json::from_str(json)
579            .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
580    }
581
582    /// Import from YAML
583    pub fn from_yaml(yaml: &str) -> Result<Self, OrchestrationError> {
584        serde_yaml::from_str(yaml)
585            .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
586    }
587}
588
589/// Execution report
590#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct ExecutionReport {
592    /// Orchestration name
593    pub orchestration_name: String,
594    /// Start time
595    pub start_time: DateTime<Utc>,
596    /// End time
597    pub end_time: DateTime<Utc>,
598    /// Total duration in seconds
599    pub total_duration_seconds: f64,
600    /// Success status
601    pub success: bool,
602    /// Step results
603    pub step_results: Vec<StepResult>,
604    /// Assertion results
605    pub assertion_results: Vec<AssertionResult>,
606    /// Final variables
607    pub final_variables: HashMap<String, JsonValue>,
608    /// Final metrics
609    pub final_metrics: HashMap<String, f64>,
610    /// Errors encountered
611    pub errors: Vec<String>,
612}
613
614impl ExecutionReport {
615    /// Create a new report
616    pub fn new(orchestration_name: String, start_time: DateTime<Utc>) -> Self {
617        Self {
618            orchestration_name,
619            start_time,
620            end_time: Utc::now(),
621            total_duration_seconds: 0.0,
622            success: true,
623            step_results: Vec::new(),
624            assertion_results: Vec::new(),
625            final_variables: HashMap::new(),
626            final_metrics: HashMap::new(),
627            errors: Vec::new(),
628        }
629    }
630
631    /// Finalize the report
632    pub fn finalize(mut self, context: &ExecutionContext) -> Self {
633        self.end_time = Utc::now();
634        self.total_duration_seconds =
635            (self.end_time - self.start_time).num_milliseconds() as f64 / 1000.0;
636        self.final_variables = context.variables.clone();
637        self.final_metrics = context.metrics.clone();
638        self.step_results = context.step_results.values().cloned().collect();
639        self.success = self.step_results.iter().all(|r| r.success) && self.errors.is_empty();
640        self
641    }
642
643    /// Export to JSON
644    pub fn to_json(&self) -> Result<String, OrchestrationError> {
645        serde_json::to_string_pretty(self)
646            .map_err(|e| OrchestrationError::SerializationError(e.to_string()))
647    }
648
649    /// Export to HTML
650    pub fn to_html(&self) -> String {
651        format!(
652            r#"<!DOCTYPE html>
653<html>
654<head>
655    <title>Chaos Orchestration Report: {}</title>
656    <style>
657        body {{ font-family: Arial, sans-serif; margin: 20px; }}
658        .header {{ background: #f5f5f5; padding: 20px; border-radius: 5px; }}
659        .success {{ color: green; }}
660        .failure {{ color: red; }}
661        table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
662        th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
663        th {{ background: #f5f5f5; }}
664    </style>
665</head>
666<body>
667    <div class="header">
668        <h1>Chaos Orchestration Report</h1>
669        <h2>{}</h2>
670        <p><strong>Status:</strong> <span class="{}">{}</span></p>
671        <p><strong>Duration:</strong> {:.2} seconds</p>
672        <p><strong>Start Time:</strong> {}</p>
673        <p><strong>End Time:</strong> {}</p>
674    </div>
675
676    <h2>Step Results</h2>
677    <table>
678        <tr>
679            <th>Step</th>
680            <th>Status</th>
681            <th>Duration (s)</th>
682            <th>Assertions</th>
683        </tr>
684        {}
685    </table>
686
687    <h2>Metrics</h2>
688    <table>
689        <tr>
690            <th>Metric</th>
691            <th>Value</th>
692        </tr>
693        {}
694    </table>
695</body>
696</html>"#,
697            self.orchestration_name,
698            self.orchestration_name,
699            if self.success { "success" } else { "failure" },
700            if self.success { "SUCCESS" } else { "FAILURE" },
701            self.total_duration_seconds,
702            self.start_time,
703            self.end_time,
704            self.step_results
705                .iter()
706                .map(|r| format!(
707                    "<tr><td>{}</td><td class=\"{}\">{}</td><td>{:.2}</td><td>{}/{}</td></tr>",
708                    r.step_name,
709                    if r.success { "success" } else { "failure" },
710                    if r.success { "SUCCESS" } else { "FAILURE" },
711                    r.duration_seconds,
712                    r.assertion_results.iter().filter(|a| a.passed).count(),
713                    r.assertion_results.len()
714                ))
715                .collect::<Vec<_>>()
716                .join("\n        "),
717            self.final_metrics
718                .iter()
719                .map(|(k, v)| format!("<tr><td>{}</td><td>{:.2}</td></tr>", k, v))
720                .collect::<Vec<_>>()
721                .join("\n        ")
722        )
723    }
724}
725
726/// Orchestration library for storing and sharing orchestrations
727#[derive(Debug, Clone)]
728pub struct OrchestrationLibrary {
729    /// Storage for orchestrations
730    orchestrations: Arc<RwLock<HashMap<String, AdvancedOrchestratedScenario>>>,
731}
732
733impl OrchestrationLibrary {
734    /// Create a new library
735    pub fn new() -> Self {
736        Self {
737            orchestrations: Arc::new(RwLock::new(HashMap::new())),
738        }
739    }
740
741    /// Store an orchestration
742    pub fn store(&self, name: String, orchestration: AdvancedOrchestratedScenario) {
743        let mut orch = self.orchestrations.write();
744        orch.insert(name, orchestration);
745    }
746
747    /// Retrieve an orchestration
748    pub fn retrieve(&self, name: &str) -> Option<AdvancedOrchestratedScenario> {
749        let orch = self.orchestrations.read();
750        orch.get(name).cloned()
751    }
752
753    /// List all orchestrations
754    pub fn list(&self) -> Vec<String> {
755        let orch = self.orchestrations.read();
756        orch.keys().cloned().collect()
757    }
758
759    /// Delete an orchestration
760    pub fn delete(&self, name: &str) -> bool {
761        let mut orch = self.orchestrations.write();
762        orch.remove(name).is_some()
763    }
764
765    /// Import from directory
766    pub fn import_from_directory(&self, _path: &str) -> Result<usize, OrchestrationError> {
767        // In production, this would scan a directory and import files
768        // For now, just return 0
769        Ok(0)
770    }
771
772    /// Export to directory
773    pub fn export_to_directory(&self, _path: &str) -> Result<usize, OrchestrationError> {
774        // In production, this would export all orchestrations to files
775        // For now, just return count
776        let orch = self.orchestrations.read();
777        Ok(orch.len())
778    }
779}
780
781impl Default for OrchestrationLibrary {
782    fn default() -> Self {
783        Self::new()
784    }
785}
786
787#[cfg(test)]
788mod tests {
789    use super::*;
790
791    #[test]
792    fn test_condition_equals() {
793        let mut context = ExecutionContext::new();
794        context.set_variable("test".to_string(), JsonValue::String("value".to_string()));
795
796        let condition = Condition::Equals {
797            variable: "test".to_string(),
798            value: JsonValue::String("value".to_string()),
799        };
800
801        assert!(condition.evaluate(&context).unwrap());
802    }
803
804    #[test]
805    fn test_condition_and() {
806        let mut context = ExecutionContext::new();
807        context.set_variable("a".to_string(), JsonValue::Number(5.into()));
808        context.set_variable("b".to_string(), JsonValue::Number(10.into()));
809
810        let condition = Condition::And {
811            conditions: vec![
812                Condition::GreaterThan {
813                    variable: "a".to_string(),
814                    value: 3.0,
815                },
816                Condition::LessThan {
817                    variable: "b".to_string(),
818                    value: 15.0,
819                },
820            ],
821        };
822
823        assert!(condition.evaluate(&context).unwrap());
824    }
825
826    #[test]
827    fn test_execution_context() {
828        let mut context = ExecutionContext::new();
829        context.set_variable("test".to_string(), JsonValue::String("value".to_string()));
830        context.record_metric("latency".to_string(), 100.0);
831
832        assert_eq!(context.get_variable("test").unwrap(), &JsonValue::String("value".to_string()));
833        assert_eq!(*context.metrics.get("latency").unwrap(), 100.0);
834    }
835
836    #[test]
837    fn test_orchestration_library() {
838        let library = OrchestrationLibrary::new();
839
840        let orch = AdvancedOrchestratedScenario::from_base(OrchestratedScenario::new("test"));
841
842        library.store("test".to_string(), orch.clone());
843
844        let retrieved = library.retrieve("test");
845        assert!(retrieved.is_some());
846
847        let list = library.list();
848        assert_eq!(list.len(), 1);
849
850        let deleted = library.delete("test");
851        assert!(deleted);
852
853        let list = library.list();
854        assert_eq!(list.len(), 0);
855    }
856
857    #[test]
858    fn test_execution_report() {
859        let report = ExecutionReport::new("test".to_string(), Utc::now());
860        let context = ExecutionContext::new();
861
862        let final_report = report.finalize(&context);
863        assert_eq!(final_report.orchestration_name, "test");
864        assert!(final_report.total_duration_seconds >= 0.0);
865    }
866}