Skip to main content

mockforge_intelligence/intelligent_behavior/
mockai.rs

1//! Unified MockAI interface
2//!
3//! This module provides a unified interface for all MockAI features, including
4//! auto-configuration from OpenAPI or examples, intelligent response generation,
5//! and context-aware behavior orchestration.
6
7use super::config::IntelligentBehaviorConfig;
8use super::context::StatefulAiContext;
9use super::mutation_analyzer::MutationAnalyzer;
10use super::pagination_intelligence::{
11    PaginationIntelligence, PaginationMetadata, PaginationRequest,
12};
13use super::rule_generator::{ExamplePair, RuleGenerator};
14use super::types::BehaviorRules;
15use super::validation_generator::{RequestContext, ValidationGenerator};
16use mockforge_foundation::Result;
17use mockforge_openapi::OpenApiSpec;
18use serde_json::Value;
19use std::collections::HashMap;
20use uuid;
21
22// `Request` and `Response` are re-exported from `mockforge_foundation::intelligent_behavior`.
23pub use mockforge_foundation::intelligent_behavior::{Request, Response};
24
25/// MockAI unified interface
26pub struct MockAI {
27    /// Behavior rules
28    rules: BehaviorRules,
29    /// Rule generator for learning
30    rule_generator: RuleGenerator,
31    /// Mutation analyzer
32    mutation_analyzer: MutationAnalyzer,
33    /// Validation generator
34    validation_generator: ValidationGenerator,
35    /// Pagination intelligence
36    pagination_intelligence: PaginationIntelligence,
37    /// Configuration
38    config: IntelligentBehaviorConfig,
39    /// Session contexts for stateful behavior across requests
40    session_contexts: std::sync::Arc<tokio::sync::RwLock<HashMap<String, StatefulAiContext>>>,
41}
42
43#[async_trait::async_trait]
44impl mockforge_foundation::intelligent_behavior::MockAiBehavior for MockAI {
45    fn as_any(&self) -> &dyn std::any::Any {
46        self
47    }
48
49    async fn process_request(&self, request: &Request) -> Result<Response> {
50        // Delegate to the inherent implementation below.
51        Self::process_request(self, request).await
52    }
53}
54
55impl MockAI {
56    /// Create MockAI from OpenAPI specification
57    ///
58    /// Automatically generates behavioral rules from the OpenAPI spec.
59    pub async fn from_openapi(
60        spec: &OpenApiSpec,
61        config: IntelligentBehaviorConfig,
62    ) -> Result<Self> {
63        // Extract examples from OpenAPI spec
64        let examples = Self::extract_examples_from_openapi(spec)?;
65
66        // Generate rules from examples
67        let behavior_config = config.behavior_model.clone();
68        let rule_generator = RuleGenerator::new(behavior_config.clone());
69        let rules = rule_generator.generate_rules_from_examples(examples).await?;
70
71        // Create components
72        let mutation_analyzer = MutationAnalyzer::new().with_rules(rules.clone());
73        let validation_generator = ValidationGenerator::new(behavior_config.clone());
74        let pagination_intelligence = PaginationIntelligence::new(behavior_config);
75
76        Ok(Self {
77            rules,
78            rule_generator,
79            mutation_analyzer,
80            validation_generator,
81            pagination_intelligence,
82            config,
83            session_contexts: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
84        })
85    }
86
87    /// Create MockAI from example pairs
88    ///
89    /// Learns behavioral patterns from provided examples.
90    pub async fn from_examples(
91        examples: Vec<ExamplePair>,
92        config: IntelligentBehaviorConfig,
93    ) -> Result<Self> {
94        // Generate rules from examples
95        let behavior_config = config.behavior_model.clone();
96        let rule_generator = RuleGenerator::new(behavior_config.clone());
97        let rules = rule_generator.generate_rules_from_examples(examples).await?;
98
99        // Create components
100        let mutation_analyzer = MutationAnalyzer::new().with_rules(rules.clone());
101        let validation_generator = ValidationGenerator::new(behavior_config.clone());
102        let pagination_intelligence = PaginationIntelligence::new(behavior_config);
103
104        Ok(Self {
105            rules,
106            rule_generator,
107            mutation_analyzer,
108            validation_generator,
109            pagination_intelligence,
110            config,
111            session_contexts: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
112        })
113    }
114
115    /// Create a new MockAI instance (for testing or manual creation)
116    pub fn new(config: IntelligentBehaviorConfig) -> Self {
117        let behavior_config = config.behavior_model.clone();
118        let rule_generator = RuleGenerator::new(behavior_config.clone());
119        let rules = BehaviorRules::default();
120        let mutation_analyzer = MutationAnalyzer::new().with_rules(rules.clone());
121        let validation_generator = ValidationGenerator::new(behavior_config.clone());
122        let pagination_intelligence = PaginationIntelligence::new(behavior_config);
123
124        Self {
125            rules,
126            rule_generator,
127            mutation_analyzer,
128            validation_generator,
129            pagination_intelligence,
130            config,
131            session_contexts: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
132        }
133    }
134
135    /// Process a request and generate a response
136    ///
137    /// Convenience method that gets or creates a session context and generates a response.
138    /// This is the main entry point for processing HTTP requests.
139    /// Session ID is extracted from headers (X-Session-ID or Cookie) or generated if not present.
140    pub async fn process_request(&self, request: &Request) -> Result<Response> {
141        // Extract session ID from request headers
142        let session_id = self.extract_session_id(request);
143
144        // Get or create session context
145        let session_context = self.get_or_create_session_context(session_id).await?;
146
147        // Generate response using the session context
148        let response = self.generate_response(request, &session_context).await?;
149
150        // Record interaction in session history
151        // Since record_interaction now takes &self (uses internal RwLock),
152        // we can call it directly on the cloned context
153        if let Err(e) = session_context
154            .record_interaction(
155                request.method.clone(),
156                request.path.clone(),
157                request.body.clone(),
158                Some(response.body.clone()),
159            )
160            .await
161        {
162            tracing::warn!("Failed to record interaction: {}", e);
163        }
164
165        Ok(response)
166    }
167
168    /// Extract session ID from request headers
169    fn extract_session_id(&self, request: &Request) -> Option<String> {
170        // Try header first (X-Session-ID)
171        if let Some(session_id) = request.headers.get("X-Session-ID") {
172            return Some(session_id.clone());
173        }
174
175        // Try cookie (mockforge_session)
176        if let Some(cookie_header) = request.headers.get("Cookie") {
177            for part in cookie_header.split(';') {
178                let part = part.trim();
179                if let Some((key, value)) = part.split_once('=') {
180                    if key.trim() == "mockforge_session" {
181                        return Some(value.trim().to_string());
182                    }
183                }
184            }
185        }
186
187        None
188    }
189
190    /// Get or create a session context
191    async fn get_or_create_session_context(
192        &self,
193        session_id: Option<String>,
194    ) -> Result<StatefulAiContext> {
195        let session_id = session_id.unwrap_or_else(|| format!("session_{}", uuid::Uuid::new_v4()));
196
197        // Try to get existing context
198        {
199            let contexts = self.session_contexts.read().await;
200            if let Some(context) = contexts.get(&session_id) {
201                return Ok(context.clone());
202            }
203        }
204
205        // Create new context
206        let new_context = StatefulAiContext::new(session_id.clone(), self.config.clone());
207
208        // Store it
209        {
210            let mut contexts = self.session_contexts.write().await;
211            contexts.insert(session_id, new_context.clone());
212        }
213
214        Ok(new_context)
215    }
216
217    /// Generate response for a request
218    ///
219    /// Uses intelligent behavior to generate contextually appropriate responses
220    /// based on request mutations, validation, and pagination needs.
221    pub async fn generate_response(
222        &self,
223        request: &Request,
224        session_context: &StatefulAiContext,
225    ) -> Result<Response> {
226        // CRITICAL FIX: GET, HEAD, and OPTIONS requests should NEVER be analyzed as mutations
227        // These are idempotent methods that don't mutate state. Only POST, PUT, PATCH, DELETE are mutations.
228        let method_upper = request.method.to_uppercase();
229        let is_mutation_method =
230            matches!(method_upper.as_str(), "POST" | "PUT" | "PATCH" | "DELETE");
231
232        // Get previous request from session history
233        let history = session_context.get_history().await;
234        let previous_request = history.last().and_then(|interaction| interaction.request.clone());
235
236        // Only analyze mutations for mutation methods (POST, PUT, PATCH, DELETE)
237        // GET, HEAD, OPTIONS should use standard OpenAPI response generation, not mutation responses
238        let mutation_analysis = if is_mutation_method {
239            // Analyze mutation for mutation methods
240            let current_body = request.body.clone().unwrap_or(serde_json::json!({}));
241            self.mutation_analyzer
242                .analyze_mutation(&current_body, previous_request.as_ref(), session_context)
243                .await?
244        } else {
245            // For non-mutation methods (GET, HEAD, OPTIONS), create a dummy analysis
246            // that won't trigger mutation-based response generation
247            // This ensures GET requests use OpenAPI examples/schemas, not mutation responses
248            super::mutation_analyzer::MutationAnalysis {
249                mutation_type: super::mutation_analyzer::MutationType::NoChange, // Read operations are not mutations
250                changed_fields: Vec::new(),
251                added_fields: Vec::new(),
252                removed_fields: Vec::new(),
253                validation_issues: Vec::new(),
254                confidence: 1.0,
255            }
256        };
257
258        // Check for validation issues
259        if !mutation_analysis.validation_issues.is_empty() {
260            // Generate validation error response
261            let issue = &mutation_analysis.validation_issues[0];
262            let request_context = RequestContext {
263                method: request.method.clone(),
264                path: request.path.clone(),
265                request_body: request.body.clone(),
266                query_params: request.query_params.clone(),
267                headers: request.headers.clone(),
268            };
269
270            let error_response = self
271                .validation_generator
272                .generate_validation_error(issue, &request_context)
273                .await?;
274
275            return Ok(Response {
276                status_code: error_response.status_code,
277                body: error_response.body,
278                headers: HashMap::new(),
279            });
280        }
281
282        // Check if this is a paginated request
283        if self.is_paginated_request(request) {
284            let pagination_meta =
285                self.generate_pagination_metadata(request, session_context).await?;
286
287            let body = self.build_paginated_response(&pagination_meta, request).await?;
288
289            return Ok(Response {
290                status_code: 200,
291                body,
292                headers: HashMap::new(),
293            });
294        }
295
296        // Generate normal response based on mutation type
297        // For GET/HEAD/OPTIONS (Read operations), this should return an empty object
298        // to signal that OpenAPI response generation should be used instead
299        let response_body = if is_mutation_method {
300            // Only use mutation-based response generation for actual mutations
301            self.generate_response_body(&mutation_analysis, request, session_context)
302                .await?
303        } else {
304            // For GET/HEAD/OPTIONS, return empty object to signal OpenAPI generation should be used
305            // This prevents GET requests from returning POST-style {id: "generated_id", status: "created"} responses
306            tracing::debug!(
307                "Skipping mutation-based response generation for {} request - using OpenAPI response generation",
308                method_upper
309            );
310            serde_json::json!({}) // Empty object signals to use OpenAPI response generation
311        };
312
313        Ok(Response {
314            status_code: 200,
315            body: response_body,
316            headers: HashMap::new(),
317        })
318    }
319
320    /// Learn from an example pair
321    ///
322    /// Updates behavioral rules based on a new example.
323    pub async fn learn_from_example(&mut self, example: ExamplePair) -> Result<()> {
324        // Regenerate rules with new example
325        let examples = vec![example];
326        let new_rules = self.rule_generator.generate_rules_from_examples(examples).await?;
327
328        // Merge with existing rules
329        self.merge_rules(new_rules);
330
331        Ok(())
332    }
333
334    /// Get current behavior rules
335    pub fn rules(&self) -> &BehaviorRules {
336        &self.rules
337    }
338
339    /// Update behavior rules
340    pub fn update_rules(&mut self, rules: BehaviorRules) {
341        self.rules = rules;
342        // Update mutation analyzer with new rules
343        self.mutation_analyzer = MutationAnalyzer::new().with_rules(self.rules.clone());
344    }
345
346    /// Update configuration at runtime
347    ///
348    /// This allows changing MockAI configuration without recreating the instance.
349    /// Useful for hot-reloading reality level configurations.
350    ///
351    /// Note: This updates the configuration but does not regenerate rules.
352    /// For rule updates, use `update_rules()` or `learn_from_example()`.
353    pub fn update_config(&mut self, config: IntelligentBehaviorConfig) {
354        self.config = config.clone();
355
356        // Update components that depend on config
357        let behavior_config = self.config.behavior_model.clone();
358        self.validation_generator = ValidationGenerator::new(behavior_config.clone());
359        self.pagination_intelligence = PaginationIntelligence::new(behavior_config);
360
361        // Note: We don't recreate rule_generator or mutation_analyzer
362        // as they may have learned rules that should be preserved
363    }
364
365    /// Update configuration (async version for Arc<RwLock>)
366    ///
367    /// Convenience method for updating a MockAI instance wrapped in Arc<RwLock>.
368    /// This is the recommended way to update MockAI configuration at runtime.
369    ///
370    /// # Returns
371    /// `Ok(())` on success, or an error if the update fails.
372    pub async fn update_config_async(
373        this: &std::sync::Arc<tokio::sync::RwLock<Self>>,
374        config: IntelligentBehaviorConfig,
375    ) -> Result<()> {
376        let mut mockai = this.write().await;
377        mockai.update_config(config);
378        Ok(())
379    }
380
381    /// Get current configuration
382    ///
383    /// Primarily for testing purposes to verify configuration updates.
384    pub fn get_config(&self) -> &IntelligentBehaviorConfig {
385        &self.config
386    }
387
388    // ===== Private helper methods =====
389
390    /// Extract examples from OpenAPI spec
391    pub fn extract_examples_from_openapi(spec: &OpenApiSpec) -> Result<Vec<ExamplePair>> {
392        let mut examples = Vec::new();
393
394        // Use the all_paths_and_operations method
395        let path_operations = spec.all_paths_and_operations();
396
397        for (path, operations) in path_operations {
398            for (method, operation) in operations {
399                // Extract request example
400                let request = operation
401                    .request_body
402                    .as_ref()
403                    .and_then(|rb| rb.as_item())
404                    .and_then(|rb| rb.content.get("application/json"))
405                    .and_then(|media| media.example.clone());
406
407                // Extract response example
408                let response = operation.responses.responses.iter().find_map(|(status, resp)| {
409                    if let openapiv3::StatusCode::Code(200) = status {
410                        resp.as_item()
411                            .and_then(|r| r.content.get("application/json"))
412                            .and_then(|media| media.example.clone())
413                    } else {
414                        None
415                    }
416                });
417
418                examples.push(ExamplePair {
419                    method: method.clone(),
420                    path: path.clone(),
421                    request,
422                    status: 200,
423                    response,
424                    query_params: HashMap::new(),
425                    headers: HashMap::new(),
426                    metadata: HashMap::new(),
427                });
428            }
429        }
430
431        Ok(examples)
432    }
433
434    /// Check if request is paginated
435    fn is_paginated_request(&self, request: &Request) -> bool {
436        // Check for pagination parameters
437        request.query_params.keys().any(|key| {
438            matches!(
439                key.to_lowercase().as_str(),
440                "page" | "limit" | "per_page" | "offset" | "cursor"
441            )
442        })
443    }
444
445    /// Generate pagination metadata
446    async fn generate_pagination_metadata(
447        &self,
448        request: &Request,
449        session_context: &StatefulAiContext,
450    ) -> Result<PaginationMetadata> {
451        let pagination_request = PaginationRequest {
452            path: request.path.clone(),
453            query_params: request.query_params.clone(),
454            request_body: request.body.clone(),
455        };
456
457        self.pagination_intelligence
458            .generate_pagination_metadata(&pagination_request, session_context)
459            .await
460    }
461
462    /// Build paginated response
463    async fn build_paginated_response(
464        &self,
465        meta: &PaginationMetadata,
466        _request: &Request,
467    ) -> Result<Value> {
468        // Build standard paginated response
469        Ok(serde_json::json!({
470            "data": [], // Would be populated with actual data
471            "pagination": {
472                "page": meta.page,
473                "page_size": meta.page_size,
474                "total": meta.total,
475                "total_pages": meta.total_pages,
476                "has_next": meta.has_next,
477                "has_prev": meta.has_prev,
478                "offset": meta.offset,
479                "next_cursor": meta.next_cursor,
480                "prev_cursor": meta.prev_cursor,
481            }
482        }))
483    }
484
485    /// Generate response body based on mutation analysis
486    async fn generate_response_body(
487        &self,
488        mutation: &super::mutation_analyzer::MutationAnalysis,
489        request: &Request,
490        _session_context: &StatefulAiContext,
491    ) -> Result<Value> {
492        // Generate response based on mutation type
493        // CRITICAL: NoChange (used for GET/HEAD/OPTIONS) should return empty object
494        // to signal that OpenAPI response generation should be used instead
495        match mutation.mutation_type {
496            super::mutation_analyzer::MutationType::NoChange => {
497                // For read operations (GET, HEAD, OPTIONS), return empty object
498                // This signals to use OpenAPI response generation, not mutation responses
499                tracing::debug!("MutationType::NoChange - returning empty object to use OpenAPI response generation");
500                Ok(serde_json::json!({}))
501            }
502            super::mutation_analyzer::MutationType::Create => {
503                // Generate created resource response
504                Ok(serde_json::json!({
505                    "id": "generated_id",
506                    "status": "created",
507                    "data": request.body.clone().unwrap_or(serde_json::json!({}))
508                }))
509            }
510            super::mutation_analyzer::MutationType::Update
511            | super::mutation_analyzer::MutationType::PartialUpdate => {
512                // Generate updated resource response
513                Ok(serde_json::json!({
514                    "id": "resource_id",
515                    "status": "updated",
516                    "data": request.body.clone().unwrap_or(serde_json::json!({}))
517                }))
518            }
519            super::mutation_analyzer::MutationType::Delete => {
520                // Generate deletion response
521                Ok(serde_json::json!({
522                    "status": "deleted",
523                    "message": "Resource deleted successfully"
524                }))
525            }
526            _ => {
527                // Default success response
528                Ok(serde_json::json!({
529                    "status": "success",
530                    "data": request.body.clone().unwrap_or(serde_json::json!({}))
531                }))
532            }
533        }
534    }
535
536    /// Merge new rules with existing rules
537    fn merge_rules(&mut self, new_rules: BehaviorRules) {
538        // Merge consistency rules
539        self.rules.consistency_rules.extend(new_rules.consistency_rules);
540
541        // Merge schemas
542        for (key, value) in new_rules.schemas {
543            self.rules.schemas.insert(key, value);
544        }
545
546        // Merge state machines
547        for (key, value) in new_rules.state_transitions {
548            self.rules.state_transitions.insert(key, value);
549        }
550
551        // Update system prompt if new one is more descriptive
552        if new_rules.system_prompt.len() > self.rules.system_prompt.len() {
553            self.rules.system_prompt = new_rules.system_prompt;
554        }
555    }
556}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561    use serde_json::json;
562
563    #[tokio::test]
564    async fn test_is_paginated_request() {
565        // Skip test if API key is not available
566        if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
567            eprintln!("Skipping test: No API key found");
568            return;
569        }
570
571        let config = IntelligentBehaviorConfig::default();
572        let examples = vec![ExamplePair {
573            method: "GET".to_string(),
574            path: "/api/users".to_string(),
575            request: None,
576            status: 200,
577            response: Some(json!({})),
578            query_params: HashMap::new(),
579            headers: HashMap::new(),
580            metadata: HashMap::new(),
581        }];
582
583        let mockai = match MockAI::from_examples(examples, config).await {
584            Ok(m) => m,
585            Err(e) => {
586                eprintln!("Skipping test: Failed to create MockAI: {}", e);
587                return;
588            }
589        };
590
591        let mut query_params = HashMap::new();
592        query_params.insert("page".to_string(), "1".to_string());
593
594        let request = Request {
595            method: "GET".to_string(),
596            path: "/api/users".to_string(),
597            body: None,
598            query_params,
599            headers: HashMap::new(),
600        };
601
602        assert!(mockai.is_paginated_request(&request));
603    }
604
605    #[tokio::test]
606    async fn test_process_request() {
607        // Skip test if API key is not available
608        if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
609            eprintln!("Skipping test: No API key found");
610            return;
611        }
612
613        let config = IntelligentBehaviorConfig::default();
614        let examples = vec![ExamplePair {
615            method: "GET".to_string(),
616            path: "/api/users".to_string(),
617            request: None,
618            status: 200,
619            response: Some(json!({
620                "users": [],
621                "total": 0
622            })),
623            query_params: HashMap::new(),
624            headers: HashMap::new(),
625            metadata: HashMap::new(),
626        }];
627
628        let mockai = match MockAI::from_examples(examples, config).await {
629            Ok(m) => m,
630            Err(e) => {
631                eprintln!("Skipping test: Failed to create MockAI: {}", e);
632                return;
633            }
634        };
635
636        let request = Request {
637            method: "GET".to_string(),
638            path: "/api/users".to_string(),
639            body: None,
640            query_params: HashMap::new(),
641            headers: HashMap::new(),
642        };
643
644        let response = match mockai.process_request(&request).await {
645            Ok(r) => r,
646            Err(e) => {
647                eprintln!("Skipping test: Failed to process request: {}", e);
648                return;
649            }
650        };
651
652        // Verify response structure
653        assert_eq!(response.status_code, 200);
654        assert!(response.body.is_object() || response.body.is_array());
655    }
656
657    #[tokio::test]
658    async fn test_process_request_with_body() {
659        // Skip test if API key is not available
660        if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
661            eprintln!("Skipping test: No API key found");
662            return;
663        }
664
665        let config = IntelligentBehaviorConfig::default();
666        let examples = vec![ExamplePair {
667            method: "POST".to_string(),
668            path: "/api/users".to_string(),
669            request: Some(json!({
670                "name": "John Doe",
671                "email": "john@example.com"
672            })),
673            status: 201,
674            response: Some(json!({
675                "id": "123",
676                "name": "John Doe",
677                "email": "john@example.com"
678            })),
679            query_params: HashMap::new(),
680            headers: HashMap::new(),
681            metadata: HashMap::new(),
682        }];
683
684        let mockai = match MockAI::from_examples(examples, config).await {
685            Ok(m) => m,
686            Err(e) => {
687                eprintln!("Skipping test: Failed to create MockAI: {}", e);
688                return;
689            }
690        };
691
692        let request = Request {
693            method: "POST".to_string(),
694            path: "/api/users".to_string(),
695            body: Some(json!({
696                "name": "Jane Doe",
697                "email": "jane@example.com"
698            })),
699            query_params: HashMap::new(),
700            headers: HashMap::new(),
701        };
702
703        let response = match mockai.process_request(&request).await {
704            Ok(r) => r,
705            Err(e) => {
706                eprintln!("Skipping test: Failed to process request: {}", e);
707                return;
708            }
709        };
710
711        // Verify response structure
712        assert_eq!(response.status_code, 201);
713        assert!(response.body.is_object());
714    }
715
716    #[test]
717    fn test_request_creation() {
718        let mut query_params = HashMap::new();
719        query_params.insert("page".to_string(), "1".to_string());
720
721        let mut headers = HashMap::new();
722        headers.insert("Content-Type".to_string(), "application/json".to_string());
723
724        let request = Request {
725            method: "GET".to_string(),
726            path: "/api/users".to_string(),
727            body: Some(json!({"id": 1})),
728            query_params,
729            headers,
730        };
731
732        assert_eq!(request.method, "GET");
733        assert_eq!(request.path, "/api/users");
734        assert!(request.body.is_some());
735    }
736
737    #[test]
738    fn test_response_creation() {
739        let mut headers = HashMap::new();
740        headers.insert("Content-Type".to_string(), "application/json".to_string());
741
742        let response = Response {
743            status_code: 200,
744            body: json!({"message": "success"}),
745            headers,
746        };
747
748        assert_eq!(response.status_code, 200);
749        assert!(response.body.is_object());
750    }
751
752    #[test]
753    fn test_mockai_new() {
754        let config = IntelligentBehaviorConfig::default();
755        let mockai = MockAI::new(config);
756        // Just verify it can be created
757        let _ = mockai;
758    }
759
760    #[test]
761    fn test_mockai_rules() {
762        let config = IntelligentBehaviorConfig::default();
763        let mockai = MockAI::new(config);
764        let rules = mockai.rules();
765        // Just verify we can access rules
766        let _ = rules;
767    }
768
769    #[test]
770    fn test_mockai_update_rules() {
771        let config = IntelligentBehaviorConfig::default();
772        let mut mockai = MockAI::new(config);
773        let new_rules = BehaviorRules::default();
774        mockai.update_rules(new_rules);
775        // Just verify it doesn't panic
776    }
777
778    #[test]
779    fn test_mockai_get_config() {
780        let config = IntelligentBehaviorConfig::default();
781        let mockai = MockAI::new(config.clone());
782        let retrieved_config = mockai.get_config();
783        // Just verify we can access config
784        let _ = retrieved_config;
785    }
786
787    #[test]
788    fn test_mockai_update_config() {
789        let config = IntelligentBehaviorConfig::default();
790        let mut mockai = MockAI::new(config.clone());
791        let new_config = IntelligentBehaviorConfig::default();
792        mockai.update_config(new_config);
793        // Just verify it doesn't panic
794    }
795
796    #[test]
797    fn test_extract_examples_from_openapi_empty_spec() {
798        let spec_json = json!({
799            "openapi": "3.0.0",
800            "info": {
801                "title": "Test API",
802                "version": "1.0.0"
803            },
804            "paths": {}
805        });
806        let spec = OpenApiSpec::from_json(spec_json).unwrap();
807        let examples = MockAI::extract_examples_from_openapi(&spec).unwrap();
808        assert!(examples.is_empty());
809    }
810
811    #[test]
812    fn test_request_with_all_fields() {
813        let mut headers = HashMap::new();
814        headers.insert("Authorization".to_string(), "Bearer token".to_string());
815        let mut query_params = HashMap::new();
816        query_params.insert("limit".to_string(), "10".to_string());
817
818        let request = Request {
819            method: "POST".to_string(),
820            path: "/api/data".to_string(),
821            body: Some(json!({"key": "value"})),
822            query_params: query_params.clone(),
823            headers: headers.clone(),
824        };
825
826        assert_eq!(request.method, "POST");
827        assert_eq!(request.path, "/api/data");
828        assert!(request.body.is_some());
829        assert_eq!(request.query_params.get("limit"), Some(&"10".to_string()));
830        assert_eq!(request.headers.get("Authorization"), Some(&"Bearer token".to_string()));
831    }
832
833    #[test]
834    fn test_response_with_headers() {
835        let mut headers = HashMap::new();
836        headers.insert("X-Total-Count".to_string(), "100".to_string());
837        headers.insert("Content-Type".to_string(), "application/json".to_string());
838
839        let response = Response {
840            status_code: 201,
841            body: json!({"id": "123", "created": true}),
842            headers: headers.clone(),
843        };
844
845        assert_eq!(response.status_code, 201);
846        assert!(response.body.is_object());
847        assert_eq!(response.headers.len(), 2);
848        assert_eq!(response.headers.get("X-Total-Count"), Some(&"100".to_string()));
849    }
850
851    #[test]
852    fn test_request_clone() {
853        let request1 = Request {
854            method: "GET".to_string(),
855            path: "/api/test".to_string(),
856            body: Some(json!({"id": 1})),
857            query_params: HashMap::new(),
858            headers: HashMap::new(),
859        };
860        let request2 = request1.clone();
861        assert_eq!(request1.method, request2.method);
862        assert_eq!(request1.path, request2.path);
863    }
864
865    #[test]
866    fn test_request_debug() {
867        let request = Request {
868            method: "POST".to_string(),
869            path: "/api/users".to_string(),
870            body: None,
871            query_params: HashMap::new(),
872            headers: HashMap::new(),
873        };
874        let debug_str = format!("{:?}", request);
875        assert!(debug_str.contains("Request"));
876    }
877
878    #[test]
879    fn test_response_clone() {
880        let response1 = Response {
881            status_code: 200,
882            body: json!({"status": "ok"}),
883            headers: HashMap::new(),
884        };
885        let response2 = response1.clone();
886        assert_eq!(response1.status_code, response2.status_code);
887    }
888
889    #[test]
890    fn test_response_debug() {
891        let response = Response {
892            status_code: 404,
893            body: json!({"error": "Not found"}),
894            headers: HashMap::new(),
895        };
896        let debug_str = format!("{:?}", response);
897        assert!(debug_str.contains("Response"));
898    }
899
900    #[test]
901    fn test_request_with_empty_fields() {
902        let request = Request {
903            method: "GET".to_string(),
904            path: "/api/test".to_string(),
905            body: None,
906            query_params: HashMap::new(),
907            headers: HashMap::new(),
908        };
909        assert!(request.body.is_none());
910        assert!(request.query_params.is_empty());
911        assert!(request.headers.is_empty());
912    }
913
914    #[test]
915    fn test_response_with_empty_headers() {
916        let response = Response {
917            status_code: 200,
918            body: json!({"data": []}),
919            headers: HashMap::new(),
920        };
921        assert!(response.headers.is_empty());
922        assert!(response.body.is_object());
923    }
924
925    #[test]
926    fn test_request_with_complex_body() {
927        let request = Request {
928            method: "PUT".to_string(),
929            path: "/api/users/123".to_string(),
930            body: Some(json!({
931                "name": "John Doe",
932                "email": "john@example.com",
933                "metadata": {
934                    "role": "admin",
935                    "permissions": ["read", "write"]
936                }
937            })),
938            query_params: HashMap::new(),
939            headers: HashMap::new(),
940        };
941        assert!(request.body.is_some());
942        let body = request.body.unwrap();
943        assert!(body.is_object());
944        assert!(body.get("metadata").is_some());
945    }
946
947    #[test]
948    fn test_response_with_array_body() {
949        let response = Response {
950            status_code: 200,
951            body: json!([
952                {"id": 1, "name": "Alice"},
953                {"id": 2, "name": "Bob"},
954                {"id": 3, "name": "Charlie"}
955            ]),
956            headers: HashMap::new(),
957        };
958        assert!(response.body.is_array());
959        let array = response.body.as_array().unwrap();
960        assert_eq!(array.len(), 3);
961    }
962
963    #[test]
964    fn test_request_with_multiple_query_params() {
965        let mut query_params = HashMap::new();
966        query_params.insert("page".to_string(), "1".to_string());
967        query_params.insert("limit".to_string(), "20".to_string());
968        query_params.insert("sort".to_string(), "name".to_string());
969        query_params.insert("order".to_string(), "asc".to_string());
970
971        let request = Request {
972            method: "GET".to_string(),
973            path: "/api/users".to_string(),
974            body: None,
975            query_params: query_params.clone(),
976            headers: HashMap::new(),
977        };
978
979        assert_eq!(request.query_params.len(), 4);
980        assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
981        assert_eq!(request.query_params.get("limit"), Some(&"20".to_string()));
982    }
983
984    #[test]
985    fn test_response_with_multiple_headers() {
986        let mut headers = HashMap::new();
987        headers.insert("Content-Type".to_string(), "application/json".to_string());
988        headers.insert("X-Request-ID".to_string(), "req-123".to_string());
989        headers.insert("X-Rate-Limit-Remaining".to_string(), "99".to_string());
990        headers.insert("Cache-Control".to_string(), "no-cache".to_string());
991
992        let response = Response {
993            status_code: 200,
994            body: json!({"data": "test"}),
995            headers: headers.clone(),
996        };
997
998        assert_eq!(response.headers.len(), 4);
999        assert_eq!(response.headers.get("X-Request-ID"), Some(&"req-123".to_string()));
1000    }
1001
1002    #[test]
1003    fn test_request_different_methods() {
1004        let methods = vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
1005        for method in methods {
1006            let request = Request {
1007                method: method.to_string(),
1008                path: "/api/test".to_string(),
1009                body: None,
1010                query_params: HashMap::new(),
1011                headers: HashMap::new(),
1012            };
1013            assert_eq!(request.method, method);
1014        }
1015    }
1016
1017    #[test]
1018    fn test_response_different_status_codes() {
1019        let status_codes = vec![200, 201, 204, 400, 401, 403, 404, 500, 503];
1020        for status_code in status_codes {
1021            let response = Response {
1022                status_code,
1023                body: json!({"status": status_code}),
1024                headers: HashMap::new(),
1025            };
1026            assert_eq!(response.status_code, status_code);
1027        }
1028    }
1029}