Skip to main content

mockforge_intelligence/ai_studio/
system_generator.rs

1//! System Generator - Natural Language to Entire System Generation
2//!
3//! This module provides functionality to generate complete backend systems
4//! from natural language descriptions, including:
5//! - 20-30 REST endpoints (OpenAPI 3.1 spec)
6//! - 4-5 personas (driver, rider, admin, dispatcher, support)
7//! - 6-10 lifecycle states (trip: requested, matched, in_progress, completed, cancelled)
8//! - WebSocket topics (location_updates, trip_status, surge_alerts)
9//! - Payment failure scenarios (insufficient_funds, card_declined, network_error)
10//! - Surge pricing chaos profiles (peak_hours, event_surge, weather_surge)
11//! - Full OpenAPI specification
12//! - Mock backend configuration (mockforge.yaml)
13//! - GraphQL schema (optional)
14//! - TypeScript/Go/Rust typings
15//! - CI pipeline templates (GitHub Actions, GitLab CI)
16//!
17//! # Features
18//!
19//! - **Versioned Draft Artifacts**: Generates v1, v2, etc. (never mutates existing)
20//! - **Deterministic Mode Integration**: Honors workspace `ai.deterministic_mode` setting
21//! - **System Coherence Validation**: Ensures personas match endpoints, lifecycles match entities
22//!
23//! # Example Usage
24//!
25//! ```rust,ignore
26//! use mockforge_core::ai_studio::system_generator::{SystemGenerator, SystemGenerationRequest};
27//! use mockforge_core::intelligent_behavior::IntelligentBehaviorConfig;
28//!
29//! async fn example() -> mockforge_core::Result<()> {
30//!     let config = IntelligentBehaviorConfig::default();
31//!     let generator = SystemGenerator::new(config);
32//!
33//!     let request = SystemGenerationRequest {
34//!         description: "I'm building a ride-sharing app".to_string(),
35//!         output_formats: vec!["openapi".to_string(), "personas".to_string()],
36//!         workspace_id: Some("workspace-123".to_string()),
37//!     };
38//!
39//!     let system = generator.generate(&request).await?;
40//!     Ok(())
41//! }
42//! ```
43
44use crate::ai_studio::{
45    artifact_freezer::{ArtifactFreezer, FreezeMetadata, FreezeRequest},
46    config::DeterministicModeConfig,
47};
48use crate::intelligent_behavior::{
49    config::IntelligentBehaviorConfig,
50    llm_client::{LlmClient, LlmUsage},
51    types::LlmGenerationRequest,
52};
53use mockforge_foundation::Result;
54// Data types re-exported from foundation.
55pub use mockforge_foundation::ai_studio_types::{
56    AppliedSystem, GeneratedSystem, SystemArtifact, SystemGenerationRequest, SystemMetadata,
57};
58use serde_json::Value;
59use sha2::{Digest, Sha256};
60use std::collections::HashMap;
61use uuid::Uuid;
62
63/// System Generator Engine
64pub struct SystemGenerator {
65    /// LLM client for generation
66    llm_client: LlmClient,
67
68    /// Configuration
69    config: IntelligentBehaviorConfig,
70
71    /// Artifact freezer for deterministic mode
72    artifact_freezer: ArtifactFreezer,
73}
74
75impl SystemGenerator {
76    /// Create a new system generator
77    pub fn new(config: IntelligentBehaviorConfig) -> Self {
78        let llm_client = LlmClient::new(config.behavior_model.clone());
79        let artifact_freezer = ArtifactFreezer::new();
80        Self {
81            llm_client,
82            config,
83            artifact_freezer,
84        }
85    }
86
87    /// Create with custom artifact freezer directory
88    pub fn with_freeze_dir<P: AsRef<std::path::Path>>(
89        config: IntelligentBehaviorConfig,
90        freeze_dir: P,
91    ) -> Self {
92        let llm_client = LlmClient::new(config.behavior_model.clone());
93        let artifact_freezer = ArtifactFreezer::with_base_dir(freeze_dir);
94        Self {
95            llm_client,
96            config,
97            artifact_freezer,
98        }
99    }
100
101    /// Generate a complete system from natural language description
102    ///
103    /// If deterministic mode is enabled with auto-freeze, artifacts are automatically frozen.
104    pub async fn generate(
105        &self,
106        request: &SystemGenerationRequest,
107        deterministic_config: Option<&DeterministicModeConfig>,
108    ) -> Result<GeneratedSystem> {
109        // Determine system ID and version
110        let system_id = request
111            .system_id
112            .clone()
113            .unwrap_or_else(|| format!("system-{}", Uuid::new_v4()));
114
115        // Version starts at v1 since there is no persistent storage for generated systems.
116        // If versioning is needed, a storage backend would track previous versions by system_id.
117        let version = "v1".to_string();
118
119        // Build the generation prompt
120        let system_prompt = self.build_system_prompt();
121        let user_prompt = self.build_user_prompt(request)?;
122
123        // Generate system using LLM
124        let llm_request = LlmGenerationRequest {
125            system_prompt,
126            user_prompt,
127            temperature: 0.7, // Higher temperature for more creative generation
128            max_tokens: 8000, // Large context for full system generation
129            schema: None,
130            seed: None,
131        };
132
133        let (response_json, usage) = self.llm_client.generate_with_usage(&llm_request).await?;
134
135        // Parse the response
136        let artifacts = self.parse_system_response(response_json, &request.output_formats)?;
137
138        // Extract metadata
139        let metadata = self.extract_metadata(request, &artifacts)?;
140
141        // Calculate cost
142        let cost_usd = self.estimate_cost(&usage);
143
144        // Check if we should auto-freeze
145        let should_auto_freeze = deterministic_config
146            .map(|cfg| cfg.enabled && cfg.is_auto_freeze_enabled())
147            .unwrap_or(false);
148
149        let status = if should_auto_freeze {
150            // Auto-freeze all artifacts
151            self.freeze_system_artifacts(&system_id, &version, &artifacts, deterministic_config)
152                .await?;
153            "frozen".to_string()
154        } else {
155            "draft".to_string()
156        };
157
158        Ok(GeneratedSystem {
159            system_id,
160            version,
161            artifacts,
162            workspace_id: request.workspace_id.clone(),
163            status,
164            tokens_used: Some(usage.total_tokens),
165            cost_usd: Some(cost_usd),
166            metadata,
167        })
168    }
169
170    /// Freeze system artifacts (used for manual freeze or auto-freeze)
171    pub async fn freeze_system_artifacts(
172        &self,
173        system_id: &str,
174        version: &str,
175        artifacts: &HashMap<String, SystemArtifact>,
176        _deterministic_config: Option<&DeterministicModeConfig>,
177    ) -> Result<Vec<String>> {
178        let mut frozen_ids = Vec::new();
179
180        for (artifact_type, artifact) in artifacts {
181            let freeze_request = FreezeRequest {
182                artifact_type: format!("system_{}", artifact_type),
183                content: artifact.content.clone(),
184                format: artifact.format.clone(),
185                path: Some(format!(
186                    "{}/{}_{}_{}.{}",
187                    self.artifact_freezer.base_dir().display(),
188                    system_id,
189                    version,
190                    artifact_type,
191                    artifact.format
192                )),
193                metadata: Some(FreezeMetadata {
194                    llm_provider: Some(self.config.behavior_model.llm_provider.clone()),
195                    llm_model: Some(self.config.behavior_model.model.clone()),
196                    llm_version: None,
197                    prompt_hash: Some(self.hash_description(artifact_type)),
198                    output_hash: None,
199                    original_prompt: None,
200                }),
201            };
202
203            let frozen = self.artifact_freezer.freeze(&freeze_request).await?;
204            frozen_ids.push(frozen.path);
205        }
206
207        Ok(frozen_ids)
208    }
209
210    /// Apply system design (freeze artifacts if deterministic mode requires it)
211    ///
212    /// This is called when user clicks "Apply system design" button.
213    /// If deterministic mode is "auto", artifacts are already frozen.
214    /// If deterministic mode is "manual", this freezes them now.
215    pub async fn apply_system_design(
216        &self,
217        system: &GeneratedSystem,
218        deterministic_config: Option<&DeterministicModeConfig>,
219        artifact_ids: Option<Vec<String>>,
220    ) -> Result<AppliedSystem> {
221        // If already frozen, return as-is
222        if system.status == "frozen" {
223            return Ok(AppliedSystem {
224                system_id: system.system_id.clone(),
225                version: system.version.clone(),
226                applied_artifacts: system.artifacts.keys().cloned().collect(),
227                frozen: true,
228            });
229        }
230
231        // Check if we should freeze
232        let should_freeze = deterministic_config
233            .map(|cfg| cfg.enabled && cfg.is_auto_freeze_enabled())
234            .unwrap_or(false);
235
236        // Filter artifacts if specific IDs provided
237        let artifacts_to_apply = if let Some(ids) = artifact_ids {
238            system
239                .artifacts
240                .iter()
241                .filter(|(_, artifact)| ids.contains(&artifact.artifact_id))
242                .map(|(k, v)| (k.clone(), v.clone()))
243                .collect()
244        } else {
245            system.artifacts.clone()
246        };
247
248        if should_freeze {
249            let frozen_paths = self
250                .freeze_system_artifacts(
251                    &system.system_id,
252                    &system.version,
253                    &artifacts_to_apply,
254                    deterministic_config,
255                )
256                .await?;
257
258            Ok(AppliedSystem {
259                system_id: system.system_id.clone(),
260                version: system.version.clone(),
261                applied_artifacts: artifacts_to_apply.keys().cloned().collect(),
262                frozen: !frozen_paths.is_empty(),
263            })
264        } else {
265            // Just mark as applied, don't freeze
266            Ok(AppliedSystem {
267                system_id: system.system_id.clone(),
268                version: system.version.clone(),
269                applied_artifacts: artifacts_to_apply.keys().cloned().collect(),
270                frozen: false,
271            })
272        }
273    }
274
275    /// Manually freeze specific artifacts
276    pub async fn freeze_artifacts(
277        &self,
278        system: &GeneratedSystem,
279        artifact_ids: Vec<String>,
280    ) -> Result<Vec<String>> {
281        let artifacts_to_freeze: HashMap<String, SystemArtifact> = system
282            .artifacts
283            .iter()
284            .filter(|(_, artifact)| artifact_ids.contains(&artifact.artifact_id))
285            .map(|(k, v)| (k.clone(), v.clone()))
286            .collect();
287
288        self.freeze_system_artifacts(&system.system_id, &system.version, &artifacts_to_freeze, None)
289            .await
290    }
291
292    /// Hash description for metadata tracking
293    fn hash_description(&self, artifact_type: &str) -> String {
294        let mut hasher = Sha256::new();
295        hasher.update(artifact_type.as_bytes());
296        format!("{:x}", hasher.finalize())
297    }
298
299    /// Build system prompt for system generation
300    fn build_system_prompt(&self) -> String {
301        r#"You are an expert backend architect and system designer. Your task is to generate complete backend systems from natural language descriptions.
302
303Generate comprehensive backend systems including:
304
3051. **OpenAPI Specification** (20-30 REST endpoints)
306   - Full CRUD operations for all entities
307   - Realistic request/response schemas
308   - Proper HTTP methods and status codes
309   - Authentication and authorization where appropriate
310
3112. **Personas** (4-5 personas based on entity roles)
312   - Each persona should have realistic traits, goals, and behaviors
313   - Personas should match the roles mentioned in the description
314
3153. **Lifecycle States** (6-10 states for main entities)
316   - State machines for key entities (e.g., trip: requested → matched → in_progress → completed)
317   - State transitions with realistic conditions
318
3194. **WebSocket Topics** (if real-time features mentioned)
320   - Topic names and event schemas
321   - Event types and payloads
322
3235. **Chaos/Failure Scenarios** (if applicable)
324   - Payment failure scenarios
325   - Network error scenarios
326   - Surge pricing profiles (if pricing mentioned)
327
3286. **CI/CD Templates** (optional)
329   - GitHub Actions workflows
330   - GitLab CI configurations
331
3327. **GraphQL Schema** (optional, if requested)
333   - Type definitions
334   - Queries and mutations
335
3368. **TypeScript Typings** (optional)
337   - Type definitions from OpenAPI schema
338
339Return your generation as a JSON object with the following structure:
340{
341  "openapi": { ... OpenAPI 3.1 specification ... },
342  "personas": [
343    {
344      "name": "persona_name",
345      "traits": { ... },
346      "goals": [...],
347      "behaviors": [...]
348    }
349  ],
350  "lifecycles": [
351    {
352      "entity": "entity_name",
353      "states": ["state1", "state2", ...],
354      "transitions": [
355        {
356          "from": "state1",
357          "to": "state2",
358          "condition": "..."
359        }
360      ]
361    }
362  ],
363  "websocket_topics": [
364    {
365      "topic": "topic_name",
366      "event_types": [...],
367      "schema": { ... }
368    }
369  ],
370  "chaos_profiles": [
371    {
372      "name": "profile_name",
373      "type": "payment_failure|surge_pricing|network_error",
374      "config": { ... }
375    }
376  ],
377  "ci_templates": {
378    "github_actions": "...",
379    "gitlab_ci": "..."
380  },
381  "graphql": "... GraphQL SDL ...",
382  "typings": {
383    "typescript": "...",
384    "go": "...",
385    "rust": "..."
386  },
387  "metadata": {
388    "entities": ["entity1", "entity2", ...],
389    "relationships": ["entity1 -> entity2", ...],
390    "operations": ["create", "read", "update", "delete", ...]
391  }
392}
393
394Be thorough and generate realistic, production-ready artifacts. Ensure all artifacts are coherent (personas match endpoints, lifecycles match entities)."#
395            .to_string()
396    }
397
398    /// Build user prompt with description and output formats
399    fn build_user_prompt(&self, request: &SystemGenerationRequest) -> Result<String> {
400        let formats_text = if request.output_formats.is_empty() {
401            "all available formats".to_string()
402        } else {
403            request.output_formats.join(", ")
404        };
405
406        Ok(format!(
407            r#"Generate a complete backend system from this description:
408
409Description:
410{}
411
412Please generate the following formats: {}
413
414Make sure to:
4151. Extract all entities, relationships, and operations from the description
4162. Generate realistic and comprehensive artifacts
4173. Ensure coherence across all artifacts (personas match endpoints, lifecycles match entities)
4184. Include proper error handling and edge cases
4195. Make it production-ready
420
421Provide a complete system that can bootstrap a startup backend."#,
422            request.description, formats_text
423        ))
424    }
425
426    /// Parse LLM response into system artifacts
427    fn parse_system_response(
428        &self,
429        response: Value,
430        requested_formats: &[String],
431    ) -> Result<HashMap<String, SystemArtifact>> {
432        let mut artifacts = HashMap::new();
433
434        // Extract OpenAPI spec
435        if requested_formats.is_empty() || requested_formats.contains(&"openapi".to_string()) {
436            if let Some(openapi) = response.get("openapi") {
437                let artifact_id = format!("openapi-{}", Uuid::new_v4());
438                artifacts.insert(
439                    "openapi".to_string(),
440                    SystemArtifact {
441                        artifact_type: "openapi".to_string(),
442                        content: openapi.clone(),
443                        format: "json".to_string(),
444                        artifact_id,
445                    },
446                );
447            }
448        }
449
450        // Extract personas
451        if requested_formats.is_empty() || requested_formats.contains(&"personas".to_string()) {
452            if let Some(personas) = response.get("personas") {
453                let artifact_id = format!("personas-{}", Uuid::new_v4());
454                artifacts.insert(
455                    "personas".to_string(),
456                    SystemArtifact {
457                        artifact_type: "personas".to_string(),
458                        content: personas.clone(),
459                        format: "json".to_string(),
460                        artifact_id,
461                    },
462                );
463            }
464        }
465
466        // Extract lifecycles
467        if requested_formats.is_empty() || requested_formats.contains(&"lifecycles".to_string()) {
468            if let Some(lifecycles) = response.get("lifecycles") {
469                let artifact_id = format!("lifecycles-{}", Uuid::new_v4());
470                artifacts.insert(
471                    "lifecycles".to_string(),
472                    SystemArtifact {
473                        artifact_type: "lifecycles".to_string(),
474                        content: lifecycles.clone(),
475                        format: "json".to_string(),
476                        artifact_id,
477                    },
478                );
479            }
480        }
481
482        // Extract WebSocket topics
483        if requested_formats.contains(&"websocket".to_string()) {
484            if let Some(websocket) = response.get("websocket_topics") {
485                let artifact_id = format!("websocket-{}", Uuid::new_v4());
486                artifacts.insert(
487                    "websocket".to_string(),
488                    SystemArtifact {
489                        artifact_type: "websocket".to_string(),
490                        content: websocket.clone(),
491                        format: "json".to_string(),
492                        artifact_id,
493                    },
494                );
495            }
496        }
497
498        // Extract chaos profiles
499        if requested_formats.contains(&"chaos".to_string()) {
500            if let Some(chaos) = response.get("chaos_profiles") {
501                let artifact_id = format!("chaos-{}", Uuid::new_v4());
502                artifacts.insert(
503                    "chaos".to_string(),
504                    SystemArtifact {
505                        artifact_type: "chaos".to_string(),
506                        content: chaos.clone(),
507                        format: "json".to_string(),
508                        artifact_id,
509                    },
510                );
511            }
512        }
513
514        // Extract CI templates
515        if requested_formats.contains(&"ci".to_string()) {
516            if let Some(ci) = response.get("ci_templates") {
517                let artifact_id = format!("ci-{}", Uuid::new_v4());
518                artifacts.insert(
519                    "ci".to_string(),
520                    SystemArtifact {
521                        artifact_type: "ci".to_string(),
522                        content: ci.clone(),
523                        format: "yaml".to_string(),
524                        artifact_id,
525                    },
526                );
527            }
528        }
529
530        // Extract GraphQL schema
531        if requested_formats.contains(&"graphql".to_string()) {
532            if let Some(graphql) = response.get("graphql") {
533                let artifact_id = format!("graphql-{}", Uuid::new_v4());
534                artifacts.insert(
535                    "graphql".to_string(),
536                    SystemArtifact {
537                        artifact_type: "graphql".to_string(),
538                        content: graphql.clone(),
539                        format: "graphql".to_string(),
540                        artifact_id,
541                    },
542                );
543            }
544        }
545
546        // Extract typings
547        if requested_formats.contains(&"typings".to_string()) {
548            if let Some(typings) = response.get("typings") {
549                let artifact_id = format!("typings-{}", Uuid::new_v4());
550                artifacts.insert(
551                    "typings".to_string(),
552                    SystemArtifact {
553                        artifact_type: "typings".to_string(),
554                        content: typings.clone(),
555                        format: "json".to_string(),
556                        artifact_id,
557                    },
558                );
559            }
560        }
561
562        Ok(artifacts)
563    }
564
565    /// Extract metadata from request and artifacts
566    fn extract_metadata(
567        &self,
568        request: &SystemGenerationRequest,
569        _artifacts: &HashMap<String, SystemArtifact>,
570    ) -> Result<SystemMetadata> {
571        // In a full implementation, we'd parse the artifacts to extract entities, relationships, etc.
572        // For now, we'll use basic extraction from the description
573        let entities = self.extract_entities(&request.description);
574        let relationships = self.extract_relationships(&request.description);
575        let operations = vec![
576            "create".to_string(),
577            "read".to_string(),
578            "update".to_string(),
579            "delete".to_string(),
580        ];
581
582        Ok(SystemMetadata {
583            description: request.description.clone(),
584            entities,
585            relationships,
586            operations,
587            generated_at: chrono::Utc::now().to_rfc3339(),
588        })
589    }
590
591    /// Extract entities from description (simple heuristic)
592    fn extract_entities(&self, description: &str) -> Vec<String> {
593        // Simple extraction - in a full implementation, this would use NLP
594        let mut entities = Vec::new();
595        let words: Vec<&str> = description.split_whitespace().collect();
596
597        // Look for plural nouns that might be entities
598        for word in words {
599            if word.ends_with('s') && word.len() > 3 {
600                let singular = word.trim_end_matches('s');
601                if !entities.contains(&singular.to_string()) {
602                    entities.push(singular.to_string());
603                }
604            }
605        }
606
607        entities
608    }
609
610    /// Extract relationships from description (simple heuristic)
611    fn extract_relationships(&self, description: &str) -> Vec<String> {
612        // Simple extraction - in a full implementation, this would use NLP
613        let mut relationships = Vec::new();
614        let entities = self.extract_entities(description);
615
616        // Generate simple relationships based on proximity
617        for i in 0..entities.len() {
618            for j in (i + 1)..entities.len() {
619                relationships.push(format!("{} -> {}", entities[i], entities[j]));
620            }
621        }
622
623        relationships
624    }
625
626    /// Estimate cost in USD based on token usage
627    fn estimate_cost(&self, usage: &LlmUsage) -> f64 {
628        // Rough cost estimates per 1K tokens
629        let cost_per_1k_tokens =
630            match self.config.behavior_model.llm_provider.to_lowercase().as_str() {
631                "openai" => match self.config.behavior_model.model.to_lowercase().as_str() {
632                    model if model.contains("gpt-4") => 0.03,
633                    model if model.contains("gpt-3.5") => 0.002,
634                    _ => 0.002,
635                },
636                "anthropic" => 0.008,
637                "ollama" => 0.0, // Local models are free
638                _ => 0.002,
639            };
640
641        (usage.total_tokens as f64 / 1000.0) * cost_per_1k_tokens
642    }
643}
644
645#[cfg(test)]
646mod tests {
647    use super::*;
648    use crate::intelligent_behavior::config::BehaviorModelConfig;
649
650    fn create_test_config() -> IntelligentBehaviorConfig {
651        IntelligentBehaviorConfig {
652            behavior_model: BehaviorModelConfig {
653                llm_provider: "ollama".to_string(),
654                model: "llama2".to_string(),
655                api_endpoint: Some("http://localhost:11434/api/chat".to_string()),
656                api_key: None,
657                temperature: 0.7,
658                max_tokens: 2000,
659                rules: crate::intelligent_behavior::types::BehaviorRules::default(),
660                seed: None,
661            },
662            ..Default::default()
663        }
664    }
665
666    #[test]
667    fn test_system_generation_request_serialization() {
668        let request = SystemGenerationRequest {
669            description: "Ride-sharing app".to_string(),
670            output_formats: vec!["openapi".to_string(), "personas".to_string()],
671            workspace_id: None,
672            system_id: None,
673        };
674
675        let json = serde_json::to_string(&request).unwrap();
676        assert!(json.contains("Ride-sharing"));
677        assert!(json.contains("openapi"));
678    }
679
680    #[test]
681    fn test_entity_extraction() {
682        let config = create_test_config();
683        let generator = SystemGenerator::new(config);
684        let entities = generator.extract_entities(
685            "I'm building a ride-sharing app with drivers, riders, trips, payments",
686        );
687        assert!(!entities.is_empty());
688    }
689
690    #[test]
691    fn test_system_generation_request_creation() {
692        let request = SystemGenerationRequest {
693            description: "Test system".to_string(),
694            output_formats: vec!["openapi".to_string()],
695            workspace_id: Some("workspace-123".to_string()),
696            system_id: Some("system-456".to_string()),
697        };
698
699        assert_eq!(request.description, "Test system");
700        assert_eq!(request.output_formats.len(), 1);
701        assert_eq!(request.workspace_id, Some("workspace-123".to_string()));
702        assert_eq!(request.system_id, Some("system-456".to_string()));
703    }
704
705    #[test]
706    fn test_system_generation_request_default_output_formats() {
707        let request = SystemGenerationRequest {
708            description: "Test".to_string(),
709            output_formats: vec![],
710            workspace_id: None,
711            system_id: None,
712        };
713
714        assert!(request.output_formats.is_empty());
715    }
716
717    #[test]
718    fn test_generated_system_creation() {
719        let mut artifacts = HashMap::new();
720        artifacts.insert(
721            "openapi".to_string(),
722            SystemArtifact {
723                artifact_type: "openapi".to_string(),
724                content: serde_json::json!({"openapi": "3.0.0"}),
725                format: "json".to_string(),
726                artifact_id: "artifact-1".to_string(),
727            },
728        );
729
730        let system = GeneratedSystem {
731            system_id: "system-123".to_string(),
732            version: "v1".to_string(),
733            artifacts,
734            workspace_id: Some("workspace-456".to_string()),
735            status: "draft".to_string(),
736            tokens_used: Some(1000),
737            cost_usd: Some(0.01),
738            metadata: SystemMetadata {
739                description: "Test system".to_string(),
740                entities: vec!["User".to_string()],
741                relationships: vec![],
742                operations: vec![],
743                generated_at: "2024-01-01T00:00:00Z".to_string(),
744            },
745        };
746
747        assert_eq!(system.system_id, "system-123");
748        assert_eq!(system.version, "v1");
749        assert_eq!(system.artifacts.len(), 1);
750        assert_eq!(system.status, "draft");
751    }
752
753    #[test]
754    fn test_applied_system_creation() {
755        let applied = AppliedSystem {
756            system_id: "system-123".to_string(),
757            version: "v1".to_string(),
758            applied_artifacts: vec!["artifact-1".to_string(), "artifact-2".to_string()],
759            frozen: true,
760        };
761
762        assert_eq!(applied.system_id, "system-123");
763        assert_eq!(applied.version, "v1");
764        assert_eq!(applied.applied_artifacts.len(), 2);
765        assert!(applied.frozen);
766    }
767
768    #[test]
769    fn test_system_artifact_creation() {
770        let artifact = SystemArtifact {
771            artifact_type: "openapi".to_string(),
772            content: serde_json::json!({"openapi": "3.0.0", "info": {"title": "API"}}),
773            format: "yaml".to_string(),
774            artifact_id: "artifact-123".to_string(),
775        };
776
777        assert_eq!(artifact.artifact_type, "openapi");
778        assert_eq!(artifact.format, "yaml");
779        assert_eq!(artifact.artifact_id, "artifact-123");
780    }
781
782    #[test]
783    fn test_system_metadata_creation() {
784        let metadata = SystemMetadata {
785            description: "Ride-sharing app".to_string(),
786            entities: vec![
787                "Driver".to_string(),
788                "Rider".to_string(),
789                "Trip".to_string(),
790            ],
791            relationships: vec!["Driver has many Trips".to_string()],
792            operations: vec!["create_trip".to_string(), "update_trip".to_string()],
793            generated_at: "2024-01-01T00:00:00Z".to_string(),
794        };
795
796        assert_eq!(metadata.description, "Ride-sharing app");
797        assert_eq!(metadata.entities.len(), 3);
798        assert_eq!(metadata.relationships.len(), 1);
799        assert_eq!(metadata.operations.len(), 2);
800    }
801
802    #[test]
803    fn test_system_generator_new() {
804        let config = create_test_config();
805        let generator = SystemGenerator::new(config);
806        // Just verify it can be created
807        let _ = generator;
808    }
809
810    #[test]
811    fn test_system_generator_with_freeze_dir() {
812        let config = create_test_config();
813        let generator = SystemGenerator::with_freeze_dir(config, "/tmp/freeze");
814        // Just verify it can be created
815        let _ = generator;
816    }
817
818    #[test]
819    fn test_system_generation_request_clone() {
820        let request1 = SystemGenerationRequest {
821            description: "Test system".to_string(),
822            output_formats: vec!["openapi".to_string()],
823            workspace_id: Some("workspace-123".to_string()),
824            system_id: Some("system-456".to_string()),
825        };
826        let request2 = request1.clone();
827        assert_eq!(request1.description, request2.description);
828        assert_eq!(request1.output_formats, request2.output_formats);
829    }
830
831    #[test]
832    fn test_system_generation_request_debug() {
833        let request = SystemGenerationRequest {
834            description: "Test".to_string(),
835            output_formats: vec![],
836            workspace_id: None,
837            system_id: None,
838        };
839        let debug_str = format!("{:?}", request);
840        assert!(debug_str.contains("SystemGenerationRequest"));
841    }
842
843    #[test]
844    fn test_generated_system_clone() {
845        let system1 = GeneratedSystem {
846            system_id: "system-123".to_string(),
847            version: "v1".to_string(),
848            artifacts: HashMap::new(),
849            workspace_id: None,
850            status: "draft".to_string(),
851            tokens_used: None,
852            cost_usd: None,
853            metadata: SystemMetadata {
854                description: "Test".to_string(),
855                entities: vec![],
856                relationships: vec![],
857                operations: vec![],
858                generated_at: "2024-01-01T00:00:00Z".to_string(),
859            },
860        };
861        let system2 = system1.clone();
862        assert_eq!(system1.system_id, system2.system_id);
863        assert_eq!(system1.version, system2.version);
864    }
865
866    #[test]
867    fn test_generated_system_debug() {
868        let system = GeneratedSystem {
869            system_id: "system-123".to_string(),
870            version: "v1".to_string(),
871            artifacts: HashMap::new(),
872            workspace_id: None,
873            status: "draft".to_string(),
874            tokens_used: None,
875            cost_usd: None,
876            metadata: SystemMetadata {
877                description: "Test".to_string(),
878                entities: vec![],
879                relationships: vec![],
880                operations: vec![],
881                generated_at: "2024-01-01T00:00:00Z".to_string(),
882            },
883        };
884        let debug_str = format!("{:?}", system);
885        assert!(debug_str.contains("GeneratedSystem"));
886    }
887
888    #[test]
889    fn test_applied_system_clone() {
890        let applied1 = AppliedSystem {
891            system_id: "system-123".to_string(),
892            version: "v1".to_string(),
893            applied_artifacts: vec!["artifact-1".to_string()],
894            frozen: true,
895        };
896        let applied2 = applied1.clone();
897        assert_eq!(applied1.system_id, applied2.system_id);
898        assert_eq!(applied1.frozen, applied2.frozen);
899    }
900
901    #[test]
902    fn test_applied_system_debug() {
903        let applied = AppliedSystem {
904            system_id: "system-123".to_string(),
905            version: "v1".to_string(),
906            applied_artifacts: vec![],
907            frozen: false,
908        };
909        let debug_str = format!("{:?}", applied);
910        assert!(debug_str.contains("AppliedSystem"));
911    }
912
913    #[test]
914    fn test_system_artifact_clone() {
915        let artifact1 = SystemArtifact {
916            artifact_type: "openapi".to_string(),
917            content: serde_json::json!({}),
918            format: "json".to_string(),
919            artifact_id: "artifact-1".to_string(),
920        };
921        let artifact2 = artifact1.clone();
922        assert_eq!(artifact1.artifact_type, artifact2.artifact_type);
923        assert_eq!(artifact1.artifact_id, artifact2.artifact_id);
924    }
925
926    #[test]
927    fn test_system_artifact_debug() {
928        let artifact = SystemArtifact {
929            artifact_type: "openapi".to_string(),
930            content: serde_json::json!({}),
931            format: "json".to_string(),
932            artifact_id: "artifact-1".to_string(),
933        };
934        let debug_str = format!("{:?}", artifact);
935        assert!(debug_str.contains("SystemArtifact"));
936    }
937
938    #[test]
939    fn test_system_metadata_clone() {
940        let metadata1 = SystemMetadata {
941            description: "Test".to_string(),
942            entities: vec!["User".to_string()],
943            relationships: vec![],
944            operations: vec![],
945            generated_at: "2024-01-01T00:00:00Z".to_string(),
946        };
947        let metadata2 = metadata1.clone();
948        assert_eq!(metadata1.description, metadata2.description);
949        assert_eq!(metadata1.entities, metadata2.entities);
950    }
951
952    #[test]
953    fn test_system_metadata_debug() {
954        let metadata = SystemMetadata {
955            description: "Test".to_string(),
956            entities: vec![],
957            relationships: vec![],
958            operations: vec![],
959            generated_at: "2024-01-01T00:00:00Z".to_string(),
960        };
961        let debug_str = format!("{:?}", metadata);
962        assert!(debug_str.contains("SystemMetadata"));
963    }
964
965    #[test]
966    fn test_system_generation_request_with_all_fields() {
967        let request = SystemGenerationRequest {
968            description: "Complete e-commerce system".to_string(),
969            output_formats: vec![
970                "openapi".to_string(),
971                "graphql".to_string(),
972                "personas".to_string(),
973                "lifecycles".to_string(),
974            ],
975            workspace_id: Some("workspace-789".to_string()),
976            system_id: Some("system-999".to_string()),
977        };
978        assert_eq!(request.output_formats.len(), 4);
979        assert!(request.output_formats.contains(&"openapi".to_string()));
980        assert!(request.output_formats.contains(&"graphql".to_string()));
981    }
982
983    #[test]
984    fn test_generated_system_with_all_fields() {
985        let mut artifacts = HashMap::new();
986        artifacts.insert(
987            "openapi".to_string(),
988            SystemArtifact {
989                artifact_type: "openapi".to_string(),
990                content: serde_json::json!({"openapi": "3.0.0"}),
991                format: "json".to_string(),
992                artifact_id: "artifact-1".to_string(),
993            },
994        );
995        artifacts.insert(
996            "personas".to_string(),
997            SystemArtifact {
998                artifact_type: "personas".to_string(),
999                content: serde_json::json!({"personas": []}),
1000                format: "json".to_string(),
1001                artifact_id: "artifact-2".to_string(),
1002            },
1003        );
1004
1005        let system = GeneratedSystem {
1006            system_id: "system-123".to_string(),
1007            version: "v2".to_string(),
1008            artifacts: artifacts.clone(),
1009            workspace_id: Some("workspace-456".to_string()),
1010            status: "frozen".to_string(),
1011            tokens_used: Some(5000),
1012            cost_usd: Some(0.05),
1013            metadata: SystemMetadata {
1014                description: "Ride-sharing app".to_string(),
1015                entities: vec!["Driver".to_string(), "Rider".to_string()],
1016                relationships: vec!["Driver-Trip".to_string()],
1017                operations: vec!["create_trip".to_string()],
1018                generated_at: "2024-01-01T00:00:00Z".to_string(),
1019            },
1020        };
1021
1022        assert_eq!(system.artifacts.len(), 2);
1023        assert_eq!(system.version, "v2");
1024        assert_eq!(system.status, "frozen");
1025        assert_eq!(system.tokens_used, Some(5000));
1026        assert_eq!(system.cost_usd, Some(0.05));
1027    }
1028
1029    #[test]
1030    fn test_applied_system_with_multiple_artifacts() {
1031        let applied = AppliedSystem {
1032            system_id: "system-123".to_string(),
1033            version: "v1".to_string(),
1034            applied_artifacts: vec![
1035                "artifact-1".to_string(),
1036                "artifact-2".to_string(),
1037                "artifact-3".to_string(),
1038            ],
1039            frozen: true,
1040        };
1041        assert_eq!(applied.applied_artifacts.len(), 3);
1042        assert!(applied.frozen);
1043    }
1044
1045    #[test]
1046    fn test_system_artifact_with_yaml_format() {
1047        let artifact = SystemArtifact {
1048            artifact_type: "openapi".to_string(),
1049            content: serde_json::json!({"openapi": "3.0.0"}),
1050            format: "yaml".to_string(),
1051            artifact_id: "artifact-yaml".to_string(),
1052        };
1053        assert_eq!(artifact.format, "yaml");
1054    }
1055
1056    #[test]
1057    fn test_system_metadata_with_all_fields() {
1058        let metadata = SystemMetadata {
1059            description: "Complete system description".to_string(),
1060            entities: vec![
1061                "User".to_string(),
1062                "Order".to_string(),
1063                "Product".to_string(),
1064            ],
1065            relationships: vec!["User-Order".to_string(), "Order-Product".to_string()],
1066            operations: vec![
1067                "GET /users".to_string(),
1068                "POST /orders".to_string(),
1069                "PUT /products".to_string(),
1070            ],
1071            generated_at: "2024-01-01T12:00:00Z".to_string(),
1072        };
1073        assert_eq!(metadata.entities.len(), 3);
1074        assert_eq!(metadata.relationships.len(), 2);
1075        assert_eq!(metadata.operations.len(), 3);
1076    }
1077}