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