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