mockforge_data/
mock_generator.rs

1//! Enhanced Mock Data Generator for OpenAPI Specifications
2//!
3//! This module provides comprehensive mock data generation capabilities that go beyond
4//! the basic schema generator, offering intelligent data generation based on OpenAPI
5//! specifications with type safety and realistic data patterns.
6
7use crate::consistency::ConsistencyStore;
8use crate::domains::Domain;
9use crate::faker::EnhancedFaker;
10use crate::persona::PersonaRegistry;
11use crate::persona_backstory::BackstoryGenerator;
12use crate::persona_templates::PersonaTemplateRegistry;
13use crate::schema::{FieldDefinition, SchemaDefinition};
14use crate::{Error, Result};
15use serde_json::{json, Value};
16use std::collections::HashMap;
17use std::sync::Arc;
18use tracing::{debug, info, warn};
19
20/// Configuration for mock data generation
21#[derive(Debug, Clone)]
22pub struct MockGeneratorConfig {
23    /// Whether to use realistic data patterns
24    pub realistic_mode: bool,
25    /// Default array size for generated arrays
26    pub default_array_size: usize,
27    /// Maximum array size for generated arrays
28    pub max_array_size: usize,
29    /// Whether to include optional fields
30    pub include_optional_fields: bool,
31    /// Custom field mappings for specific field names
32    pub field_mappings: HashMap<String, String>,
33    /// Whether to validate generated data against schemas
34    pub validate_generated_data: bool,
35    /// Whether to generate backstories for personas
36    pub enable_backstories: bool,
37}
38
39impl Default for MockGeneratorConfig {
40    fn default() -> Self {
41        Self {
42            realistic_mode: true,
43            default_array_size: 3,
44            max_array_size: 10,
45            include_optional_fields: true,
46            field_mappings: HashMap::new(),
47            validate_generated_data: true,
48            enable_backstories: false,
49        }
50    }
51}
52
53impl MockGeneratorConfig {
54    /// Create a new configuration with realistic defaults
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    /// Enable realistic data generation
60    pub fn realistic_mode(mut self, enabled: bool) -> Self {
61        self.realistic_mode = enabled;
62        self
63    }
64
65    /// Set default array size
66    pub fn default_array_size(mut self, size: usize) -> Self {
67        self.default_array_size = size;
68        self
69    }
70
71    /// Set maximum array size
72    pub fn max_array_size(mut self, size: usize) -> Self {
73        self.max_array_size = size;
74        self
75    }
76
77    /// Control whether to include optional fields
78    pub fn include_optional_fields(mut self, include: bool) -> Self {
79        self.include_optional_fields = include;
80        self
81    }
82
83    /// Add a custom field mapping
84    pub fn field_mapping(mut self, field_name: String, faker_type: String) -> Self {
85        self.field_mappings.insert(field_name, faker_type);
86        self
87    }
88
89    /// Enable/disable data validation
90    pub fn validate_generated_data(mut self, validate: bool) -> Self {
91        self.validate_generated_data = validate;
92        self
93    }
94
95    /// Enable/disable backstory generation for personas
96    pub fn enable_backstories(mut self, enable: bool) -> Self {
97        self.enable_backstories = enable;
98        self
99    }
100}
101
102/// Enhanced mock data generator with intelligent schema analysis
103#[derive(Debug)]
104pub struct MockDataGenerator {
105    /// Configuration for the generator
106    config: MockGeneratorConfig,
107    /// Enhanced faker instance
108    faker: EnhancedFaker,
109    /// Schema registry for complex types
110    #[allow(dead_code)]
111    schema_registry: HashMap<String, SchemaDefinition>,
112    /// Field name patterns for intelligent mapping
113    field_patterns: HashMap<String, String>,
114    /// Persona registry for consistent persona-based generation
115    persona_registry: Option<Arc<PersonaRegistry>>,
116    /// Consistency store for entity ID → persona mappings
117    consistency_store: Option<Arc<ConsistencyStore>>,
118    /// Active domain for persona-based generation
119    active_domain: Option<Domain>,
120}
121
122impl MockDataGenerator {
123    /// Create a new mock data generator with default configuration
124    pub fn new() -> Self {
125        Self::with_config(MockGeneratorConfig::new())
126    }
127
128    /// Create a new mock data generator with custom configuration
129    pub fn with_config(config: MockGeneratorConfig) -> Self {
130        let mut generator = Self {
131            config,
132            faker: EnhancedFaker::new(),
133            schema_registry: HashMap::new(),
134            field_patterns: Self::create_field_patterns(),
135            persona_registry: None,
136            consistency_store: None,
137            active_domain: None,
138        };
139
140        // Initialize with common schema patterns
141        generator.initialize_common_schemas();
142        generator
143    }
144
145    /// Create a new mock data generator with persona support
146    pub fn with_persona_support(config: MockGeneratorConfig, domain: Option<Domain>) -> Self {
147        let persona_registry = Arc::new(PersonaRegistry::new());
148        let consistency_store =
149            Arc::new(ConsistencyStore::with_registry_and_domain(persona_registry.clone(), domain));
150
151        let mut generator = Self {
152            config,
153            faker: EnhancedFaker::new(),
154            schema_registry: HashMap::new(),
155            field_patterns: Self::create_field_patterns(),
156            persona_registry: Some(persona_registry),
157            consistency_store: Some(consistency_store),
158            active_domain: domain,
159        };
160
161        // Initialize with common schema patterns
162        generator.initialize_common_schemas();
163        generator
164    }
165
166    /// Set the active domain for persona-based generation
167    pub fn set_active_domain(&mut self, domain: Option<Domain>) {
168        self.active_domain = domain;
169        // Note: ConsistencyStore doesn't have a setter for default domain,
170        // so we just update the active_domain field which is used when generating values
171    }
172
173    /// Get the persona registry
174    pub fn persona_registry(&self) -> Option<&Arc<PersonaRegistry>> {
175        self.persona_registry.as_ref()
176    }
177
178    /// Get the consistency store
179    pub fn consistency_store(&self) -> Option<&Arc<ConsistencyStore>> {
180        self.consistency_store.as_ref()
181    }
182
183    /// Generate mock data from an OpenAPI specification
184    pub fn generate_from_openapi_spec(&mut self, spec: &Value) -> Result<MockDataResult> {
185        info!("Generating mock data from OpenAPI specification");
186
187        // Parse the OpenAPI spec
188        let openapi_spec = self.parse_openapi_spec(spec)?;
189
190        // Extract all schemas from the spec
191        let schemas = self.extract_schemas_from_spec(spec)?;
192
193        // Generate mock data for each schema
194        let mut generated_data = HashMap::new();
195        let mut warnings = Vec::new();
196
197        for (schema_name, schema_def) in schemas {
198            debug!("Generating data for schema: {}", schema_name);
199
200            match self.generate_schema_data(&schema_def) {
201                Ok(data) => {
202                    generated_data.insert(schema_name, data);
203                }
204                Err(e) => {
205                    let warning =
206                        format!("Failed to generate data for schema '{}': {}", schema_name, e);
207                    warn!("{}", warning);
208                    warnings.push(warning);
209                }
210            }
211        }
212
213        // Generate mock responses for each endpoint
214        let mut mock_responses = HashMap::new();
215        for (path, path_item) in &openapi_spec.paths {
216            for (method, operation) in path_item.operations() {
217                let endpoint_key = format!("{} {}", method.to_uppercase(), path);
218
219                // Generate mock response for this endpoint
220                if let Some(response_data) = self.generate_endpoint_response(operation)? {
221                    mock_responses.insert(endpoint_key, response_data);
222                }
223            }
224        }
225
226        Ok(MockDataResult {
227            schemas: generated_data,
228            responses: mock_responses,
229            warnings,
230            spec_info: openapi_spec.info,
231        })
232    }
233
234    /// Generate mock data from a JSON Schema
235    pub fn generate_from_json_schema(&mut self, schema: &Value) -> Result<Value> {
236        debug!("Generating mock data from JSON Schema");
237
238        // Convert JSON Schema to our internal schema format
239        let schema_def = SchemaDefinition::from_json_schema(schema)?;
240
241        // Generate data using our enhanced generator
242        self.generate_schema_data(&schema_def)
243    }
244
245    /// Generate mock data for a specific schema definition
246    fn generate_schema_data(&mut self, schema: &SchemaDefinition) -> Result<Value> {
247        let mut object = serde_json::Map::new();
248
249        for field in &schema.fields {
250            // Skip optional fields if configured to do so
251            if !field.required && !self.config.include_optional_fields {
252                continue;
253            }
254
255            // Determine the best faker type for this field
256            let faker_type = self.determine_faker_type(field);
257
258            // Generate the value
259            let value = self.generate_field_value(field, &faker_type)?;
260
261            // Validate the generated value if configured
262            if self.config.validate_generated_data {
263                field.validate_value(&value)?;
264            }
265
266            object.insert(field.name.clone(), value);
267        }
268
269        Ok(Value::Object(object))
270    }
271
272    /// Generate mock response for an OpenAPI operation
273    fn generate_endpoint_response(
274        &mut self,
275        operation: &openapiv3::Operation,
276    ) -> Result<Option<MockResponse>> {
277        // Find the best response to mock (prefer 200, then 201, then any 2xx)
278        let response_schema = self.find_best_response_schema(operation)?;
279
280        if let Some(schema) = response_schema {
281            let mock_data = self.generate_from_json_schema(&schema)?;
282
283            Ok(Some(MockResponse {
284                status: 200, // Default to 200 for successful responses
285                headers: HashMap::new(),
286                body: mock_data,
287            }))
288        } else {
289            Ok(None)
290        }
291    }
292
293    /// Find the best response schema from an operation
294    fn find_best_response_schema(&self, operation: &openapiv3::Operation) -> Result<Option<Value>> {
295        let responses = &operation.responses;
296
297        // Look for 200 response first
298        if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(200)) {
299            if let Some(schema) = self.extract_response_schema(response)? {
300                return Ok(Some(schema));
301            }
302        }
303
304        // Look for 201 response
305        if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(201)) {
306            if let Some(schema) = self.extract_response_schema(response)? {
307                return Ok(Some(schema));
308            }
309        }
310
311        // Look for any 2xx response
312        for (code, response) in &responses.responses {
313            if let openapiv3::StatusCode::Code(status_code) = code {
314                if *status_code >= 200 && *status_code < 300 {
315                    if let Some(schema) = self.extract_response_schema(response)? {
316                        return Ok(Some(schema));
317                    }
318                }
319            }
320        }
321
322        Ok(None)
323    }
324
325    /// Extract schema from an OpenAPI response
326    fn extract_response_schema(
327        &self,
328        response: &openapiv3::ReferenceOr<openapiv3::Response>,
329    ) -> Result<Option<Value>> {
330        match response {
331            openapiv3::ReferenceOr::Item(response) => {
332                let content = &response.content;
333                // Prefer application/json content
334                if let Some(json_content) = content.get("application/json") {
335                    if let Some(schema) = &json_content.schema {
336                        return Ok(Some(serde_json::to_value(schema)?));
337                    }
338                }
339
340                // Fall back to any content type
341                for (_, media_type) in content {
342                    if let Some(schema) = &media_type.schema {
343                        return Ok(Some(serde_json::to_value(schema)?));
344                    }
345                }
346
347                Ok(None)
348            }
349            openapiv3::ReferenceOr::Reference { .. } => {
350                // Handle reference responses (could be expanded)
351                Ok(None)
352            }
353        }
354    }
355
356    /// Determine the best faker type for a field based on its name and type
357    fn determine_faker_type(&self, field: &FieldDefinition) -> String {
358        let field_name = field.name.to_lowercase();
359
360        // Check custom field mappings first
361        if let Some(mapped_type) = self.config.field_mappings.get(&field_name) {
362            return mapped_type.clone();
363        }
364
365        // Use field name patterns for intelligent mapping
366        for (pattern, faker_type) in &self.field_patterns {
367            if field_name.contains(pattern) {
368                return faker_type.clone();
369            }
370        }
371
372        // Fall back to field type
373        field.field_type.clone()
374    }
375
376    /// Generate a value for a specific field
377    fn generate_field_value(&mut self, field: &FieldDefinition, faker_type: &str) -> Result<Value> {
378        // Note: Automatic persona-based generation from field names would require
379        // entity ID values from request context (path params, query params, body).
380        // For now, use explicit generate_with_persona() for persona-based generation.
381        // Automatic detection can be enhanced in the future when request context is available.
382
383        // Use faker template if provided
384        if let Some(template) = &field.faker_template {
385            return Ok(self.faker.generate_by_type(template));
386        }
387
388        // Generate based on determined faker type
389        let value = self.faker.generate_by_type(faker_type);
390
391        // Apply constraints if present
392        self.apply_constraints(&value, field)
393    }
394
395    /// Generate data with explicit persona support
396    ///
397    /// Generates data for a schema using a specific entity ID and domain.
398    /// This ensures the same entity ID always generates the same data pattern.
399    /// If backstories are enabled, automatically generates backstories for personas.
400    pub fn generate_with_persona(
401        &mut self,
402        entity_id: &str,
403        domain: Domain,
404        schema: &SchemaDefinition,
405    ) -> Result<Value> {
406        // Ensure consistency store is available
407        let store = self.consistency_store.as_ref().ok_or_else(|| {
408            Error::generic("Persona support not enabled. Use with_persona_support() to create generator with persona support.")
409        })?;
410
411        // Generate backstory if enabled
412        if self.config.enable_backstories {
413            self.ensure_persona_backstory(store, entity_id, domain)?;
414        }
415
416        let mut object = serde_json::Map::new();
417
418        for field in &schema.fields {
419            // Skip optional fields if configured to do so
420            if !field.required && !self.config.include_optional_fields {
421                continue;
422            }
423
424            // Determine the best faker type for this field
425            let faker_type = self.determine_faker_type(field);
426
427            // Generate value using persona-based generation
428            let value = match store.generate_consistent_value(entity_id, &faker_type, Some(domain))
429            {
430                Ok(v) => v,
431                Err(_) => {
432                    // Fallback to regular generation
433                    self.faker.generate_by_type(&faker_type)
434                }
435            };
436
437            // Validate the generated value if configured
438            if self.config.validate_generated_data {
439                field.validate_value(&value)?;
440            }
441
442            object.insert(field.name.clone(), value);
443        }
444
445        Ok(Value::Object(object))
446    }
447
448    /// Ensure a persona has a backstory, generating one if needed
449    ///
450    /// Checks if the persona has a backstory, and if not, generates one
451    /// using the PersonaTemplateRegistry and BackstoryGenerator.
452    fn ensure_persona_backstory(
453        &self,
454        store: &ConsistencyStore,
455        entity_id: &str,
456        domain: Domain,
457    ) -> Result<()> {
458        let persona_registry = store.persona_registry();
459        let persona = store.get_entity_persona(entity_id, Some(domain));
460
461        // If persona already has a backstory, no need to generate
462        if persona.has_backstory() {
463            return Ok(());
464        }
465
466        // Generate traits using template if persona doesn't have traits
467        let mut persona_mut = persona.clone();
468        if persona_mut.traits.is_empty() {
469            let template_registry = PersonaTemplateRegistry::new();
470            template_registry.apply_template_to_persona(&mut persona_mut)?;
471        }
472
473        // Generate backstory using BackstoryGenerator
474        let backstory_generator = BackstoryGenerator::new();
475        match backstory_generator.generate_backstory(&persona_mut) {
476            Ok(backstory) => {
477                // Update persona in registry with traits and backstory
478                let mut traits = HashMap::new();
479                for (key, value) in &persona_mut.traits {
480                    traits.insert(key.clone(), value.clone());
481                }
482
483                // Update traits first
484                if !traits.is_empty() {
485                    persona_registry.update_persona(entity_id, traits)?;
486                }
487
488                // Update backstory
489                persona_registry.update_persona_backstory(entity_id, backstory)?;
490            }
491            Err(e) => {
492                warn!("Failed to generate backstory for persona {}: {}", entity_id, e);
493            }
494        }
495
496        Ok(())
497    }
498
499    /// Apply constraints to a generated value
500    fn apply_constraints(&mut self, value: &Value, field: &FieldDefinition) -> Result<Value> {
501        let mut constrained_value = value.clone();
502
503        // Apply numeric constraints
504        if let Value::Number(num) = value {
505            if let Some(minimum) = field.constraints.get("minimum") {
506                if let Some(min_val) = minimum.as_f64() {
507                    if num.as_f64().unwrap_or(0.0) < min_val {
508                        constrained_value = json!(min_val);
509                    }
510                }
511            }
512
513            if let Some(maximum) = field.constraints.get("maximum") {
514                if let Some(max_val) = maximum.as_f64() {
515                    if num.as_f64().unwrap_or(0.0) > max_val {
516                        constrained_value = json!(max_val);
517                    }
518                }
519            }
520        }
521
522        // Apply string constraints
523        if let Value::String(s) = value {
524            let mut constrained_string = s.clone();
525
526            // Apply min/max length constraints
527            if let Some(min_length) = field.constraints.get("minLength") {
528                if let Some(min_len) = min_length.as_u64() {
529                    if constrained_string.len() < min_len as usize {
530                        // Pad with random characters
531                        let padding_needed = min_len as usize - constrained_string.len();
532                        let padding = self.faker.string(padding_needed);
533                        constrained_string = format!("{}{}", constrained_string, padding);
534                    }
535                }
536            }
537
538            if let Some(max_length) = field.constraints.get("maxLength") {
539                if let Some(max_len) = max_length.as_u64() {
540                    if constrained_string.len() > max_len as usize {
541                        constrained_string.truncate(max_len as usize);
542                    }
543                }
544            }
545
546            constrained_value = json!(constrained_string);
547        }
548
549        // Apply enum constraints
550        if let Some(enum_values) = field.constraints.get("enum") {
551            if let Some(enum_array) = enum_values.as_array() {
552                if !enum_array.is_empty() {
553                    if let Some(random_value) = self.faker.random_element(enum_array) {
554                        constrained_value = random_value.clone();
555                    }
556                }
557            }
558        }
559
560        Ok(constrained_value)
561    }
562
563    /// Parse OpenAPI specification
564    fn parse_openapi_spec(&self, spec: &Value) -> Result<OpenApiSpec> {
565        // This is a simplified parser - in a real implementation,
566        // you'd want to use a proper OpenAPI parser
567        let spec_obj = spec
568            .as_object()
569            .ok_or_else(|| Error::generic("Invalid OpenAPI specification"))?;
570
571        let info = spec_obj
572            .get("info")
573            .ok_or_else(|| Error::generic("Missing 'info' section in OpenAPI spec"))?;
574
575        let title = info.get("title").and_then(|t| t.as_str()).unwrap_or("Unknown API").to_string();
576
577        let version = info.get("version").and_then(|v| v.as_str()).unwrap_or("1.0.0").to_string();
578
579        let description = info.get("description").and_then(|d| d.as_str()).map(|s| s.to_string());
580
581        Ok(OpenApiSpec {
582            info: OpenApiInfo {
583                title,
584                version,
585                description,
586            },
587            paths: HashMap::new(), // Simplified for this example
588        })
589    }
590
591    /// Extract schemas from OpenAPI specification
592    fn extract_schemas_from_spec(
593        &mut self,
594        spec: &Value,
595    ) -> Result<HashMap<String, SchemaDefinition>> {
596        let mut schemas = HashMap::new();
597
598        // Extract component schemas
599        if let Some(components) = spec.get("components") {
600            if let Some(schemas_section) = components.get("schemas") {
601                if let Some(schema_obj) = schemas_section.as_object() {
602                    for (name, schema_def) in schema_obj {
603                        let schema = SchemaDefinition::from_json_schema(schema_def)?;
604                        schemas.insert(name.clone(), schema);
605                    }
606                }
607            }
608        }
609
610        // Extract schemas from paths
611        if let Some(paths) = spec.get("paths") {
612            if let Some(paths_obj) = paths.as_object() {
613                for (path, path_item) in paths_obj {
614                    if let Some(path_obj) = path_item.as_object() {
615                        for (method, operation) in path_obj {
616                            if let Some(op_obj) = operation.as_object() {
617                                // Extract request body schemas
618                                if let Some(request_body) = op_obj.get("requestBody") {
619                                    if let Some(content) = request_body.get("content") {
620                                        if let Some(json_content) = content.get("application/json")
621                                        {
622                                            if let Some(schema) = json_content.get("schema") {
623                                                let schema_name = format!(
624                                                    "{}_{}_request",
625                                                    path.replace("/", "_").trim_start_matches("_"),
626                                                    method
627                                                );
628                                                let schema_def =
629                                                    SchemaDefinition::from_json_schema(schema)?;
630                                                schemas.insert(schema_name, schema_def);
631                                            }
632                                        }
633                                    }
634                                }
635
636                                // Extract response schemas
637                                if let Some(responses) = op_obj.get("responses") {
638                                    if let Some(resp_obj) = responses.as_object() {
639                                        for (status_code, response) in resp_obj {
640                                            if let Some(content) = response.get("content") {
641                                                if let Some(json_content) =
642                                                    content.get("application/json")
643                                                {
644                                                    if let Some(schema) = json_content.get("schema")
645                                                    {
646                                                        let schema_name = format!(
647                                                            "{}_{}_response_{}",
648                                                            path.replace("/", "_")
649                                                                .trim_start_matches("_"),
650                                                            method,
651                                                            status_code
652                                                        );
653                                                        let schema_def =
654                                                            SchemaDefinition::from_json_schema(
655                                                                schema,
656                                                            )?;
657                                                        schemas.insert(schema_name, schema_def);
658                                                    }
659                                                }
660                                            }
661                                        }
662                                    }
663                                }
664                            }
665                        }
666                    }
667                }
668            }
669        }
670
671        Ok(schemas)
672    }
673
674    /// Create field name patterns for intelligent mapping
675    fn create_field_patterns() -> HashMap<String, String> {
676        let mut patterns = HashMap::new();
677
678        // Email patterns
679        patterns.insert("email".to_string(), "email".to_string());
680        patterns.insert("mail".to_string(), "email".to_string());
681
682        // Name patterns
683        patterns.insert("name".to_string(), "name".to_string());
684        patterns.insert("firstname".to_string(), "name".to_string());
685        patterns.insert("lastname".to_string(), "name".to_string());
686        patterns.insert("username".to_string(), "name".to_string());
687
688        // Phone patterns
689        patterns.insert("phone".to_string(), "phone".to_string());
690        patterns.insert("mobile".to_string(), "phone".to_string());
691        patterns.insert("telephone".to_string(), "phone".to_string());
692
693        // Address patterns
694        patterns.insert("address".to_string(), "address".to_string());
695        patterns.insert("street".to_string(), "address".to_string());
696        patterns.insert("city".to_string(), "string".to_string());
697        patterns.insert("state".to_string(), "string".to_string());
698        patterns.insert("zip".to_string(), "string".to_string());
699        patterns.insert("postal".to_string(), "string".to_string());
700
701        // Company patterns
702        patterns.insert("company".to_string(), "company".to_string());
703        patterns.insert("organization".to_string(), "company".to_string());
704        patterns.insert("corp".to_string(), "company".to_string());
705
706        // URL patterns
707        patterns.insert("url".to_string(), "url".to_string());
708        patterns.insert("website".to_string(), "url".to_string());
709        patterns.insert("link".to_string(), "url".to_string());
710
711        // Date patterns
712        patterns.insert("date".to_string(), "date".to_string());
713        patterns.insert("created".to_string(), "date".to_string());
714        patterns.insert("updated".to_string(), "date".to_string());
715        patterns.insert("timestamp".to_string(), "date".to_string());
716
717        // ID patterns
718        patterns.insert("id".to_string(), "uuid".to_string());
719        patterns.insert("uuid".to_string(), "uuid".to_string());
720        patterns.insert("guid".to_string(), "uuid".to_string());
721
722        // IP patterns
723        patterns.insert("ip".to_string(), "ip".to_string());
724        patterns.insert("ipv4".to_string(), "ip".to_string());
725        patterns.insert("ipv6".to_string(), "ip".to_string());
726
727        patterns
728    }
729
730    /// Initialize common schemas
731    fn initialize_common_schemas(&mut self) {
732        // Add common schema patterns here
733        // This could include User, Product, Order, etc.
734    }
735}
736
737impl Default for MockDataGenerator {
738    fn default() -> Self {
739        Self::new()
740    }
741}
742
743/// Result of mock data generation
744#[derive(Debug, Clone, serde::Serialize)]
745pub struct MockDataResult {
746    /// Generated data for each schema
747    pub schemas: HashMap<String, Value>,
748    /// Generated mock responses for each endpoint
749    pub responses: HashMap<String, MockResponse>,
750    /// Warnings encountered during generation
751    pub warnings: Vec<String>,
752    /// OpenAPI specification info
753    pub spec_info: OpenApiInfo,
754}
755
756/// Mock response data
757#[derive(Debug, Clone, serde::Serialize)]
758pub struct MockResponse {
759    /// HTTP status code
760    pub status: u16,
761    /// Response headers
762    pub headers: HashMap<String, String>,
763    /// Response body
764    pub body: Value,
765}
766
767/// OpenAPI specification info
768#[derive(Debug, Clone, serde::Serialize)]
769pub struct OpenApiInfo {
770    /// API title
771    pub title: String,
772    /// API version
773    pub version: String,
774    /// API description
775    pub description: Option<String>,
776}
777
778/// Simplified OpenAPI spec structure
779#[derive(Debug)]
780struct OpenApiSpec {
781    info: OpenApiInfo,
782    paths: HashMap<String, PathItem>,
783}
784
785/// Simplified path item structure
786#[derive(Debug)]
787struct PathItem {
788    operations: HashMap<String, openapiv3::Operation>,
789}
790
791impl PathItem {
792    fn operations(&self) -> &HashMap<String, openapiv3::Operation> {
793        &self.operations
794    }
795}
796
797#[cfg(test)]
798mod tests {
799    use super::*;
800
801    #[test]
802    fn test_mock_generator_config_default() {
803        let config = MockGeneratorConfig::default();
804
805        assert!(config.realistic_mode);
806        assert_eq!(config.default_array_size, 3);
807        assert_eq!(config.max_array_size, 10);
808        assert!(config.include_optional_fields);
809        assert!(config.validate_generated_data);
810    }
811
812    #[test]
813    fn test_mock_generator_config_custom() {
814        let config = MockGeneratorConfig::new()
815            .realistic_mode(false)
816            .default_array_size(5)
817            .max_array_size(20)
818            .include_optional_fields(false)
819            .field_mapping("email".to_string(), "email".to_string())
820            .validate_generated_data(false);
821
822        assert!(!config.realistic_mode);
823        assert_eq!(config.default_array_size, 5);
824        assert_eq!(config.max_array_size, 20);
825        assert!(!config.include_optional_fields);
826        assert!(!config.validate_generated_data);
827        assert!(config.field_mappings.contains_key("email"));
828    }
829
830    #[test]
831    fn test_mock_data_generator_new() {
832        let generator = MockDataGenerator::new();
833
834        assert!(generator.config.realistic_mode);
835        assert!(!generator.field_patterns.is_empty());
836    }
837
838    #[test]
839    fn test_mock_data_generator_with_config() {
840        let config = MockGeneratorConfig::new().realistic_mode(false).default_array_size(10);
841
842        let generator = MockDataGenerator::with_config(config);
843
844        assert!(!generator.config.realistic_mode);
845        assert_eq!(generator.config.default_array_size, 10);
846    }
847
848    #[test]
849    fn test_determine_faker_type_custom_mapping() {
850        let mut config = MockGeneratorConfig::new();
851        config.field_mappings.insert("user_email".to_string(), "email".to_string());
852
853        let generator = MockDataGenerator::with_config(config);
854
855        let field = FieldDefinition::new("user_email".to_string(), "string".to_string());
856        let faker_type = generator.determine_faker_type(&field);
857
858        assert_eq!(faker_type, "email");
859    }
860
861    #[test]
862    fn test_determine_faker_type_pattern_matching() {
863        let generator = MockDataGenerator::new();
864
865        let field = FieldDefinition::new("email_address".to_string(), "string".to_string());
866        let faker_type = generator.determine_faker_type(&field);
867
868        assert_eq!(faker_type, "email");
869    }
870
871    #[test]
872    fn test_determine_faker_type_fallback() {
873        let generator = MockDataGenerator::new();
874
875        let field = FieldDefinition::new("unknown_field".to_string(), "integer".to_string());
876        let faker_type = generator.determine_faker_type(&field);
877
878        assert_eq!(faker_type, "integer");
879    }
880
881    #[test]
882    fn test_field_patterns_creation() {
883        let patterns = MockDataGenerator::create_field_patterns();
884
885        assert!(patterns.contains_key("email"));
886        assert!(patterns.contains_key("name"));
887        assert!(patterns.contains_key("phone"));
888        assert!(patterns.contains_key("address"));
889        assert!(patterns.contains_key("company"));
890        assert!(patterns.contains_key("url"));
891        assert!(patterns.contains_key("date"));
892        assert!(patterns.contains_key("id"));
893        assert!(patterns.contains_key("ip"));
894    }
895
896    #[test]
897    fn test_generate_from_json_schema_simple() {
898        let mut generator = MockDataGenerator::new();
899
900        let schema = json!({
901            "type": "object",
902            "properties": {
903                "name": { "type": "string" },
904                "age": { "type": "integer" },
905                "email": { "type": "string" }
906            },
907            "required": ["name", "age"]
908        });
909
910        let result = generator.generate_from_json_schema(&schema).unwrap();
911
912        assert!(result.is_object());
913        let obj = result.as_object().unwrap();
914        assert!(obj.contains_key("name"));
915        assert!(obj.contains_key("age"));
916        assert!(obj.contains_key("email"));
917    }
918
919    #[test]
920    fn test_generate_from_json_schema_with_constraints() {
921        let mut generator = MockDataGenerator::new();
922
923        let schema = json!({
924            "type": "object",
925            "properties": {
926                "age": {
927                    "type": "integer",
928                    "minimum": 18,
929                    "maximum": 65
930                },
931                "name": {
932                    "type": "string",
933                    "minLength": 5,
934                    "maxLength": 20
935                }
936            }
937        });
938
939        let result = generator.generate_from_json_schema(&schema).unwrap();
940
941        assert!(result.is_object());
942        let obj = result.as_object().unwrap();
943
944        if let Some(age) = obj.get("age") {
945            if let Some(age_num) = age.as_i64() {
946                assert!(age_num >= 18);
947                assert!(age_num <= 65);
948            }
949        }
950    }
951
952    #[test]
953    fn test_generate_from_json_schema_with_enum() {
954        let mut generator = MockDataGenerator::new();
955
956        let schema = json!({
957            "type": "object",
958            "properties": {
959                "status": {
960                    "type": "string",
961                    "enum": ["active", "inactive", "pending"]
962                }
963            }
964        });
965
966        let result = generator.generate_from_json_schema(&schema).unwrap();
967
968        assert!(result.is_object());
969        let obj = result.as_object().unwrap();
970
971        if let Some(status) = obj.get("status") {
972            if let Some(status_str) = status.as_str() {
973                assert!(["active", "inactive", "pending"].contains(&status_str));
974            }
975        }
976    }
977}