Skip to main content

mockforge_openapi/response/
mod.rs

1//! OpenAPI response generation and mocking
2//!
3//! This module provides functionality for generating mock responses
4//! based on OpenAPI specifications.
5
6mod ai_assisted;
7mod schema_based;
8
9use crate::OpenApiSpec;
10use async_trait::async_trait;
11use chrono;
12use mockforge_foundation::ai_response::{AiResponseConfig, RequestContext};
13use mockforge_foundation::error::Result;
14use mockforge_foundation::intelligent_behavior::Persona;
15use openapiv3::{Operation, ReferenceOr, Response, Responses, Schema};
16use rand::Rng;
17use serde_json::Value;
18use std::collections::HashMap;
19use uuid;
20
21/// Trait for AI response generation
22///
23/// This trait allows the HTTP layer to provide custom AI generation
24/// implementations without creating circular dependencies between crates.
25#[async_trait]
26pub trait AiGenerator: Send + Sync {
27    /// Generate an AI response from a prompt
28    ///
29    /// # Arguments
30    /// * `prompt` - The expanded prompt to send to the LLM
31    /// * `config` - The AI response configuration with temperature, max_tokens, etc.
32    ///
33    /// # Returns
34    /// A JSON value containing the generated response
35    async fn generate(&self, prompt: &str, config: &AiResponseConfig) -> Result<Value>;
36}
37
38/// Response generator for creating mock responses
39pub struct ResponseGenerator;
40
41impl ResponseGenerator {
42    /// Generate a mock response for an operation and status code
43    pub fn generate_response(
44        spec: &OpenApiSpec,
45        operation: &Operation,
46        status_code: u16,
47        content_type: Option<&str>,
48    ) -> Result<Value> {
49        Self::generate_response_with_expansion(spec, operation, status_code, content_type, true)
50    }
51
52    /// Generate a mock response for an operation and status code with token expansion control
53    pub fn generate_response_with_expansion(
54        spec: &OpenApiSpec,
55        operation: &Operation,
56        status_code: u16,
57        content_type: Option<&str>,
58        expand_tokens: bool,
59    ) -> Result<Value> {
60        Self::generate_response_with_expansion_and_mode(
61            spec,
62            operation,
63            status_code,
64            content_type,
65            expand_tokens,
66            None,
67            None,
68        )
69    }
70
71    /// Generate response with token expansion and selection mode
72    pub fn generate_response_with_expansion_and_mode(
73        spec: &OpenApiSpec,
74        operation: &Operation,
75        status_code: u16,
76        content_type: Option<&str>,
77        expand_tokens: bool,
78        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
79        selector: Option<&crate::response_selection::ResponseSelector>,
80    ) -> Result<Value> {
81        Self::generate_response_with_expansion_and_mode_and_persona(
82            spec,
83            operation,
84            status_code,
85            content_type,
86            expand_tokens,
87            selection_mode,
88            selector,
89            None, // No persona by default
90        )
91    }
92
93    /// Generate response with token expansion, selection mode, and persona
94    #[allow(clippy::too_many_arguments)]
95    pub fn generate_response_with_expansion_and_mode_and_persona(
96        spec: &OpenApiSpec,
97        operation: &Operation,
98        status_code: u16,
99        content_type: Option<&str>,
100        expand_tokens: bool,
101        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
102        selector: Option<&crate::response_selection::ResponseSelector>,
103        persona: Option<&Persona>,
104    ) -> Result<Value> {
105        Self::generate_response_with_scenario_and_mode_and_persona(
106            spec,
107            operation,
108            status_code,
109            content_type,
110            expand_tokens,
111            None, // No scenario by default
112            selection_mode,
113            selector,
114            persona,
115        )
116    }
117
118    /// Generate a mock response with scenario support
119    ///
120    /// This method allows selection of specific example scenarios from the OpenAPI spec.
121    /// Scenarios are defined using the standard OpenAPI `examples` field (not the singular `example`).
122    ///
123    /// # Arguments
124    /// * `spec` - The OpenAPI specification
125    /// * `operation` - The operation to generate a response for
126    /// * `status_code` - The HTTP status code
127    /// * `content_type` - Optional content type (e.g., "application/json")
128    /// * `expand_tokens` - Whether to expand template tokens like {{now}} and {{uuid}}
129    /// * `scenario` - Optional scenario name to select from the examples map
130    ///
131    /// # Example
132    /// ```yaml
133    /// responses:
134    ///   '200':
135    ///     content:
136    ///       application/json:
137    ///         examples:
138    ///           happy:
139    ///             value: { "status": "success", "message": "All good!" }
140    ///           error:
141    ///             value: { "status": "error", "message": "Something went wrong" }
142    /// ```
143    pub fn generate_response_with_scenario(
144        spec: &OpenApiSpec,
145        operation: &Operation,
146        status_code: u16,
147        content_type: Option<&str>,
148        expand_tokens: bool,
149        scenario: Option<&str>,
150    ) -> Result<Value> {
151        Self::generate_response_with_scenario_and_mode(
152            spec,
153            operation,
154            status_code,
155            content_type,
156            expand_tokens,
157            scenario,
158            None,
159            None,
160        )
161    }
162
163    /// Generate response with scenario support and selection mode
164    #[allow(clippy::too_many_arguments)]
165    pub fn generate_response_with_scenario_and_mode(
166        spec: &OpenApiSpec,
167        operation: &Operation,
168        status_code: u16,
169        content_type: Option<&str>,
170        expand_tokens: bool,
171        scenario: Option<&str>,
172        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
173        selector: Option<&crate::response_selection::ResponseSelector>,
174    ) -> Result<Value> {
175        Self::generate_response_with_scenario_and_mode_and_persona(
176            spec,
177            operation,
178            status_code,
179            content_type,
180            expand_tokens,
181            scenario,
182            selection_mode,
183            selector,
184            None, // No persona by default
185        )
186    }
187
188    /// Generate response with scenario support, selection mode, and persona
189    #[allow(clippy::too_many_arguments)]
190    pub fn generate_response_with_scenario_and_mode_and_persona(
191        spec: &OpenApiSpec,
192        operation: &Operation,
193        status_code: u16,
194        content_type: Option<&str>,
195        expand_tokens: bool,
196        scenario: Option<&str>,
197        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
198        selector: Option<&crate::response_selection::ResponseSelector>,
199        _persona: Option<&Persona>,
200    ) -> Result<Value> {
201        // Find the response for the status code
202        let response = Self::find_response_for_status(&operation.responses, status_code);
203
204        tracing::debug!(
205            "Finding response for status code {}: {:?}",
206            status_code,
207            if response.is_some() {
208                "found"
209            } else {
210                "not found"
211            }
212        );
213
214        match response {
215            Some(response_ref) => {
216                match response_ref {
217                    ReferenceOr::Item(response) => {
218                        tracing::debug!(
219                            "Using direct response item with {} content types",
220                            response.content.len()
221                        );
222                        Self::generate_from_response_with_scenario_and_mode(
223                            spec,
224                            response,
225                            content_type,
226                            expand_tokens,
227                            scenario,
228                            selection_mode,
229                            selector,
230                        )
231                    }
232                    ReferenceOr::Reference { reference } => {
233                        tracing::debug!("Resolving response reference: {}", reference);
234                        // Resolve the reference
235                        if let Some(resolved_response) = spec.get_response(reference) {
236                            tracing::debug!(
237                                "Resolved response reference with {} content types",
238                                resolved_response.content.len()
239                            );
240                            Self::generate_from_response_with_scenario_and_mode(
241                                spec,
242                                resolved_response,
243                                content_type,
244                                expand_tokens,
245                                scenario,
246                                selection_mode,
247                                selector,
248                            )
249                        } else {
250                            tracing::warn!("Response reference '{}' not found in spec", reference);
251                            // Reference not found, return empty object
252                            Ok(Value::Object(serde_json::Map::new()))
253                        }
254                    }
255                }
256            }
257            None => {
258                tracing::warn!(
259                    "No response found for status code {} in operation. Available status codes: {:?}",
260                    status_code,
261                    operation.responses.responses.keys().collect::<Vec<_>>()
262                );
263                // No response found for this status code
264                Ok(Value::Object(serde_json::Map::new()))
265            }
266        }
267    }
268
269    /// Find response for a given status code
270    fn find_response_for_status(
271        responses: &Responses,
272        status_code: u16,
273    ) -> Option<&ReferenceOr<Response>> {
274        // First try exact match
275        if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(status_code)) {
276            return Some(response);
277        }
278
279        // Try default response
280        if let Some(default_response) = &responses.default {
281            return Some(default_response);
282        }
283
284        None
285    }
286
287    /// Generate response from a Response object
288    #[allow(dead_code)]
289    fn generate_from_response(
290        spec: &OpenApiSpec,
291        response: &Response,
292        content_type: Option<&str>,
293        expand_tokens: bool,
294    ) -> Result<Value> {
295        Self::generate_from_response_with_scenario(
296            spec,
297            response,
298            content_type,
299            expand_tokens,
300            None,
301        )
302    }
303
304    /// Generate response from a Response object with scenario support
305    #[allow(dead_code)]
306    fn generate_from_response_with_scenario(
307        spec: &OpenApiSpec,
308        response: &Response,
309        content_type: Option<&str>,
310        expand_tokens: bool,
311        scenario: Option<&str>,
312    ) -> Result<Value> {
313        Self::generate_from_response_with_scenario_and_mode(
314            spec,
315            response,
316            content_type,
317            expand_tokens,
318            scenario,
319            None,
320            None,
321        )
322    }
323
324    /// Generate response from a Response object with scenario support and selection mode
325    fn generate_from_response_with_scenario_and_mode(
326        spec: &OpenApiSpec,
327        response: &Response,
328        content_type: Option<&str>,
329        expand_tokens: bool,
330        scenario: Option<&str>,
331        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
332        selector: Option<&crate::response_selection::ResponseSelector>,
333    ) -> Result<Value> {
334        Self::generate_from_response_with_scenario_and_mode_and_persona(
335            spec,
336            response,
337            content_type,
338            expand_tokens,
339            scenario,
340            selection_mode,
341            selector,
342            None, // No persona by default
343        )
344    }
345
346    /// Generate response from a Response object with scenario support, selection mode, and persona
347    #[allow(clippy::too_many_arguments)]
348    #[allow(dead_code)]
349    fn generate_from_response_with_scenario_and_mode_and_persona(
350        spec: &OpenApiSpec,
351        response: &Response,
352        content_type: Option<&str>,
353        expand_tokens: bool,
354        scenario: Option<&str>,
355        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
356        selector: Option<&crate::response_selection::ResponseSelector>,
357        persona: Option<&Persona>,
358    ) -> Result<Value> {
359        // If content_type is specified, look for that media type
360        if let Some(content_type) = content_type {
361            if let Some(media_type) = response.content.get(content_type) {
362                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
363                    spec,
364                    media_type,
365                    expand_tokens,
366                    scenario,
367                    selection_mode,
368                    selector,
369                    persona,
370                );
371            }
372        }
373
374        // If no content_type specified or not found, try common content types
375        let preferred_types = ["application/json", "application/xml", "text/plain"];
376
377        for content_type in &preferred_types {
378            if let Some(media_type) = response.content.get(*content_type) {
379                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
380                    spec,
381                    media_type,
382                    expand_tokens,
383                    scenario,
384                    selection_mode,
385                    selector,
386                    persona,
387                );
388            }
389        }
390
391        // If no suitable content type found, return the first available
392        if let Some((_, media_type)) = response.content.iter().next() {
393            return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
394                spec,
395                media_type,
396                expand_tokens,
397                scenario,
398                selection_mode,
399                selector,
400                persona,
401            );
402        }
403
404        // No content found, return empty object
405        Ok(Value::Object(serde_json::Map::new()))
406    }
407
408    /// Generate response from a MediaType with optional scenario selection
409    #[allow(dead_code)]
410    fn generate_from_media_type(
411        spec: &OpenApiSpec,
412        media_type: &openapiv3::MediaType,
413        expand_tokens: bool,
414    ) -> Result<Value> {
415        Self::generate_from_media_type_with_scenario(spec, media_type, expand_tokens, None)
416    }
417
418    /// Generate response from a MediaType with scenario support and selection mode
419    #[allow(dead_code)]
420    fn generate_from_media_type_with_scenario(
421        spec: &OpenApiSpec,
422        media_type: &openapiv3::MediaType,
423        expand_tokens: bool,
424        scenario: Option<&str>,
425    ) -> Result<Value> {
426        Self::generate_from_media_type_with_scenario_and_mode(
427            spec,
428            media_type,
429            expand_tokens,
430            scenario,
431            None,
432            None,
433        )
434    }
435
436    /// Generate response from a MediaType with scenario support and selection mode (6 args)
437    #[allow(dead_code)]
438    fn generate_from_media_type_with_scenario_and_mode(
439        spec: &OpenApiSpec,
440        media_type: &openapiv3::MediaType,
441        expand_tokens: bool,
442        scenario: Option<&str>,
443        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
444        selector: Option<&crate::response_selection::ResponseSelector>,
445    ) -> Result<Value> {
446        Self::generate_from_media_type_with_scenario_and_mode_and_persona(
447            spec,
448            media_type,
449            expand_tokens,
450            scenario,
451            selection_mode,
452            selector,
453            None, // No persona by default
454        )
455    }
456
457    /// Generate response from a MediaType with scenario support, selection mode, and persona
458    fn generate_from_media_type_with_scenario_and_mode_and_persona(
459        spec: &OpenApiSpec,
460        media_type: &openapiv3::MediaType,
461        expand_tokens: bool,
462        scenario: Option<&str>,
463        selection_mode: Option<crate::response_selection::ResponseSelectionMode>,
464        selector: Option<&crate::response_selection::ResponseSelector>,
465        persona: Option<&Persona>,
466    ) -> Result<Value> {
467        // First, check if there's an explicit example
468        // CRITICAL: Always check examples first before falling back to schema generation
469        // This ensures GET requests use the correct response format from OpenAPI examples
470        if let Some(example) = &media_type.example {
471            tracing::debug!("Using explicit example from media type: {:?}", example);
472            // Expand templates in the example if enabled
473            if expand_tokens {
474                let expanded_example = Self::expand_templates(example);
475                return Ok(expanded_example);
476            } else {
477                return Ok(example.clone());
478            }
479        }
480
481        // Then check examples map - with scenario support and selection modes
482        // CRITICAL: Always use examples if available, even if query parameters are missing
483        // This fixes the bug where GET requests without query params return POST-style responses
484        if !media_type.examples.is_empty() {
485            use crate::response_selection::{ResponseSelectionMode, ResponseSelector};
486
487            tracing::debug!(
488                "Found {} examples in media type, available examples: {:?}",
489                media_type.examples.len(),
490                media_type.examples.keys().collect::<Vec<_>>()
491            );
492
493            // If a scenario is specified, try to find it first
494            if let Some(scenario_name) = scenario {
495                if let Some(example_ref) = media_type.examples.get(scenario_name) {
496                    tracing::debug!("Using scenario '{}' from examples map", scenario_name);
497                    match Self::extract_example_value_with_persona(
498                        spec,
499                        example_ref,
500                        expand_tokens,
501                        persona,
502                        media_type.schema.as_ref(),
503                    ) {
504                        Ok(value) => return Ok(value),
505                        Err(e) => {
506                            tracing::warn!(
507                                "Failed to extract example for scenario '{}': {}, falling back",
508                                scenario_name,
509                                e
510                            );
511                        }
512                    }
513                } else {
514                    tracing::warn!(
515                        "Scenario '{}' not found in examples, falling back based on selection mode",
516                        scenario_name
517                    );
518                }
519            }
520
521            // Determine selection mode
522            let mode = selection_mode.unwrap_or(ResponseSelectionMode::First);
523
524            // Get list of example names for selection
525            let example_names: Vec<String> = media_type.examples.keys().cloned().collect();
526
527            if example_names.is_empty() {
528                // No examples available, fall back to schema
529                tracing::warn!("Examples map is empty, falling back to schema generation");
530            } else if mode == ResponseSelectionMode::Scenario && scenario.is_some() {
531                // Scenario mode was requested but scenario not found, fall through to selection mode
532                tracing::debug!("Scenario not found, using selection mode: {:?}", mode);
533            } else {
534                // Use selection mode to choose an example
535                let selected_index = if let Some(sel) = selector {
536                    sel.select(&example_names)
537                } else {
538                    // Create temporary selector for this selection
539                    let temp_selector = ResponseSelector::new(mode);
540                    temp_selector.select(&example_names)
541                };
542
543                if let Some(example_name) = example_names.get(selected_index) {
544                    if let Some(example_ref) = media_type.examples.get(example_name) {
545                        tracing::debug!(
546                            "Using example '{}' from examples map (mode: {:?}, index: {})",
547                            example_name,
548                            mode,
549                            selected_index
550                        );
551                        match Self::extract_example_value_with_persona(
552                            spec,
553                            example_ref,
554                            expand_tokens,
555                            persona,
556                            media_type.schema.as_ref(),
557                        ) {
558                            Ok(value) => return Ok(value),
559                            Err(e) => {
560                                tracing::warn!(
561                                    "Failed to extract example '{}': {}, trying fallback",
562                                    example_name,
563                                    e
564                                );
565                            }
566                        }
567                    }
568                }
569            }
570
571            // Fall back to first example if selection failed
572            // This is critical - always use the first example if available, even if previous attempts failed
573            if let Some((example_name, example_ref)) = media_type.examples.iter().next() {
574                tracing::debug!(
575                    "Using first example '{}' from examples map as fallback",
576                    example_name
577                );
578                match Self::extract_example_value_with_persona(
579                    spec,
580                    example_ref,
581                    expand_tokens,
582                    persona,
583                    media_type.schema.as_ref(),
584                ) {
585                    Ok(value) => {
586                        tracing::debug!(
587                            "Successfully extracted fallback example '{}'",
588                            example_name
589                        );
590                        return Ok(value);
591                    }
592                    Err(e) => {
593                        tracing::error!(
594                            "Failed to extract fallback example '{}': {}, falling back to schema generation",
595                            example_name,
596                            e
597                        );
598                        // Continue to schema generation as last resort
599                    }
600                }
601            }
602        } else {
603            tracing::debug!("No examples found in media type, will use schema generation");
604        }
605
606        // Fall back to schema-based generation
607        // Pass persona through to schema generation for consistent data patterns
608        if let Some(schema_ref) = &media_type.schema {
609            Ok(Self::generate_example_from_schema_ref(spec, schema_ref, persona))
610        } else {
611            Ok(Value::Object(serde_json::Map::new()))
612        }
613    }
614
615    /// Extract value from an example reference
616    /// Optionally expands items arrays based on pagination metadata if persona is provided
617    #[allow(dead_code)]
618    fn extract_example_value(
619        spec: &OpenApiSpec,
620        example_ref: &ReferenceOr<openapiv3::Example>,
621        expand_tokens: bool,
622    ) -> Result<Value> {
623        Self::extract_example_value_with_persona(spec, example_ref, expand_tokens, None, None)
624    }
625
626    /// Extract value from an example reference with optional persona and schema for pagination expansion
627    fn extract_example_value_with_persona(
628        spec: &OpenApiSpec,
629        example_ref: &ReferenceOr<openapiv3::Example>,
630        expand_tokens: bool,
631        persona: Option<&Persona>,
632        schema_ref: Option<&ReferenceOr<Schema>>,
633    ) -> Result<Value> {
634        let mut value = match example_ref {
635            ReferenceOr::Item(example) => {
636                if let Some(v) = &example.value {
637                    tracing::debug!("Using example from examples map: {:?}", v);
638                    if expand_tokens {
639                        Self::expand_templates(v)
640                    } else {
641                        v.clone()
642                    }
643                } else {
644                    return Ok(Value::Object(serde_json::Map::new()));
645                }
646            }
647            ReferenceOr::Reference { reference } => {
648                // Resolve the example reference
649                if let Some(example) = spec.get_example(reference) {
650                    if let Some(v) = &example.value {
651                        tracing::debug!("Using resolved example reference: {:?}", v);
652                        if expand_tokens {
653                            Self::expand_templates(v)
654                        } else {
655                            v.clone()
656                        }
657                    } else {
658                        return Ok(Value::Object(serde_json::Map::new()));
659                    }
660                } else {
661                    tracing::warn!("Example reference '{}' not found", reference);
662                    return Ok(Value::Object(serde_json::Map::new()));
663                }
664            }
665        };
666
667        // Check for pagination mismatch and expand items array if needed
668        value = Self::expand_example_items_if_needed(spec, value, persona, schema_ref);
669
670        Ok(value)
671    }
672
673    /// Expand items array in example if pagination metadata suggests more items
674    /// Checks for common response structures: { data: { items: [...], total, limit } } or { items: [...], total, limit }
675    fn expand_example_items_if_needed(
676        _spec: &OpenApiSpec,
677        mut example: Value,
678        _persona: Option<&Persona>,
679        _schema_ref: Option<&ReferenceOr<Schema>>,
680    ) -> Value {
681        // Try to find items array and pagination metadata in the example
682        // Support both nested (data.items) and flat (items) structures
683        let has_nested_items = example
684            .get("data")
685            .and_then(|v| v.as_object())
686            .map(|obj| obj.contains_key("items"))
687            .unwrap_or(false);
688
689        let has_flat_items = example.get("items").is_some();
690
691        if !has_nested_items && !has_flat_items {
692            return example; // No items array found
693        }
694
695        // Extract pagination metadata
696        let total = example
697            .get("data")
698            .and_then(|d| d.get("total"))
699            .or_else(|| example.get("total"))
700            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
701
702        let limit = example
703            .get("data")
704            .and_then(|d| d.get("limit"))
705            .or_else(|| example.get("limit"))
706            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
707
708        // Get current items array
709        let items_array = example
710            .get("data")
711            .and_then(|d| d.get("items"))
712            .or_else(|| example.get("items"))
713            .and_then(|v| v.as_array())
714            .cloned();
715
716        if let (Some(total_val), Some(limit_val), Some(mut items)) = (total, limit, items_array) {
717            let current_count = items.len() as u64;
718            let expected_count = std::cmp::min(total_val, limit_val);
719            let max_items = 100; // Cap at reasonable maximum
720            let expected_count = std::cmp::min(expected_count, max_items);
721
722            // If items array is smaller than expected, expand it
723            if current_count < expected_count && !items.is_empty() {
724                tracing::debug!(
725                    "Expanding example items array: {} -> {} items (total={}, limit={})",
726                    current_count,
727                    expected_count,
728                    total_val,
729                    limit_val
730                );
731
732                // Use first item as template
733                let template = items[0].clone();
734                let additional_count = expected_count - current_count;
735
736                // Generate additional items
737                for i in 0..additional_count {
738                    let mut new_item = template.clone();
739                    // Use the centralized variation function
740                    let item_index = current_count + i + 1;
741                    Self::add_item_variation(&mut new_item, item_index);
742                    items.push(new_item);
743                }
744
745                // Update the items array in the example
746                if let Some(data_obj) = example.get_mut("data").and_then(|v| v.as_object_mut()) {
747                    data_obj.insert("items".to_string(), Value::Array(items));
748                } else if let Some(root_obj) = example.as_object_mut() {
749                    root_obj.insert("items".to_string(), Value::Array(items));
750                }
751            }
752        }
753
754        example
755    }
756
757    /// Generate example responses from OpenAPI examples
758    pub fn generate_from_examples(
759        response: &Response,
760        content_type: Option<&str>,
761    ) -> Result<Option<Value>> {
762        use openapiv3::ReferenceOr;
763
764        // If content_type is specified, look for examples in that media type
765        if let Some(content_type) = content_type {
766            if let Some(media_type) = response.content.get(content_type) {
767                // Check for single example first
768                if let Some(example) = &media_type.example {
769                    return Ok(Some(example.clone()));
770                }
771
772                // Check for multiple examples
773                for (_, example_ref) in &media_type.examples {
774                    if let ReferenceOr::Item(example) = example_ref {
775                        if let Some(value) = &example.value {
776                            return Ok(Some(value.clone()));
777                        }
778                    }
779                    // Reference resolution would require spec parameter to be added to this function
780                }
781            }
782        }
783
784        // If no content_type specified or not found, check all media types
785        for (_, media_type) in &response.content {
786            // Check for single example first
787            if let Some(example) = &media_type.example {
788                return Ok(Some(example.clone()));
789            }
790
791            // Check for multiple examples
792            for (_, example_ref) in &media_type.examples {
793                if let ReferenceOr::Item(example) = example_ref {
794                    if let Some(value) = &example.value {
795                        return Ok(Some(value.clone()));
796                    }
797                }
798                // Reference resolution would require spec parameter to be added to this function
799            }
800        }
801
802        Ok(None)
803    }
804
805    /// Expand templates like {{now}} and {{uuid}} in JSON values
806    fn expand_templates(value: &Value) -> Value {
807        match value {
808            Value::String(s) => {
809                let expanded = s
810                    .replace("{{now}}", &chrono::Utc::now().to_rfc3339())
811                    .replace("{{uuid}}", &uuid::Uuid::new_v4().to_string());
812                Value::String(expanded)
813            }
814            Value::Object(map) => {
815                let mut new_map = serde_json::Map::new();
816                for (key, val) in map {
817                    new_map.insert(key.clone(), Self::expand_templates(val));
818                }
819                Value::Object(new_map)
820            }
821            Value::Array(arr) => {
822                let new_arr: Vec<Value> = arr.iter().map(Self::expand_templates).collect();
823                Value::Array(new_arr)
824            }
825            _ => value.clone(),
826        }
827    }
828}
829
830/// Mock response data
831#[derive(Debug, Clone)]
832pub struct MockResponse {
833    /// HTTP status code
834    pub status_code: u16,
835    /// Response headers
836    pub headers: HashMap<String, String>,
837    /// Response body
838    pub body: Option<Value>,
839}
840
841impl MockResponse {
842    /// Create a new mock response
843    pub fn new(status_code: u16) -> Self {
844        Self {
845            status_code,
846            headers: HashMap::new(),
847            body: None,
848        }
849    }
850
851    /// Add a header to the response
852    pub fn with_header(mut self, name: String, value: String) -> Self {
853        self.headers.insert(name, value);
854        self
855    }
856
857    /// Set the response body
858    pub fn with_body(mut self, body: Value) -> Self {
859        self.body = Some(body);
860        self
861    }
862}
863
864/// OpenAPI security requirement wrapper
865#[derive(Debug, Clone)]
866pub struct OpenApiSecurityRequirement {
867    /// The security scheme name
868    pub scheme: String,
869    /// Required scopes (for OAuth2)
870    pub scopes: Vec<String>,
871}
872
873impl OpenApiSecurityRequirement {
874    /// Create a new security requirement
875    pub fn new(scheme: String, scopes: Vec<String>) -> Self {
876        Self { scheme, scopes }
877    }
878}
879
880/// OpenAPI operation wrapper with path context
881#[derive(Debug, Clone)]
882pub struct OpenApiOperation {
883    /// The HTTP method
884    pub method: String,
885    /// The path this operation belongs to
886    pub path: String,
887    /// The OpenAPI operation
888    pub operation: Operation,
889}
890
891impl OpenApiOperation {
892    /// Create a new OpenApiOperation
893    pub fn new(method: String, path: String, operation: Operation) -> Self {
894        Self {
895            method,
896            path,
897            operation,
898        }
899    }
900}
901
902#[cfg(test)]
903mod tests {
904    use super::*;
905    use openapiv3::ReferenceOr;
906    use serde_json::json;
907
908    // Mock AI generator for testing
909    struct MockAiGenerator {
910        response: Value,
911    }
912
913    #[async_trait]
914    impl AiGenerator for MockAiGenerator {
915        async fn generate(&self, _prompt: &str, _config: &AiResponseConfig) -> Result<Value> {
916            Ok(self.response.clone())
917        }
918    }
919
920    #[test]
921    fn generates_example_using_referenced_schemas() {
922        let yaml = r#"
923openapi: 3.0.3
924info:
925  title: Test API
926  version: "1.0.0"
927paths:
928  /apiaries:
929    get:
930      responses:
931        '200':
932          description: ok
933          content:
934            application/json:
935              schema:
936                $ref: '#/components/schemas/Apiary'
937components:
938  schemas:
939    Apiary:
940      type: object
941      properties:
942        id:
943          type: string
944        hive:
945          $ref: '#/components/schemas/Hive'
946    Hive:
947      type: object
948      properties:
949        name:
950          type: string
951        active:
952          type: boolean
953        "#;
954
955        let spec = OpenApiSpec::from_string(yaml, Some("yaml")).expect("load spec");
956        let path_item = spec
957            .spec
958            .paths
959            .paths
960            .get("/apiaries")
961            .and_then(ReferenceOr::as_item)
962            .expect("path item");
963        let operation = path_item.get.as_ref().expect("GET operation");
964
965        let response =
966            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
967                .expect("generate response");
968
969        let obj = response.as_object().expect("response object");
970        assert!(obj.contains_key("id"));
971        let hive = obj.get("hive").and_then(|value| value.as_object()).expect("hive object");
972        assert!(hive.contains_key("name"));
973        assert!(hive.contains_key("active"));
974    }
975
976    #[tokio::test]
977    async fn test_generate_ai_response_with_generator() {
978        let ai_config = AiResponseConfig {
979            enabled: true,
980            mode: mockforge_foundation::ai_response::AiResponseMode::Intelligent,
981            prompt: Some("Generate a response for {{method}} {{path}}".to_string()),
982            context: None,
983            temperature: 0.7,
984            max_tokens: 1000,
985            schema: None,
986            cache_enabled: true,
987        };
988        let context = RequestContext {
989            method: "GET".to_string(),
990            path: "/api/users".to_string(),
991            path_params: HashMap::new(),
992            query_params: HashMap::new(),
993            headers: HashMap::new(),
994            body: None,
995            multipart_fields: HashMap::new(),
996            multipart_files: HashMap::new(),
997        };
998        let mock_generator = MockAiGenerator {
999            response: json!({"message": "Generated response"}),
1000        };
1001
1002        let result =
1003            ResponseGenerator::generate_ai_response(&ai_config, &context, Some(&mock_generator))
1004                .await;
1005
1006        assert!(result.is_ok());
1007        let value = result.unwrap();
1008        assert_eq!(value["message"], "Generated response");
1009    }
1010
1011    #[tokio::test]
1012    async fn test_generate_ai_response_without_generator() {
1013        let ai_config = AiResponseConfig {
1014            enabled: true,
1015            mode: mockforge_foundation::ai_response::AiResponseMode::Intelligent,
1016            prompt: Some("Generate a response for {{method}} {{path}}".to_string()),
1017            context: None,
1018            temperature: 0.7,
1019            max_tokens: 1000,
1020            schema: None,
1021            cache_enabled: true,
1022        };
1023        let context = RequestContext {
1024            method: "POST".to_string(),
1025            path: "/api/users".to_string(),
1026            path_params: HashMap::new(),
1027            query_params: HashMap::new(),
1028            headers: HashMap::new(),
1029            body: None,
1030            multipart_fields: HashMap::new(),
1031            multipart_files: HashMap::new(),
1032        };
1033
1034        let result = ResponseGenerator::generate_ai_response(&ai_config, &context, None).await;
1035
1036        // Without a generator, generate_ai_response returns an error
1037        assert!(result.is_err());
1038        let err = result.unwrap_err().to_string();
1039        assert!(
1040            err.contains("no AI generator configured"),
1041            "Expected 'no AI generator configured' error, got: {}",
1042            err
1043        );
1044    }
1045
1046    #[tokio::test]
1047    async fn test_generate_ai_response_no_prompt() {
1048        let ai_config = AiResponseConfig {
1049            enabled: true,
1050            mode: mockforge_foundation::ai_response::AiResponseMode::Intelligent,
1051            prompt: None,
1052            context: None,
1053            temperature: 0.7,
1054            max_tokens: 1000,
1055            schema: None,
1056            cache_enabled: true,
1057        };
1058        let context = RequestContext {
1059            method: "GET".to_string(),
1060            path: "/api/test".to_string(),
1061            path_params: HashMap::new(),
1062            query_params: HashMap::new(),
1063            headers: HashMap::new(),
1064            body: None,
1065            multipart_fields: HashMap::new(),
1066            multipart_files: HashMap::new(),
1067        };
1068
1069        let result = ResponseGenerator::generate_ai_response(&ai_config, &context, None).await;
1070
1071        assert!(result.is_err());
1072    }
1073
1074    #[test]
1075    fn test_generate_response_with_expansion() {
1076        let spec = OpenApiSpec::from_string(
1077            r#"openapi: 3.0.0
1078info:
1079  title: Test API
1080  version: 1.0.0
1081paths:
1082  /users:
1083    get:
1084      responses:
1085        '200':
1086          description: OK
1087          content:
1088            application/json:
1089              schema:
1090                type: object
1091                properties:
1092                  id:
1093                    type: integer
1094                  name:
1095                    type: string
1096"#,
1097            Some("yaml"),
1098        )
1099        .unwrap();
1100
1101        let operation = spec
1102            .spec
1103            .paths
1104            .paths
1105            .get("/users")
1106            .and_then(|p| p.as_item())
1107            .and_then(|p| p.get.as_ref())
1108            .unwrap();
1109
1110        let response = ResponseGenerator::generate_response_with_expansion(
1111            &spec,
1112            operation,
1113            200,
1114            Some("application/json"),
1115            true,
1116        )
1117        .unwrap();
1118
1119        assert!(response.is_object());
1120    }
1121
1122    #[test]
1123    fn test_generate_response_with_scenario() {
1124        let spec = OpenApiSpec::from_string(
1125            r#"openapi: 3.0.0
1126info:
1127  title: Test API
1128  version: 1.0.0
1129paths:
1130  /users:
1131    get:
1132      responses:
1133        '200':
1134          description: OK
1135          content:
1136            application/json:
1137              examples:
1138                happy:
1139                  value:
1140                    id: 1
1141                    name: "Happy User"
1142                sad:
1143                  value:
1144                    id: 2
1145                    name: "Sad User"
1146"#,
1147            Some("yaml"),
1148        )
1149        .unwrap();
1150
1151        let operation = spec
1152            .spec
1153            .paths
1154            .paths
1155            .get("/users")
1156            .and_then(|p| p.as_item())
1157            .and_then(|p| p.get.as_ref())
1158            .unwrap();
1159
1160        let response = ResponseGenerator::generate_response_with_scenario(
1161            &spec,
1162            operation,
1163            200,
1164            Some("application/json"),
1165            false,
1166            Some("happy"),
1167        )
1168        .unwrap();
1169
1170        assert_eq!(response["id"], 1);
1171        assert_eq!(response["name"], "Happy User");
1172    }
1173
1174    #[test]
1175    fn test_generate_response_with_referenced_response() {
1176        let spec = OpenApiSpec::from_string(
1177            r#"openapi: 3.0.0
1178info:
1179  title: Test API
1180  version: 1.0.0
1181paths:
1182  /users:
1183    get:
1184      responses:
1185        '200':
1186          $ref: '#/components/responses/UserResponse'
1187components:
1188  responses:
1189    UserResponse:
1190      description: User response
1191      content:
1192        application/json:
1193          schema:
1194            type: object
1195            properties:
1196              id:
1197                type: integer
1198              name:
1199                type: string
1200"#,
1201            Some("yaml"),
1202        )
1203        .unwrap();
1204
1205        let operation = spec
1206            .spec
1207            .paths
1208            .paths
1209            .get("/users")
1210            .and_then(|p| p.as_item())
1211            .and_then(|p| p.get.as_ref())
1212            .unwrap();
1213
1214        let response =
1215            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1216                .unwrap();
1217
1218        assert!(response.is_object());
1219    }
1220
1221    #[test]
1222    fn test_generate_response_with_default_status() {
1223        let spec = OpenApiSpec::from_string(
1224            r#"openapi: 3.0.0
1225info:
1226  title: Test API
1227  version: 1.0.0
1228paths:
1229  /users:
1230    get:
1231      responses:
1232        '200':
1233          description: OK
1234        default:
1235          description: Error
1236          content:
1237            application/json:
1238              schema:
1239                type: object
1240                properties:
1241                  error:
1242                    type: string
1243"#,
1244            Some("yaml"),
1245        )
1246        .unwrap();
1247
1248        let operation = spec
1249            .spec
1250            .paths
1251            .paths
1252            .get("/users")
1253            .and_then(|p| p.as_item())
1254            .and_then(|p| p.get.as_ref())
1255            .unwrap();
1256
1257        // Use default response for 500 status
1258        let response =
1259            ResponseGenerator::generate_response(&spec, operation, 500, Some("application/json"))
1260                .unwrap();
1261
1262        assert!(response.is_object());
1263    }
1264
1265    #[test]
1266    fn test_generate_response_with_example_in_media_type() {
1267        let spec = OpenApiSpec::from_string(
1268            r#"openapi: 3.0.0
1269info:
1270  title: Test API
1271  version: 1.0.0
1272paths:
1273  /users:
1274    get:
1275      responses:
1276        '200':
1277          description: OK
1278          content:
1279            application/json:
1280              example:
1281                id: 1
1282                name: "Example User"
1283"#,
1284            Some("yaml"),
1285        )
1286        .unwrap();
1287
1288        let operation = spec
1289            .spec
1290            .paths
1291            .paths
1292            .get("/users")
1293            .and_then(|p| p.as_item())
1294            .and_then(|p| p.get.as_ref())
1295            .unwrap();
1296
1297        let response =
1298            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1299                .unwrap();
1300
1301        assert_eq!(response["id"], 1);
1302        assert_eq!(response["name"], "Example User");
1303    }
1304
1305    #[test]
1306    fn test_generate_response_with_schema_example() {
1307        let spec = OpenApiSpec::from_string(
1308            r#"openapi: 3.0.0
1309info:
1310  title: Test API
1311  version: 1.0.0
1312paths:
1313  /users:
1314    get:
1315      responses:
1316        '200':
1317          description: OK
1318          content:
1319            application/json:
1320              schema:
1321                type: object
1322                example:
1323                  id: 42
1324                  name: "Schema Example"
1325                properties:
1326                  id:
1327                    type: integer
1328                  name:
1329                    type: string
1330"#,
1331            Some("yaml"),
1332        )
1333        .unwrap();
1334
1335        let operation = spec
1336            .spec
1337            .paths
1338            .paths
1339            .get("/users")
1340            .and_then(|p| p.as_item())
1341            .and_then(|p| p.get.as_ref())
1342            .unwrap();
1343
1344        let response =
1345            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1346                .unwrap();
1347
1348        // Should use schema example if available
1349        assert!(response.is_object());
1350    }
1351
1352    #[test]
1353    fn test_generate_response_with_referenced_schema() {
1354        let spec = OpenApiSpec::from_string(
1355            r#"openapi: 3.0.0
1356info:
1357  title: Test API
1358  version: 1.0.0
1359paths:
1360  /users:
1361    get:
1362      responses:
1363        '200':
1364          description: OK
1365          content:
1366            application/json:
1367              schema:
1368                $ref: '#/components/schemas/User'
1369components:
1370  schemas:
1371    User:
1372      type: object
1373      properties:
1374        id:
1375          type: integer
1376        name:
1377          type: string
1378"#,
1379            Some("yaml"),
1380        )
1381        .unwrap();
1382
1383        let operation = spec
1384            .spec
1385            .paths
1386            .paths
1387            .get("/users")
1388            .and_then(|p| p.as_item())
1389            .and_then(|p| p.get.as_ref())
1390            .unwrap();
1391
1392        let response =
1393            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1394                .unwrap();
1395
1396        assert!(response.is_object());
1397        assert!(response.get("id").is_some());
1398        assert!(response.get("name").is_some());
1399    }
1400
1401    #[test]
1402    fn test_generate_response_with_array_schema() {
1403        let spec = OpenApiSpec::from_string(
1404            r#"openapi: 3.0.0
1405info:
1406  title: Test API
1407  version: 1.0.0
1408paths:
1409  /users:
1410    get:
1411      responses:
1412        '200':
1413          description: OK
1414          content:
1415            application/json:
1416              schema:
1417                type: array
1418                items:
1419                  type: object
1420                  properties:
1421                    id:
1422                      type: integer
1423                    name:
1424                      type: string
1425"#,
1426            Some("yaml"),
1427        )
1428        .unwrap();
1429
1430        let operation = spec
1431            .spec
1432            .paths
1433            .paths
1434            .get("/users")
1435            .and_then(|p| p.as_item())
1436            .and_then(|p| p.get.as_ref())
1437            .unwrap();
1438
1439        let response =
1440            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1441                .unwrap();
1442
1443        assert!(response.is_array());
1444    }
1445
1446    #[test]
1447    fn test_generate_response_with_different_content_types() {
1448        let spec = OpenApiSpec::from_string(
1449            r#"openapi: 3.0.0
1450info:
1451  title: Test API
1452  version: 1.0.0
1453paths:
1454  /users:
1455    get:
1456      responses:
1457        '200':
1458          description: OK
1459          content:
1460            application/json:
1461              schema:
1462                type: object
1463            text/plain:
1464              schema:
1465                type: string
1466"#,
1467            Some("yaml"),
1468        )
1469        .unwrap();
1470
1471        let operation = spec
1472            .spec
1473            .paths
1474            .paths
1475            .get("/users")
1476            .and_then(|p| p.as_item())
1477            .and_then(|p| p.get.as_ref())
1478            .unwrap();
1479
1480        // Test JSON content type
1481        let json_response =
1482            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1483                .unwrap();
1484        assert!(json_response.is_object());
1485
1486        // Test text/plain content type
1487        let text_response =
1488            ResponseGenerator::generate_response(&spec, operation, 200, Some("text/plain"))
1489                .unwrap();
1490        assert!(text_response.is_string());
1491    }
1492
1493    #[test]
1494    fn test_generate_response_without_content_type() {
1495        let spec = OpenApiSpec::from_string(
1496            r#"openapi: 3.0.0
1497info:
1498  title: Test API
1499  version: 1.0.0
1500paths:
1501  /users:
1502    get:
1503      responses:
1504        '200':
1505          description: OK
1506          content:
1507            application/json:
1508              schema:
1509                type: object
1510                properties:
1511                  id:
1512                    type: integer
1513"#,
1514            Some("yaml"),
1515        )
1516        .unwrap();
1517
1518        let operation = spec
1519            .spec
1520            .paths
1521            .paths
1522            .get("/users")
1523            .and_then(|p| p.as_item())
1524            .and_then(|p| p.get.as_ref())
1525            .unwrap();
1526
1527        // No content type specified - should use first available
1528        let response = ResponseGenerator::generate_response(&spec, operation, 200, None).unwrap();
1529
1530        assert!(response.is_object());
1531    }
1532
1533    #[test]
1534    fn test_generate_response_with_no_content() {
1535        let spec = OpenApiSpec::from_string(
1536            r#"openapi: 3.0.0
1537info:
1538  title: Test API
1539  version: 1.0.0
1540paths:
1541  /users:
1542    delete:
1543      responses:
1544        '204':
1545          description: No Content
1546"#,
1547            Some("yaml"),
1548        )
1549        .unwrap();
1550
1551        let operation = spec
1552            .spec
1553            .paths
1554            .paths
1555            .get("/users")
1556            .and_then(|p| p.as_item())
1557            .and_then(|p| p.delete.as_ref())
1558            .unwrap();
1559
1560        let response = ResponseGenerator::generate_response(&spec, operation, 204, None).unwrap();
1561
1562        // Should return empty object for no content
1563        assert!(response.is_object());
1564        assert!(response.as_object().unwrap().is_empty());
1565    }
1566
1567    #[test]
1568    fn test_generate_response_with_expansion_disabled() {
1569        let spec = OpenApiSpec::from_string(
1570            r#"openapi: 3.0.0
1571info:
1572  title: Test API
1573  version: 1.0.0
1574paths:
1575  /users:
1576    get:
1577      responses:
1578        '200':
1579          description: OK
1580          content:
1581            application/json:
1582              schema:
1583                type: object
1584                properties:
1585                  id:
1586                    type: integer
1587                  name:
1588                    type: string
1589"#,
1590            Some("yaml"),
1591        )
1592        .unwrap();
1593
1594        let operation = spec
1595            .spec
1596            .paths
1597            .paths
1598            .get("/users")
1599            .and_then(|p| p.as_item())
1600            .and_then(|p| p.get.as_ref())
1601            .unwrap();
1602
1603        let response = ResponseGenerator::generate_response_with_expansion(
1604            &spec,
1605            operation,
1606            200,
1607            Some("application/json"),
1608            false, // No expansion
1609        )
1610        .unwrap();
1611
1612        assert!(response.is_object());
1613    }
1614
1615    #[test]
1616    fn test_generate_response_with_array_schema_referenced_items() {
1617        // Test array schema with referenced item schema (lines 1035-1046)
1618        let spec = OpenApiSpec::from_string(
1619            r#"openapi: 3.0.0
1620info:
1621  title: Test API
1622  version: 1.0.0
1623paths:
1624  /items:
1625    get:
1626      responses:
1627        '200':
1628          description: OK
1629          content:
1630            application/json:
1631              schema:
1632                type: array
1633                items:
1634                  $ref: '#/components/schemas/Item'
1635components:
1636  schemas:
1637    Item:
1638      type: object
1639      properties:
1640        id:
1641          type: string
1642        name:
1643          type: string
1644"#,
1645            Some("yaml"),
1646        )
1647        .unwrap();
1648
1649        let operation = spec
1650            .spec
1651            .paths
1652            .paths
1653            .get("/items")
1654            .and_then(|p| p.as_item())
1655            .and_then(|p| p.get.as_ref())
1656            .unwrap();
1657
1658        let response =
1659            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1660                .unwrap();
1661
1662        // Should generate an array with items from referenced schema
1663        let arr = response.as_array().expect("response should be array");
1664        assert!(!arr.is_empty());
1665        if let Some(item) = arr.first() {
1666            let obj = item.as_object().expect("item should be object");
1667            assert!(obj.contains_key("id") || obj.contains_key("name"));
1668        }
1669    }
1670
1671    #[test]
1672    fn test_generate_response_with_array_schema_missing_reference() {
1673        // Test array schema with missing referenced item schema (line 1045)
1674        let spec = OpenApiSpec::from_string(
1675            r#"openapi: 3.0.0
1676info:
1677  title: Test API
1678  version: 1.0.0
1679paths:
1680  /items:
1681    get:
1682      responses:
1683        '200':
1684          description: OK
1685          content:
1686            application/json:
1687              schema:
1688                type: array
1689                items:
1690                  $ref: '#/components/schemas/NonExistentItem'
1691components:
1692  schemas: {}
1693"#,
1694            Some("yaml"),
1695        )
1696        .unwrap();
1697
1698        let operation = spec
1699            .spec
1700            .paths
1701            .paths
1702            .get("/items")
1703            .and_then(|p| p.as_item())
1704            .and_then(|p| p.get.as_ref())
1705            .unwrap();
1706
1707        let response =
1708            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1709                .unwrap();
1710
1711        // Should generate an array with empty objects when reference not found
1712        let arr = response.as_array().expect("response should be array");
1713        assert!(!arr.is_empty());
1714    }
1715
1716    #[test]
1717    fn test_generate_response_with_array_example_and_pagination() {
1718        // Test array generation with pagination using example items (lines 1114-1126)
1719        let spec = OpenApiSpec::from_string(
1720            r#"openapi: 3.0.0
1721info:
1722  title: Test API
1723  version: 1.0.0
1724paths:
1725  /products:
1726    get:
1727      responses:
1728        '200':
1729          description: OK
1730          content:
1731            application/json:
1732              schema:
1733                type: array
1734                example: [{"id": 1, "name": "Product 1"}]
1735                items:
1736                  type: object
1737                  properties:
1738                    id:
1739                      type: integer
1740                    name:
1741                      type: string
1742"#,
1743            Some("yaml"),
1744        )
1745        .unwrap();
1746
1747        let operation = spec
1748            .spec
1749            .paths
1750            .paths
1751            .get("/products")
1752            .and_then(|p| p.as_item())
1753            .and_then(|p| p.get.as_ref())
1754            .unwrap();
1755
1756        let response =
1757            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1758                .unwrap();
1759
1760        // Should generate an array using the example as template
1761        let arr = response.as_array().expect("response should be array");
1762        assert!(!arr.is_empty());
1763        if let Some(item) = arr.first() {
1764            let obj = item.as_object().expect("item should be object");
1765            assert!(obj.contains_key("id") || obj.contains_key("name"));
1766        }
1767    }
1768
1769    #[test]
1770    fn test_generate_response_with_missing_response_reference() {
1771        // Test response generation with missing response reference (lines 294-298)
1772        let spec = OpenApiSpec::from_string(
1773            r#"openapi: 3.0.0
1774info:
1775  title: Test API
1776  version: 1.0.0
1777paths:
1778  /users:
1779    get:
1780      responses:
1781        '200':
1782          $ref: '#/components/responses/NonExistentResponse'
1783components:
1784  responses: {}
1785"#,
1786            Some("yaml"),
1787        )
1788        .unwrap();
1789
1790        let operation = spec
1791            .spec
1792            .paths
1793            .paths
1794            .get("/users")
1795            .and_then(|p| p.as_item())
1796            .and_then(|p| p.get.as_ref())
1797            .unwrap();
1798
1799        let response =
1800            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1801                .unwrap();
1802
1803        // Should return empty object when reference not found
1804        assert!(response.is_object());
1805        assert!(response.as_object().unwrap().is_empty());
1806    }
1807
1808    #[test]
1809    fn test_generate_response_with_no_response_for_status() {
1810        // Test response generation when no response found for status code (lines 302-310)
1811        let spec = OpenApiSpec::from_string(
1812            r#"openapi: 3.0.0
1813info:
1814  title: Test API
1815  version: 1.0.0
1816paths:
1817  /users:
1818    get:
1819      responses:
1820        '404':
1821          description: Not found
1822"#,
1823            Some("yaml"),
1824        )
1825        .unwrap();
1826
1827        let operation = spec
1828            .spec
1829            .paths
1830            .paths
1831            .get("/users")
1832            .and_then(|p| p.as_item())
1833            .and_then(|p| p.get.as_ref())
1834            .unwrap();
1835
1836        // Request status code 200 but only 404 is defined
1837        let response =
1838            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1839                .unwrap();
1840
1841        // Should return empty object when no response found
1842        assert!(response.is_object());
1843        assert!(response.as_object().unwrap().is_empty());
1844    }
1845}