mockforge_core/voice/
command_parser.rs

1//! LLM-based command parser for voice commands
2//!
3//! This module parses natural language voice commands and extracts API requirements
4//! using MockForge's LLM infrastructure.
5
6use crate::intelligent_behavior::{
7    config::IntelligentBehaviorConfig, llm_client::LlmClient, types::LlmGenerationRequest,
8};
9use crate::Result;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13/// Voice command parser that uses LLM to interpret natural language commands
14pub struct VoiceCommandParser {
15    /// LLM client for parsing commands
16    llm_client: LlmClient,
17    /// Configuration
18    config: IntelligentBehaviorConfig,
19}
20
21impl VoiceCommandParser {
22    /// Create a new voice command parser
23    pub fn new(config: IntelligentBehaviorConfig) -> Self {
24        let behavior_model = config.behavior_model.clone();
25        let llm_client = LlmClient::new(behavior_model);
26
27        Self { llm_client, config }
28    }
29
30    /// Parse a natural language command into structured API requirements
31    ///
32    /// This method uses the LLM to extract:
33    /// - API type (e-commerce, social media, etc.)
34    /// - Endpoints and HTTP methods
35    /// - Data models and relationships
36    /// - Sample data counts
37    /// - Business flows (checkout, auth, etc.)
38    pub async fn parse_command(&self, command: &str) -> Result<ParsedCommand> {
39        // Build system prompt for command parsing
40        let system_prompt = r#"You are an expert API designer. Your task is to parse natural language commands
41that describe API requirements and extract structured information.
42
43Extract the following information from the command:
441. API type/category (e.g., e-commerce, social media, blog, todo app)
452. Endpoints with HTTP methods (GET, POST, PUT, DELETE, PATCH)
463. Data models with fields and types
474. Relationships between models
485. Sample data counts (e.g., "20 products")
496. Business flows (e.g., checkout, authentication, user registration)
50
51Return your response as a JSON object with this structure:
52{
53  "api_type": "string (e.g., e-commerce, social-media, blog)",
54  "title": "string (API title)",
55  "description": "string (API description)",
56  "endpoints": [
57    {
58      "path": "string (e.g., /api/products)",
59      "method": "string (GET, POST, PUT, DELETE, PATCH)",
60      "description": "string",
61      "request_body": {
62        "schema": "object schema if applicable",
63        "required": ["array of required fields"]
64      },
65      "response": {
66        "status": 200,
67        "schema": "object schema",
68        "is_array": false,
69        "count": null or number if specified
70      }
71    }
72  ],
73  "models": [
74    {
75      "name": "string (e.g., Product)",
76      "fields": [
77        {
78          "name": "string",
79          "type": "string (string, number, integer, boolean, array, object)",
80          "description": "string",
81          "required": true
82        }
83      ]
84    }
85  ],
86  "relationships": [
87    {
88      "from": "string (model name)",
89      "to": "string (model name)",
90      "type": "string (one-to-many, many-to-many, one-to-one)"
91    }
92  ],
93  "sample_counts": {
94    "model_name": number
95  },
96  "flows": [
97    {
98      "name": "string (e.g., checkout)",
99      "description": "string",
100      "steps": ["array of step descriptions"]
101    }
102  ]
103}
104
105Be specific and extract all details mentioned in the command. If something is not mentioned,
106don't include it in the response."#;
107
108        // Build user prompt with the command
109        let user_prompt =
110            format!("Parse this API creation command and extract all requirements:\n\n{}", command);
111
112        // Create LLM request
113        let llm_request = LlmGenerationRequest {
114            system_prompt: system_prompt.to_string(),
115            user_prompt,
116            temperature: 0.3, // Lower temperature for more consistent parsing
117            max_tokens: 2000,
118            schema: None,
119        };
120
121        // Generate response from LLM
122        let response = self.llm_client.generate(&llm_request).await?;
123
124        // Parse the response into ParsedCommand
125        let response_str = serde_json::to_string(&response).unwrap_or_default();
126        let parsed: ParsedCommand = serde_json::from_value(response).map_err(|e| {
127            crate::Error::generic(format!(
128                "Failed to parse LLM response as ParsedCommand: {}. Response: {}",
129                e, response_str
130            ))
131        })?;
132
133        Ok(parsed)
134    }
135
136    /// Parse a conversational command (for multi-turn interactions)
137    ///
138    /// This method parses commands that modify or extend an existing API specification.
139    /// It takes the current conversation context into account.
140    pub async fn parse_conversational_command(
141        &self,
142        command: &str,
143        context: &super::conversation::ConversationContext,
144    ) -> Result<ParsedCommand> {
145        // Build system prompt for conversational parsing
146        let system_prompt = r#"You are an expert API designer helping to build an API through conversation.
147The user is providing incremental commands to modify or extend an existing API specification.
148
149Extract the following information from the command:
1501. What is being added/modified (endpoints, models, flows)
1512. Details about the addition/modification
1523. Any relationships or dependencies
153
154Return your response as a JSON object with the same structure as parse_command, but focus only
155on what is NEW or MODIFIED. If the command is asking to add something, include it. If it's asking
156to modify something, include the modified version.
157
158If the command is asking a question or requesting confirmation, return an empty endpoints array
159and include a "question" or "confirmation" field in the response."#;
160
161        // Build context summary
162        let context_summary = format!(
163            "Current API: {}\nExisting endpoints: {}\nExisting models: {}",
164            context.current_spec.as_ref().map(|s| s.title()).unwrap_or("None"),
165            context
166                .current_spec
167                .as_ref()
168                .map(|s| {
169                    s.all_paths_and_operations()
170                        .iter()
171                        .map(|(path, ops)| {
172                            format!(
173                                "{} ({})",
174                                path,
175                                ops.keys().map(|s| s.as_str()).collect::<Vec<_>>().join(", ")
176                            )
177                        })
178                        .collect::<Vec<_>>()
179                        .join(", ")
180                })
181                .unwrap_or_else(|| "None".to_string()),
182            context
183                .current_spec
184                .as_ref()
185                .and_then(|s| s.spec.components.as_ref())
186                .map(|c| c.schemas.keys().cloned().collect::<Vec<_>>().join(", "))
187                .unwrap_or_else(|| "None".to_string())
188        );
189
190        // Build user prompt
191        let user_prompt = format!("Context:\n{}\n\nNew command:\n{}", context_summary, command);
192
193        // Create LLM request
194        let llm_request = LlmGenerationRequest {
195            system_prompt: system_prompt.to_string(),
196            user_prompt,
197            temperature: 0.3,
198            max_tokens: 2000,
199            schema: None,
200        };
201
202        // Generate response
203        let response = self.llm_client.generate(&llm_request).await?;
204
205        // Parse response
206        let response_str = serde_json::to_string(&response).unwrap_or_default();
207        let parsed: ParsedCommand = serde_json::from_value(response).map_err(|e| {
208            crate::Error::generic(format!(
209                "Failed to parse conversational LLM response: {}. Response: {}",
210                e, response_str
211            ))
212        })?;
213
214        Ok(parsed)
215    }
216
217    /// Parse a workspace scenario description
218    ///
219    /// This method extracts information about creating a complete workspace scenario,
220    /// including domain, chaos characteristics, initial data, and API requirements.
221    pub async fn parse_workspace_scenario_command(
222        &self,
223        command: &str,
224    ) -> Result<ParsedWorkspaceScenario> {
225        // Build system prompt for workspace scenario parsing
226        let system_prompt = r#"You are an expert at parsing natural language descriptions of workspace scenarios
227and extracting structured information for creating complete mock environments.
228
229Extract the following information from the command:
2301. Domain/industry (e.g., bank, e-commerce, healthcare, etc.)
2312. Chaos/failure characteristics (flaky rates, slow KYC, high latency, etc.)
2323. Initial data requirements (number of users, disputes, orders, etc.)
2334. API endpoints needed for the domain
2345. Behavioral rules (failure rates, latency patterns, etc.)
2356. Data models and relationships
236
237Return your response as a JSON object with this structure:
238{
239  "domain": "string (e.g., bank, e-commerce, healthcare)",
240  "title": "string (workspace title)",
241  "description": "string (workspace description)",
242  "chaos_characteristics": [
243    {
244      "type": "string (latency|failure|rate_limit|etc.)",
245      "description": "string (e.g., flaky foreign exchange rates)",
246      "config": {
247        "probability": 0.0-1.0,
248        "delay_ms": number,
249        "error_rate": 0.0-1.0,
250        "error_codes": [500, 502, 503],
251        "details": "additional configuration details"
252      }
253    }
254  ],
255  "initial_data": {
256    "users": number,
257    "disputes": number,
258    "orders": number,
259    "custom": {
260      "entity_name": number
261    }
262  },
263  "api_requirements": {
264    "endpoints": [
265      {
266        "path": "string",
267        "method": "string",
268        "description": "string"
269      }
270    ],
271    "models": [
272      {
273        "name": "string",
274        "fields": [
275          {
276            "name": "string",
277            "type": "string"
278          }
279        ]
280      }
281    ]
282  },
283  "behavioral_rules": [
284    {
285      "description": "string",
286      "type": "string",
287      "config": {}
288    }
289  ]
290}
291
292Be specific and extract all details mentioned in the command."#;
293
294        // Build user prompt with the command
295        let user_prompt = format!(
296            "Parse this workspace scenario description and extract all requirements:\n\n{}",
297            command
298        );
299
300        // Create LLM request
301        let llm_request = LlmGenerationRequest {
302            system_prompt: system_prompt.to_string(),
303            user_prompt,
304            temperature: 0.3,
305            max_tokens: 3000,
306            schema: None,
307        };
308
309        // Generate response from LLM
310        let response = self.llm_client.generate(&llm_request).await?;
311
312        // Parse the response into ParsedWorkspaceScenario
313        let response_str = serde_json::to_string(&response).unwrap_or_default();
314        let parsed: ParsedWorkspaceScenario = serde_json::from_value(response).map_err(|e| {
315            crate::Error::generic(format!(
316                "Failed to parse LLM response as ParsedWorkspaceScenario: {}. Response: {}",
317                e, response_str
318            ))
319        })?;
320
321        Ok(parsed)
322    }
323
324    /// Parse a workspace creation command
325    ///
326    /// This method extracts information about creating a complete workspace including:
327    /// - Workspace name and description
328    /// - Entities (customers, orders, payments, etc.)
329    /// - Personas with relationships
330    /// - Behavioral scenarios (happy path, failure, slow path)
331    /// - Reality continuum preferences
332    /// - Drift budget preferences
333    pub async fn parse_workspace_creation_command(
334        &self,
335        command: &str,
336    ) -> Result<ParsedWorkspaceCreation> {
337        // Build system prompt for workspace creation parsing
338        let system_prompt = r#"You are an expert at parsing natural language descriptions of workspace creation
339and extracting structured information for creating complete mock backends with personas, scenarios, and configuration.
340
341Extract the following information from the command:
3421. Workspace name and description
3432. Entities (customers, orders, payments, products, etc.)
3443. Personas with their traits and relationships (e.g., customer owns orders)
3454. Behavioral scenarios:
346   - Happy path scenarios (successful flows)
347   - Failure path scenarios (error cases)
348   - Slow path scenarios (latency/performance issues)
3495. Reality continuum preferences (e.g., "80% mock, 20% real prod for catalog only")
3506. Drift budget preferences (e.g., "strict drift budget", "moderate tolerance")
351
352Return your response as a JSON object with this structure:
353{
354  "workspace_name": "string (e.g., e-commerce-workspace)",
355  "workspace_description": "string",
356  "entities": [
357    {
358      "name": "string (e.g., Customer, Order, Payment)",
359      "description": "string",
360      "endpoints": [
361        {
362          "path": "string",
363          "method": "string",
364          "description": "string"
365        }
366      ],
367      "fields": [
368        {
369          "name": "string",
370          "type": "string",
371          "description": "string"
372        }
373      ]
374    }
375  ],
376  "personas": [
377    {
378      "name": "string (e.g., premium-customer, regular-customer)",
379      "description": "string",
380      "traits": {
381        "trait_name": "trait_value"
382      },
383      "relationships": [
384        {
385          "type": "string (e.g., owns, belongs_to, has)",
386          "target_entity": "string (e.g., Order, Payment)"
387        }
388      ]
389    }
390  ],
391  "scenarios": [
392    {
393      "name": "string (e.g., happy-path-checkout, failed-payment, slow-shipping)",
394      "type": "string (happy_path|failure|slow_path)",
395      "description": "string",
396      "steps": [
397        {
398          "description": "string (e.g., Create order, Process payment)",
399          "endpoint": "string (e.g., POST /api/orders)",
400          "expected_outcome": "string"
401        }
402      ]
403    }
404  ],
405  "reality_continuum": {
406    "default_ratio": 0.0-1.0 (0.0 = 100% mock, 1.0 = 100% real),
407    "route_rules": [
408      {
409        "pattern": "string (e.g., /api/catalog/*)",
410        "ratio": 0.0-1.0,
411        "description": "string"
412      }
413    ],
414    "transition_mode": "string (manual|time_based|scheduled)"
415  },
416  "drift_budget": {
417    "strictness": "string (strict|moderate|lenient)",
418    "max_breaking_changes": number,
419    "max_non_breaking_changes": number,
420    "description": "string"
421  }
422}
423
424Be specific and extract all details mentioned in the command. Ensure at least 2-3 endpoints per entity,
4252-3 personas with relationships, and 2-3 behavioral scenarios."#;
426
427        // Build user prompt with the command
428        let user_prompt = format!(
429            "Parse this workspace creation command and extract all requirements:\n\n{}",
430            command
431        );
432
433        // Create LLM request
434        let llm_request = LlmGenerationRequest {
435            system_prompt: system_prompt.to_string(),
436            user_prompt,
437            temperature: 0.3,
438            max_tokens: 4000,
439            schema: None,
440        };
441
442        // Generate response from LLM
443        let response = self.llm_client.generate(&llm_request).await?;
444
445        // Parse the response into ParsedWorkspaceCreation
446        let response_str = serde_json::to_string(&response).unwrap_or_default();
447        let parsed: ParsedWorkspaceCreation = serde_json::from_value(response).map_err(|e| {
448            crate::Error::generic(format!(
449                "Failed to parse LLM response as ParsedWorkspaceCreation: {}. Response: {}",
450                e, response_str
451            ))
452        })?;
453
454        Ok(parsed)
455    }
456
457    /// Parse a reality continuum configuration command
458    ///
459    /// This method extracts reality continuum preferences from natural language,
460    /// such as "80% mock, 20% real prod for catalog only".
461    pub async fn parse_reality_continuum_command(
462        &self,
463        command: &str,
464    ) -> Result<ParsedRealityContinuum> {
465        // Build system prompt for reality continuum parsing
466        let system_prompt = r#"You are an expert at parsing natural language descriptions of reality continuum
467configuration and extracting structured blend ratio settings.
468
469Extract the following information from the command:
4701. Default blend ratio (e.g., "80% mock, 20% real" means ratio 0.2)
4712. Route-specific rules (e.g., "catalog only", "for /api/products/*")
4723. Transition mode preferences (manual, time-based, scheduled)
473
474Return your response as a JSON object with this structure:
475{
476  "default_ratio": 0.0-1.0 (0.0 = 100% mock, 1.0 = 100% real),
477  "enabled": true/false,
478  "route_rules": [
479    {
480      "pattern": "string (e.g., /api/catalog/*, /api/products/*)",
481      "ratio": 0.0-1.0,
482      "description": "string"
483    }
484  ],
485  "transition_mode": "string (manual|time_based|scheduled)",
486  "merge_strategy": "string (field_level|weighted|body_blend)"
487}
488
489Examples:
490- "80% mock, 20% real" → default_ratio: 0.2
491- "Make catalog 50% real" → route_rules: [{pattern: "/api/catalog/*", ratio: 0.5}]
492- "100% mock for now" → default_ratio: 0.0, enabled: true"#;
493
494        // Build user prompt with the command
495        let user_prompt =
496            format!("Parse this reality continuum configuration command:\n\n{}", command);
497
498        // Create LLM request
499        let llm_request = LlmGenerationRequest {
500            system_prompt: system_prompt.to_string(),
501            user_prompt,
502            temperature: 0.3,
503            max_tokens: 2000,
504            schema: None,
505        };
506
507        // Generate response from LLM
508        let response = self.llm_client.generate(&llm_request).await?;
509
510        // Parse the response into ParsedRealityContinuum
511        let response_str = serde_json::to_string(&response).unwrap_or_default();
512        let parsed: ParsedRealityContinuum = serde_json::from_value(response).map_err(|e| {
513            crate::Error::generic(format!(
514                "Failed to parse LLM response as ParsedRealityContinuum: {}. Response: {}",
515                e, response_str
516            ))
517        })?;
518
519        Ok(parsed)
520    }
521
522    /// Parse a drift budget configuration command
523    ///
524    /// This method extracts drift budget preferences from natural language,
525    /// such as "strict drift budget" or "moderate tolerance for changes".
526    pub async fn parse_drift_budget_command(&self, command: &str) -> Result<ParsedDriftBudget> {
527        // Build system prompt for drift budget parsing
528        let system_prompt = r#"You are an expert at parsing natural language descriptions of drift budget
529configuration and extracting structured budget settings.
530
531Extract the following information from the command:
5321. Strictness level (strict, moderate, lenient)
5332. Breaking change tolerance
5343. Non-breaking change tolerance
5354. Per-service/endpoint preferences
536
537Return your response as a JSON object with this structure:
538{
539  "strictness": "string (strict|moderate|lenient)",
540  "enabled": true/false,
541  "max_breaking_changes": number (0 for strict, higher for lenient),
542  "max_non_breaking_changes": number,
543  "max_field_churn_percent": number (0.0-100.0, optional),
544  "time_window_days": number (optional, for percentage-based budgets),
545  "per_service_budgets": {
546    "service_name": {
547      "max_breaking_changes": number,
548      "max_non_breaking_changes": number
549    }
550  },
551  "description": "string"
552}
553
554Examples:
555- "strict drift budget" → strictness: "strict", max_breaking_changes: 0, max_non_breaking_changes: 5
556- "moderate tolerance" → strictness: "moderate", max_breaking_changes: 1, max_non_breaking_changes: 10
557- "lenient, allow up to 5 breaking changes" → strictness: "lenient", max_breaking_changes: 5"#;
558
559        // Build user prompt with the command
560        let user_prompt = format!("Parse this drift budget configuration command:\n\n{}", command);
561
562        // Create LLM request
563        let llm_request = LlmGenerationRequest {
564            system_prompt: system_prompt.to_string(),
565            user_prompt,
566            temperature: 0.3,
567            max_tokens: 2000,
568            schema: None,
569        };
570
571        // Generate response from LLM
572        let response = self.llm_client.generate(&llm_request).await?;
573
574        // Parse the response into ParsedDriftBudget
575        let response_str = serde_json::to_string(&response).unwrap_or_default();
576        let parsed: ParsedDriftBudget = serde_json::from_value(response).map_err(|e| {
577            crate::Error::generic(format!(
578                "Failed to parse LLM response as ParsedDriftBudget: {}. Response: {}",
579                e, response_str
580            ))
581        })?;
582
583        Ok(parsed)
584    }
585}
586
587/// Parsed command structure containing extracted API requirements
588#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct ParsedCommand {
590    /// API type/category
591    pub api_type: String,
592    /// API title
593    pub title: String,
594    /// API description
595    pub description: String,
596    /// List of endpoints
597    pub endpoints: Vec<EndpointRequirement>,
598    /// List of data models
599    pub models: Vec<ModelRequirement>,
600    /// Relationships between models
601    #[serde(default)]
602    pub relationships: Vec<RelationshipRequirement>,
603    /// Sample data counts per model
604    #[serde(default)]
605    pub sample_counts: HashMap<String, usize>,
606    /// Business flows
607    #[serde(default)]
608    pub flows: Vec<FlowRequirement>,
609}
610
611/// Endpoint requirement extracted from command
612#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct EndpointRequirement {
614    /// Path (e.g., /api/products)
615    pub path: String,
616    /// HTTP method
617    pub method: String,
618    /// Description
619    pub description: String,
620    /// Request body schema (if applicable)
621    #[serde(default)]
622    pub request_body: Option<RequestBodyRequirement>,
623    /// Response schema
624    #[serde(default)]
625    pub response: Option<ResponseRequirement>,
626}
627
628/// Request body requirement
629#[derive(Debug, Clone, Serialize, Deserialize)]
630pub struct RequestBodyRequirement {
631    /// Schema definition
632    #[serde(default)]
633    pub schema: Option<serde_json::Value>,
634    /// Required fields
635    #[serde(default)]
636    pub required: Vec<String>,
637}
638
639/// Response requirement
640#[derive(Debug, Clone, Serialize, Deserialize)]
641pub struct ResponseRequirement {
642    /// HTTP status code
643    #[serde(default = "default_status")]
644    pub status: u16,
645    /// Response schema
646    #[serde(default)]
647    pub schema: Option<serde_json::Value>,
648    /// Whether response is an array
649    #[serde(default)]
650    pub is_array: bool,
651    /// Count of items (if specified)
652    #[serde(default)]
653    pub count: Option<usize>,
654}
655
656fn default_status() -> u16 {
657    200
658}
659
660/// Model requirement extracted from command
661#[derive(Debug, Clone, Serialize, Deserialize)]
662pub struct ModelRequirement {
663    /// Model name
664    pub name: String,
665    /// List of fields
666    pub fields: Vec<FieldRequirement>,
667}
668
669/// Field requirement
670#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct FieldRequirement {
672    /// Field name
673    pub name: String,
674    /// Field type
675    pub r#type: String,
676    /// Field description
677    #[serde(default)]
678    pub description: String,
679    /// Whether field is required
680    #[serde(default = "default_true")]
681    pub required: bool,
682}
683
684fn default_true() -> bool {
685    true
686}
687
688/// Relationship requirement
689#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct RelationshipRequirement {
691    /// Source model
692    pub from: String,
693    /// Target model
694    pub to: String,
695    /// Relationship type
696    pub r#type: String,
697}
698
699/// Flow requirement
700#[derive(Debug, Clone, Serialize, Deserialize)]
701pub struct FlowRequirement {
702    /// Flow name
703    pub name: String,
704    /// Flow description
705    pub description: String,
706    /// Steps in the flow
707    #[serde(default)]
708    pub steps: Vec<String>,
709}
710
711/// Alias for API requirement (for backwards compatibility)
712pub type ApiRequirement = ParsedCommand;
713
714/// Parsed workspace scenario structure
715#[derive(Debug, Clone, Serialize, Deserialize)]
716pub struct ParsedWorkspaceScenario {
717    /// Domain/industry
718    pub domain: String,
719    /// Workspace title
720    pub title: String,
721    /// Workspace description
722    pub description: String,
723    /// Chaos characteristics
724    #[serde(default)]
725    pub chaos_characteristics: Vec<ChaosCharacteristic>,
726    /// Initial data requirements
727    #[serde(default)]
728    pub initial_data: InitialDataRequirements,
729    /// API requirements
730    #[serde(default)]
731    pub api_requirements: ApiRequirements,
732    /// Behavioral rules
733    #[serde(default)]
734    pub behavioral_rules: Vec<BehavioralRule>,
735}
736
737/// Chaos characteristic
738#[derive(Debug, Clone, Serialize, Deserialize)]
739pub struct ChaosCharacteristic {
740    /// Type of chaos (latency, failure, rate_limit, etc.)
741    pub r#type: String,
742    /// Description
743    pub description: String,
744    /// Configuration details
745    #[serde(default)]
746    pub config: serde_json::Value,
747}
748
749/// Initial data requirements
750#[derive(Debug, Clone, Serialize, Deserialize, Default)]
751pub struct InitialDataRequirements {
752    /// Number of users
753    #[serde(default)]
754    pub users: Option<usize>,
755    /// Number of disputes
756    #[serde(default)]
757    pub disputes: Option<usize>,
758    /// Number of orders
759    #[serde(default)]
760    pub orders: Option<usize>,
761    /// Custom entity counts
762    #[serde(default)]
763    pub custom: HashMap<String, usize>,
764}
765
766/// API requirements for the scenario
767#[derive(Debug, Clone, Serialize, Deserialize, Default)]
768pub struct ApiRequirements {
769    /// List of endpoints
770    #[serde(default)]
771    pub endpoints: Vec<EndpointRequirement>,
772    /// List of models
773    #[serde(default)]
774    pub models: Vec<ModelRequirement>,
775}
776
777/// Behavioral rule
778#[derive(Debug, Clone, Serialize, Deserialize)]
779pub struct BehavioralRule {
780    /// Rule description
781    pub description: String,
782    /// Rule type
783    pub r#type: String,
784    /// Rule configuration
785    #[serde(default)]
786    pub config: serde_json::Value,
787}
788
789/// Parsed workspace creation structure
790#[derive(Debug, Clone, Serialize, Deserialize)]
791pub struct ParsedWorkspaceCreation {
792    /// Workspace name
793    pub workspace_name: String,
794    /// Workspace description
795    pub workspace_description: String,
796    /// List of entities
797    #[serde(default)]
798    pub entities: Vec<EntityRequirement>,
799    /// List of personas
800    #[serde(default)]
801    pub personas: Vec<PersonaRequirement>,
802    /// List of behavioral scenarios
803    #[serde(default)]
804    pub scenarios: Vec<ScenarioRequirement>,
805    /// Reality continuum preferences
806    #[serde(default)]
807    pub reality_continuum: Option<ParsedRealityContinuum>,
808    /// Drift budget preferences
809    #[serde(default)]
810    pub drift_budget: Option<ParsedDriftBudget>,
811}
812
813/// Entity requirement for workspace creation
814#[derive(Debug, Clone, Serialize, Deserialize)]
815pub struct EntityRequirement {
816    /// Entity name (e.g., Customer, Order, Payment)
817    pub name: String,
818    /// Entity description
819    pub description: String,
820    /// Endpoints for this entity
821    #[serde(default)]
822    pub endpoints: Vec<EntityEndpointRequirement>,
823    /// Fields for this entity
824    #[serde(default)]
825    pub fields: Vec<FieldRequirement>,
826}
827
828/// Endpoint requirement for an entity
829#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct EntityEndpointRequirement {
831    /// Path (e.g., /api/customers)
832    pub path: String,
833    /// HTTP method
834    pub method: String,
835    /// Description
836    pub description: String,
837}
838
839/// Persona requirement for workspace creation
840#[derive(Debug, Clone, Serialize, Deserialize)]
841pub struct PersonaRequirement {
842    /// Persona name (e.g., premium-customer, regular-customer)
843    pub name: String,
844    /// Persona description
845    pub description: String,
846    /// Persona traits
847    #[serde(default)]
848    pub traits: HashMap<String, String>,
849    /// Relationships to other entities
850    #[serde(default)]
851    pub relationships: Vec<PersonaRelationship>,
852}
853
854/// Persona relationship
855#[derive(Debug, Clone, Serialize, Deserialize)]
856pub struct PersonaRelationship {
857    /// Relationship type (e.g., owns, belongs_to, has)
858    pub r#type: String,
859    /// Target entity name
860    pub target_entity: String,
861}
862
863/// Scenario requirement for workspace creation
864#[derive(Debug, Clone, Serialize, Deserialize)]
865pub struct ScenarioRequirement {
866    /// Scenario name (e.g., happy-path-checkout, failed-payment)
867    pub name: String,
868    /// Scenario type (happy_path, failure, slow_path)
869    pub r#type: String,
870    /// Scenario description
871    pub description: String,
872    /// Steps in the scenario
873    #[serde(default)]
874    pub steps: Vec<ScenarioStepRequirement>,
875}
876
877/// Scenario step requirement
878#[derive(Debug, Clone, Serialize, Deserialize)]
879pub struct ScenarioStepRequirement {
880    /// Step description
881    pub description: String,
882    /// Endpoint for this step (e.g., POST /api/orders)
883    pub endpoint: String,
884    /// Expected outcome
885    pub expected_outcome: String,
886}
887
888/// Parsed reality continuum configuration
889#[derive(Debug, Clone, Serialize, Deserialize)]
890pub struct ParsedRealityContinuum {
891    /// Default blend ratio (0.0 = 100% mock, 1.0 = 100% real)
892    #[serde(default = "default_blend_ratio")]
893    pub default_ratio: f64,
894    /// Whether reality continuum is enabled
895    #[serde(default = "default_true")]
896    pub enabled: bool,
897    /// Route-specific rules
898    #[serde(default)]
899    pub route_rules: Vec<ParsedContinuumRule>,
900    /// Transition mode
901    #[serde(default)]
902    pub transition_mode: String,
903    /// Merge strategy
904    #[serde(default)]
905    pub merge_strategy: String,
906}
907
908fn default_blend_ratio() -> f64 {
909    0.0
910}
911
912/// Parsed continuum rule
913#[derive(Debug, Clone, Serialize, Deserialize)]
914pub struct ParsedContinuumRule {
915    /// Path pattern (e.g., /api/catalog/*)
916    pub pattern: String,
917    /// Blend ratio for this route
918    pub ratio: f64,
919    /// Description
920    #[serde(default)]
921    pub description: String,
922}
923
924/// Parsed drift budget configuration
925#[derive(Debug, Clone, Serialize, Deserialize)]
926pub struct ParsedDriftBudget {
927    /// Strictness level (strict, moderate, lenient)
928    pub strictness: String,
929    /// Whether drift budget is enabled
930    #[serde(default = "default_true")]
931    pub enabled: bool,
932    /// Maximum breaking changes allowed
933    #[serde(default)]
934    pub max_breaking_changes: u32,
935    /// Maximum non-breaking changes allowed
936    #[serde(default)]
937    pub max_non_breaking_changes: u32,
938    /// Maximum field churn percentage (optional)
939    #[serde(default, skip_serializing_if = "Option::is_none")]
940    pub max_field_churn_percent: Option<f64>,
941    /// Time window in days (optional)
942    #[serde(default, skip_serializing_if = "Option::is_none")]
943    pub time_window_days: Option<u32>,
944    /// Per-service budgets
945    #[serde(default)]
946    pub per_service_budgets: HashMap<String, ParsedServiceBudget>,
947    /// Description
948    #[serde(default)]
949    pub description: String,
950}
951
952/// Parsed service budget
953#[derive(Debug, Clone, Serialize, Deserialize)]
954pub struct ParsedServiceBudget {
955    /// Maximum breaking changes for this service
956    #[serde(default)]
957    pub max_breaking_changes: u32,
958    /// Maximum non-breaking changes for this service
959    #[serde(default)]
960    pub max_non_breaking_changes: u32,
961}
962
963#[cfg(test)]
964mod tests {
965    use super::*;
966    use crate::intelligent_behavior::config::IntelligentBehaviorConfig;
967    use serde_json::json;
968
969    #[test]
970    fn test_voice_command_parser_new() {
971        let config = IntelligentBehaviorConfig::default();
972        let parser = VoiceCommandParser::new(config);
973        // Just verify it doesn't panic and creates the parser
974        // The llm_client is private, so we can't directly test it
975    }
976
977    #[test]
978    fn test_parsed_command_creation() {
979        let command = ParsedCommand {
980            api_type: "e-commerce".to_string(),
981            title: "Shop API".to_string(),
982            description: "An e-commerce API".to_string(),
983            endpoints: vec![],
984            models: vec![],
985            relationships: vec![],
986            sample_counts: HashMap::new(),
987            flows: vec![],
988        };
989
990        assert_eq!(command.api_type, "e-commerce");
991        assert_eq!(command.title, "Shop API");
992        assert_eq!(command.description, "An e-commerce API");
993    }
994
995    #[test]
996    fn test_endpoint_requirement_creation() {
997        let endpoint = EndpointRequirement {
998            path: "/api/products".to_string(),
999            method: "GET".to_string(),
1000            description: "Get all products".to_string(),
1001            request_body: None,
1002            response: None,
1003        };
1004
1005        assert_eq!(endpoint.path, "/api/products");
1006        assert_eq!(endpoint.method, "GET");
1007        assert_eq!(endpoint.description, "Get all products");
1008    }
1009
1010    #[test]
1011    fn test_endpoint_requirement_with_body() {
1012        let request_body = RequestBodyRequirement {
1013            schema: Some(json!({"type": "object"})),
1014            required: vec!["name".to_string(), "price".to_string()],
1015        };
1016
1017        let response = ResponseRequirement {
1018            status: 201,
1019            schema: Some(json!({"type": "object"})),
1020            is_array: false,
1021            count: None,
1022        };
1023
1024        let endpoint = EndpointRequirement {
1025            path: "/api/products".to_string(),
1026            method: "POST".to_string(),
1027            description: "Create a product".to_string(),
1028            request_body: Some(request_body),
1029            response: Some(response),
1030        };
1031
1032        assert!(endpoint.request_body.is_some());
1033        assert!(endpoint.response.is_some());
1034        assert_eq!(endpoint.response.unwrap().status, 201);
1035    }
1036
1037    #[test]
1038    fn test_request_body_requirement_creation() {
1039        let body = RequestBodyRequirement {
1040            schema: Some(json!({"type": "object", "properties": {"name": {"type": "string"}}})),
1041            required: vec!["name".to_string()],
1042        };
1043
1044        assert!(body.schema.is_some());
1045        assert_eq!(body.required.len(), 1);
1046    }
1047
1048    #[test]
1049    fn test_response_requirement_creation() {
1050        let response = ResponseRequirement {
1051            status: 200,
1052            schema: Some(json!({"type": "array", "items": {"type": "object"}})),
1053            is_array: true,
1054            count: Some(10),
1055        };
1056
1057        assert_eq!(response.status, 200);
1058        assert!(response.is_array);
1059        assert_eq!(response.count, Some(10));
1060    }
1061
1062    #[test]
1063    fn test_response_requirement_default_status() {
1064        let response = ResponseRequirement {
1065            status: default_status(),
1066            schema: None,
1067            is_array: false,
1068            count: None,
1069        };
1070
1071        assert_eq!(response.status, 200);
1072    }
1073
1074    #[test]
1075    fn test_model_requirement_creation() {
1076        let field = FieldRequirement {
1077            name: "id".to_string(),
1078            r#type: "integer".to_string(),
1079            description: "Product ID".to_string(),
1080            required: true,
1081        };
1082
1083        let model = ModelRequirement {
1084            name: "Product".to_string(),
1085            fields: vec![field],
1086        };
1087
1088        assert_eq!(model.name, "Product");
1089        assert_eq!(model.fields.len(), 1);
1090        assert_eq!(model.fields[0].name, "id");
1091    }
1092
1093    #[test]
1094    fn test_field_requirement_creation() {
1095        let field = FieldRequirement {
1096            name: "name".to_string(),
1097            r#type: "string".to_string(),
1098            description: "Product name".to_string(),
1099            required: true,
1100        };
1101
1102        assert_eq!(field.name, "name");
1103        assert_eq!(field.r#type, "string");
1104        assert!(field.required);
1105    }
1106
1107    #[test]
1108    fn test_field_requirement_default_required() {
1109        let field = FieldRequirement {
1110            name: "optional_field".to_string(),
1111            r#type: "string".to_string(),
1112            description: "".to_string(),
1113            required: default_true(),
1114        };
1115
1116        assert!(field.required);
1117    }
1118
1119    #[test]
1120    fn test_relationship_requirement_creation() {
1121        let relationship = RelationshipRequirement {
1122            from: "Product".to_string(),
1123            to: "Category".to_string(),
1124            r#type: "many-to-one".to_string(),
1125        };
1126
1127        assert_eq!(relationship.from, "Product");
1128        assert_eq!(relationship.to, "Category");
1129        assert_eq!(relationship.r#type, "many-to-one");
1130    }
1131
1132    #[test]
1133    fn test_flow_requirement_creation() {
1134        let flow = FlowRequirement {
1135            name: "checkout".to_string(),
1136            description: "Checkout flow".to_string(),
1137            steps: vec!["Add to cart".to_string(), "Payment".to_string()],
1138        };
1139
1140        assert_eq!(flow.name, "checkout");
1141        assert_eq!(flow.steps.len(), 2);
1142    }
1143
1144    #[test]
1145    fn test_parsed_workspace_scenario_creation() {
1146        let scenario = ParsedWorkspaceScenario {
1147            domain: "e-commerce".to_string(),
1148            title: "Shop Workspace".to_string(),
1149            description: "E-commerce workspace".to_string(),
1150            chaos_characteristics: vec![],
1151            initial_data: InitialDataRequirements::default(),
1152            api_requirements: ApiRequirements::default(),
1153            behavioral_rules: vec![],
1154        };
1155
1156        assert_eq!(scenario.domain, "e-commerce");
1157        assert_eq!(scenario.title, "Shop Workspace");
1158    }
1159
1160    #[test]
1161    fn test_chaos_characteristic_creation() {
1162        let chaos = ChaosCharacteristic {
1163            r#type: "latency".to_string(),
1164            description: "High latency on checkout".to_string(),
1165            config: json!({"delay_ms": 1000}),
1166        };
1167
1168        assert_eq!(chaos.r#type, "latency");
1169        assert_eq!(chaos.description, "High latency on checkout");
1170    }
1171
1172    #[test]
1173    fn test_initial_data_requirements_creation() {
1174        let mut custom = HashMap::new();
1175        custom.insert("products".to_string(), 50);
1176
1177        let data = InitialDataRequirements {
1178            users: Some(100),
1179            disputes: Some(5),
1180            orders: Some(200),
1181            custom,
1182        };
1183
1184        assert_eq!(data.users, Some(100));
1185        assert_eq!(data.disputes, Some(5));
1186        assert_eq!(data.orders, Some(200));
1187        assert_eq!(data.custom.get("products"), Some(&50));
1188    }
1189
1190    #[test]
1191    fn test_initial_data_requirements_default() {
1192        let data = InitialDataRequirements::default();
1193        assert!(data.users.is_none());
1194        assert!(data.disputes.is_none());
1195        assert!(data.orders.is_none());
1196        assert!(data.custom.is_empty());
1197    }
1198
1199    #[test]
1200    fn test_api_requirements_creation() {
1201        let endpoint = EndpointRequirement {
1202            path: "/api/products".to_string(),
1203            method: "GET".to_string(),
1204            description: "Get products".to_string(),
1205            request_body: None,
1206            response: None,
1207        };
1208
1209        let model = ModelRequirement {
1210            name: "Product".to_string(),
1211            fields: vec![],
1212        };
1213
1214        let api_req = ApiRequirements {
1215            endpoints: vec![endpoint],
1216            models: vec![model],
1217        };
1218
1219        assert_eq!(api_req.endpoints.len(), 1);
1220        assert_eq!(api_req.models.len(), 1);
1221    }
1222
1223    #[test]
1224    fn test_api_requirements_default() {
1225        let api_req = ApiRequirements::default();
1226        assert!(api_req.endpoints.is_empty());
1227        assert!(api_req.models.is_empty());
1228    }
1229
1230    #[test]
1231    fn test_behavioral_rule_creation() {
1232        let rule = BehavioralRule {
1233            description: "Slow response on checkout".to_string(),
1234            r#type: "latency".to_string(),
1235            config: json!({"delay_ms": 2000}),
1236        };
1237
1238        assert_eq!(rule.description, "Slow response on checkout");
1239        assert_eq!(rule.r#type, "latency");
1240    }
1241
1242    #[test]
1243    fn test_parsed_workspace_creation_creation() {
1244        let creation = ParsedWorkspaceCreation {
1245            workspace_name: "New Workspace".to_string(),
1246            workspace_description: "A new workspace".to_string(),
1247            entities: vec![],
1248            personas: vec![],
1249            scenarios: vec![],
1250            reality_continuum: None,
1251            drift_budget: None,
1252        };
1253
1254        assert_eq!(creation.workspace_name, "New Workspace");
1255        assert_eq!(creation.workspace_description, "A new workspace");
1256        assert!(creation.entities.is_empty());
1257    }
1258
1259    #[test]
1260    fn test_entity_requirement_creation() {
1261        let entity = EntityRequirement {
1262            name: "Product".to_string(),
1263            description: "Product entity".to_string(),
1264            endpoints: vec![],
1265            fields: vec![],
1266        };
1267
1268        assert_eq!(entity.name, "Product");
1269        assert_eq!(entity.description, "Product entity");
1270        assert!(entity.fields.is_empty());
1271    }
1272
1273    #[test]
1274    fn test_entity_endpoint_requirement_creation() {
1275        let endpoint = EntityEndpointRequirement {
1276            path: "/api/products".to_string(),
1277            method: "GET".to_string(),
1278            description: "Get products".to_string(),
1279        };
1280
1281        assert_eq!(endpoint.path, "/api/products");
1282        assert_eq!(endpoint.method, "GET");
1283    }
1284
1285    #[test]
1286    fn test_persona_requirement_creation() {
1287        let persona = PersonaRequirement {
1288            name: "Customer".to_string(),
1289            description: "Regular customer".to_string(),
1290            traits: HashMap::new(),
1291            relationships: vec![],
1292        };
1293
1294        assert_eq!(persona.name, "Customer");
1295        assert_eq!(persona.description, "Regular customer");
1296        assert!(persona.traits.is_empty());
1297    }
1298
1299    #[test]
1300    fn test_persona_relationship_creation() {
1301        let relationship = PersonaRelationship {
1302            r#type: "one-to-many".to_string(),
1303            target_entity: "Order".to_string(),
1304        };
1305
1306        assert_eq!(relationship.r#type, "one-to-many");
1307        assert_eq!(relationship.target_entity, "Order");
1308    }
1309
1310    #[test]
1311    fn test_parsed_reality_continuum_creation() {
1312        let continuum = ParsedRealityContinuum {
1313            default_ratio: 0.2,
1314            enabled: true,
1315            route_rules: vec![],
1316            transition_mode: "manual".to_string(),
1317            merge_strategy: "field_level".to_string(),
1318        };
1319
1320        assert_eq!(continuum.default_ratio, 0.2);
1321        assert!(continuum.enabled);
1322        assert_eq!(continuum.transition_mode, "manual");
1323        assert_eq!(continuum.merge_strategy, "field_level");
1324    }
1325
1326    #[test]
1327    fn test_parsed_continuum_rule_creation() {
1328        let rule = ParsedContinuumRule {
1329            pattern: "/api/catalog/*".to_string(),
1330            ratio: 0.5,
1331            description: "Catalog route".to_string(),
1332        };
1333
1334        assert_eq!(rule.pattern, "/api/catalog/*");
1335        assert_eq!(rule.ratio, 0.5);
1336    }
1337
1338    #[test]
1339    fn test_parsed_drift_budget_creation() {
1340        let mut per_service_budgets = HashMap::new();
1341        per_service_budgets.insert(
1342            "catalog".to_string(),
1343            ParsedServiceBudget {
1344                max_breaking_changes: 5,
1345                max_non_breaking_changes: 20,
1346            },
1347        );
1348
1349        let budget = ParsedDriftBudget {
1350            strictness: "moderate".to_string(),
1351            enabled: true,
1352            max_breaking_changes: 10,
1353            max_non_breaking_changes: 50,
1354            max_field_churn_percent: Some(5.0),
1355            time_window_days: Some(30),
1356            per_service_budgets,
1357            description: "Drift budget config".to_string(),
1358        };
1359
1360        assert_eq!(budget.strictness, "moderate");
1361        assert!(budget.enabled);
1362        assert_eq!(budget.max_breaking_changes, 10);
1363        assert_eq!(budget.max_non_breaking_changes, 50);
1364        assert_eq!(budget.per_service_budgets.len(), 1);
1365    }
1366
1367    #[test]
1368    fn test_parsed_service_budget_creation() {
1369        let budget = ParsedServiceBudget {
1370            max_breaking_changes: 3,
1371            max_non_breaking_changes: 15,
1372        };
1373
1374        assert_eq!(budget.max_breaking_changes, 3);
1375        assert_eq!(budget.max_non_breaking_changes, 15);
1376    }
1377
1378    #[test]
1379    fn test_parsed_command_clone() {
1380        let command1 = ParsedCommand {
1381            api_type: "test".to_string(),
1382            title: "Test API".to_string(),
1383            description: "Test".to_string(),
1384            endpoints: vec![],
1385            models: vec![],
1386            relationships: vec![],
1387            sample_counts: HashMap::new(),
1388            flows: vec![],
1389        };
1390        let command2 = command1.clone();
1391        assert_eq!(command1.api_type, command2.api_type);
1392    }
1393
1394    #[test]
1395    fn test_parsed_command_debug() {
1396        let command = ParsedCommand {
1397            api_type: "test".to_string(),
1398            title: "Test".to_string(),
1399            description: "Test".to_string(),
1400            endpoints: vec![],
1401            models: vec![],
1402            relationships: vec![],
1403            sample_counts: HashMap::new(),
1404            flows: vec![],
1405        };
1406        let debug_str = format!("{:?}", command);
1407        assert!(debug_str.contains("ParsedCommand"));
1408    }
1409
1410    #[test]
1411    fn test_endpoint_requirement_clone() {
1412        let endpoint1 = EndpointRequirement {
1413            path: "/test".to_string(),
1414            method: "GET".to_string(),
1415            description: "Test".to_string(),
1416            request_body: None,
1417            response: None,
1418        };
1419        let endpoint2 = endpoint1.clone();
1420        assert_eq!(endpoint1.path, endpoint2.path);
1421    }
1422
1423    #[test]
1424    fn test_endpoint_requirement_debug() {
1425        let endpoint = EndpointRequirement {
1426            path: "/test".to_string(),
1427            method: "POST".to_string(),
1428            description: "Test".to_string(),
1429            request_body: None,
1430            response: None,
1431        };
1432        let debug_str = format!("{:?}", endpoint);
1433        assert!(debug_str.contains("EndpointRequirement"));
1434    }
1435
1436    #[test]
1437    fn test_request_body_requirement_clone() {
1438        let body1 = RequestBodyRequirement {
1439            schema: None,
1440            required: vec!["field".to_string()],
1441        };
1442        let body2 = body1.clone();
1443        assert_eq!(body1.required, body2.required);
1444    }
1445
1446    #[test]
1447    fn test_request_body_requirement_debug() {
1448        let body = RequestBodyRequirement {
1449            schema: Some(json!({})),
1450            required: vec![],
1451        };
1452        let debug_str = format!("{:?}", body);
1453        assert!(debug_str.contains("RequestBodyRequirement"));
1454    }
1455
1456    #[test]
1457    fn test_response_requirement_clone() {
1458        let response1 = ResponseRequirement {
1459            status: 200,
1460            schema: None,
1461            is_array: false,
1462            count: None,
1463        };
1464        let response2 = response1.clone();
1465        assert_eq!(response1.status, response2.status);
1466    }
1467
1468    #[test]
1469    fn test_response_requirement_debug() {
1470        let response = ResponseRequirement {
1471            status: 201,
1472            schema: Some(json!({})),
1473            is_array: true,
1474            count: Some(10),
1475        };
1476        let debug_str = format!("{:?}", response);
1477        assert!(debug_str.contains("ResponseRequirement"));
1478    }
1479
1480    #[test]
1481    fn test_model_requirement_clone() {
1482        let model1 = ModelRequirement {
1483            name: "User".to_string(),
1484            fields: vec![],
1485        };
1486        let model2 = model1.clone();
1487        assert_eq!(model1.name, model2.name);
1488    }
1489
1490    #[test]
1491    fn test_model_requirement_debug() {
1492        let model = ModelRequirement {
1493            name: "Product".to_string(),
1494            fields: vec![],
1495        };
1496        let debug_str = format!("{:?}", model);
1497        assert!(debug_str.contains("ModelRequirement"));
1498    }
1499
1500    #[test]
1501    fn test_field_requirement_clone() {
1502        let field1 = FieldRequirement {
1503            name: "id".to_string(),
1504            r#type: "integer".to_string(),
1505            description: "ID".to_string(),
1506            required: true,
1507        };
1508        let field2 = field1.clone();
1509        assert_eq!(field1.name, field2.name);
1510    }
1511
1512    #[test]
1513    fn test_field_requirement_debug() {
1514        let field = FieldRequirement {
1515            name: "name".to_string(),
1516            r#type: "string".to_string(),
1517            description: "Name".to_string(),
1518            required: false,
1519        };
1520        let debug_str = format!("{:?}", field);
1521        assert!(debug_str.contains("FieldRequirement"));
1522    }
1523
1524    #[test]
1525    fn test_relationship_requirement_clone() {
1526        let rel1 = RelationshipRequirement {
1527            from: "User".to_string(),
1528            to: "Order".to_string(),
1529            r#type: "one-to-many".to_string(),
1530        };
1531        let rel2 = rel1.clone();
1532        assert_eq!(rel1.from, rel2.from);
1533    }
1534
1535    #[test]
1536    fn test_relationship_requirement_debug() {
1537        let rel = RelationshipRequirement {
1538            from: "Product".to_string(),
1539            to: "Category".to_string(),
1540            r#type: "many-to-one".to_string(),
1541        };
1542        let debug_str = format!("{:?}", rel);
1543        assert!(debug_str.contains("RelationshipRequirement"));
1544    }
1545
1546    #[test]
1547    fn test_flow_requirement_clone() {
1548        let flow1 = FlowRequirement {
1549            name: "checkout".to_string(),
1550            description: "Checkout".to_string(),
1551            steps: vec![],
1552        };
1553        let flow2 = flow1.clone();
1554        assert_eq!(flow1.name, flow2.name);
1555    }
1556
1557    #[test]
1558    fn test_flow_requirement_debug() {
1559        let flow = FlowRequirement {
1560            name: "auth".to_string(),
1561            description: "Auth flow".to_string(),
1562            steps: vec!["step1".to_string()],
1563        };
1564        let debug_str = format!("{:?}", flow);
1565        assert!(debug_str.contains("FlowRequirement"));
1566    }
1567
1568    #[test]
1569    fn test_parsed_workspace_scenario_clone() {
1570        let scenario1 = ParsedWorkspaceScenario {
1571            domain: "e-commerce".to_string(),
1572            title: "Shop".to_string(),
1573            description: "Shop".to_string(),
1574            chaos_characteristics: vec![],
1575            initial_data: InitialDataRequirements::default(),
1576            api_requirements: ApiRequirements::default(),
1577            behavioral_rules: vec![],
1578        };
1579        let scenario2 = scenario1.clone();
1580        assert_eq!(scenario1.domain, scenario2.domain);
1581    }
1582
1583    #[test]
1584    fn test_parsed_workspace_scenario_debug() {
1585        let scenario = ParsedWorkspaceScenario {
1586            domain: "social".to_string(),
1587            title: "Social".to_string(),
1588            description: "Social".to_string(),
1589            chaos_characteristics: vec![],
1590            initial_data: InitialDataRequirements::default(),
1591            api_requirements: ApiRequirements::default(),
1592            behavioral_rules: vec![],
1593        };
1594        let debug_str = format!("{:?}", scenario);
1595        assert!(debug_str.contains("ParsedWorkspaceScenario"));
1596    }
1597
1598    #[test]
1599    fn test_chaos_characteristic_clone() {
1600        let chaos1 = ChaosCharacteristic {
1601            r#type: "latency".to_string(),
1602            description: "High latency".to_string(),
1603            config: json!({}),
1604        };
1605        let chaos2 = chaos1.clone();
1606        assert_eq!(chaos1.r#type, chaos2.r#type);
1607    }
1608
1609    #[test]
1610    fn test_chaos_characteristic_debug() {
1611        let chaos = ChaosCharacteristic {
1612            r#type: "failure".to_string(),
1613            description: "Failures".to_string(),
1614            config: json!({"rate": 0.1}),
1615        };
1616        let debug_str = format!("{:?}", chaos);
1617        assert!(debug_str.contains("ChaosCharacteristic"));
1618    }
1619
1620    #[test]
1621    fn test_initial_data_requirements_clone() {
1622        let data1 = InitialDataRequirements::default();
1623        let data2 = data1.clone();
1624        // Just verify it doesn't panic
1625        assert_eq!(data1.users, data2.users);
1626    }
1627
1628    #[test]
1629    fn test_initial_data_requirements_debug() {
1630        let data = InitialDataRequirements::default();
1631        let debug_str = format!("{:?}", data);
1632        assert!(debug_str.contains("InitialDataRequirements"));
1633    }
1634
1635    #[test]
1636    fn test_api_requirements_clone() {
1637        let api1 = ApiRequirements::default();
1638        let api2 = api1.clone();
1639        assert_eq!(api1.endpoints.len(), api2.endpoints.len());
1640    }
1641
1642    #[test]
1643    fn test_api_requirements_debug() {
1644        let api = ApiRequirements::default();
1645        let debug_str = format!("{:?}", api);
1646        assert!(debug_str.contains("ApiRequirements"));
1647    }
1648
1649    #[test]
1650    fn test_behavioral_rule_clone() {
1651        let rule1 = BehavioralRule {
1652            description: "Rule".to_string(),
1653            r#type: "failure".to_string(),
1654            config: json!({}),
1655        };
1656        let rule2 = rule1.clone();
1657        assert_eq!(rule1.description, rule2.description);
1658    }
1659
1660    #[test]
1661    fn test_behavioral_rule_debug() {
1662        let rule = BehavioralRule {
1663            description: "Test rule".to_string(),
1664            r#type: "latency".to_string(),
1665            config: json!({"delay": 100}),
1666        };
1667        let debug_str = format!("{:?}", rule);
1668        assert!(debug_str.contains("BehavioralRule"));
1669    }
1670
1671    #[test]
1672    fn test_parsed_workspace_creation_clone() {
1673        let creation1 = ParsedWorkspaceCreation {
1674            workspace_name: "Test".to_string(),
1675            workspace_description: "Test".to_string(),
1676            entities: vec![],
1677            personas: vec![],
1678            scenarios: vec![],
1679            reality_continuum: None,
1680            drift_budget: None,
1681        };
1682        let creation2 = creation1.clone();
1683        assert_eq!(creation1.workspace_name, creation2.workspace_name);
1684    }
1685
1686    #[test]
1687    fn test_parsed_workspace_creation_debug() {
1688        let creation = ParsedWorkspaceCreation {
1689            workspace_name: "Workspace".to_string(),
1690            workspace_description: "Description".to_string(),
1691            entities: vec![],
1692            personas: vec![],
1693            scenarios: vec![],
1694            reality_continuum: None,
1695            drift_budget: None,
1696        };
1697        let debug_str = format!("{:?}", creation);
1698        assert!(debug_str.contains("ParsedWorkspaceCreation"));
1699    }
1700
1701    #[test]
1702    fn test_parsed_reality_continuum_clone() {
1703        let continuum1 = ParsedRealityContinuum {
1704            default_ratio: 0.5,
1705            enabled: true,
1706            route_rules: vec![],
1707            transition_mode: "manual".to_string(),
1708            merge_strategy: "field_level".to_string(),
1709        };
1710        let continuum2 = continuum1.clone();
1711        assert_eq!(continuum1.default_ratio, continuum2.default_ratio);
1712    }
1713
1714    #[test]
1715    fn test_parsed_reality_continuum_debug() {
1716        let continuum = ParsedRealityContinuum {
1717            default_ratio: 0.2,
1718            enabled: true,
1719            route_rules: vec![],
1720            transition_mode: "time_based".to_string(),
1721            merge_strategy: "weighted".to_string(),
1722        };
1723        let debug_str = format!("{:?}", continuum);
1724        assert!(debug_str.contains("ParsedRealityContinuum"));
1725    }
1726
1727    #[test]
1728    fn test_parsed_continuum_rule_clone() {
1729        let rule1 = ParsedContinuumRule {
1730            pattern: "/api/*".to_string(),
1731            ratio: 0.3,
1732            description: "Test".to_string(),
1733        };
1734        let rule2 = rule1.clone();
1735        assert_eq!(rule1.pattern, rule2.pattern);
1736    }
1737
1738    #[test]
1739    fn test_parsed_continuum_rule_debug() {
1740        let rule = ParsedContinuumRule {
1741            pattern: "/catalog/*".to_string(),
1742            ratio: 0.5,
1743            description: "Catalog".to_string(),
1744        };
1745        let debug_str = format!("{:?}", rule);
1746        assert!(debug_str.contains("ParsedContinuumRule"));
1747    }
1748
1749    #[test]
1750    fn test_parsed_drift_budget_clone() {
1751        let budget1 = ParsedDriftBudget {
1752            strictness: "moderate".to_string(),
1753            enabled: true,
1754            max_breaking_changes: 10,
1755            max_non_breaking_changes: 50,
1756            max_field_churn_percent: None,
1757            time_window_days: None,
1758            per_service_budgets: HashMap::new(),
1759            description: "Budget".to_string(),
1760        };
1761        let budget2 = budget1.clone();
1762        assert_eq!(budget1.strictness, budget2.strictness);
1763    }
1764
1765    #[test]
1766    fn test_parsed_drift_budget_debug() {
1767        let budget = ParsedDriftBudget {
1768            strictness: "strict".to_string(),
1769            enabled: true,
1770            max_breaking_changes: 5,
1771            max_non_breaking_changes: 20,
1772            max_field_churn_percent: Some(3.0),
1773            time_window_days: Some(7),
1774            per_service_budgets: HashMap::new(),
1775            description: "Strict budget".to_string(),
1776        };
1777        let debug_str = format!("{:?}", budget);
1778        assert!(debug_str.contains("ParsedDriftBudget"));
1779    }
1780
1781    #[test]
1782    fn test_parsed_service_budget_clone() {
1783        let budget1 = ParsedServiceBudget {
1784            max_breaking_changes: 3,
1785            max_non_breaking_changes: 15,
1786        };
1787        let budget2 = budget1.clone();
1788        assert_eq!(budget1.max_breaking_changes, budget2.max_breaking_changes);
1789    }
1790
1791    #[test]
1792    fn test_parsed_service_budget_debug() {
1793        let budget = ParsedServiceBudget {
1794            max_breaking_changes: 5,
1795            max_non_breaking_changes: 25,
1796        };
1797        let debug_str = format!("{:?}", budget);
1798        assert!(debug_str.contains("ParsedServiceBudget"));
1799    }
1800}