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