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