Skip to main content

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