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