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