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