1use 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
22pub use mockforge_foundation::intelligent_behavior::{Request, Response};
24
25pub struct MockAI {
27 rules: BehaviorRules,
29 rule_generator: RuleGenerator,
31 mutation_analyzer: MutationAnalyzer,
33 validation_generator: ValidationGenerator,
35 pagination_intelligence: PaginationIntelligence,
37 config: IntelligentBehaviorConfig,
39 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 Self::process_request(self, request).await
52 }
53}
54
55impl MockAI {
56 pub async fn from_openapi(
60 spec: &OpenApiSpec,
61 config: IntelligentBehaviorConfig,
62 ) -> Result<Self> {
63 let examples = Self::extract_examples_from_openapi(spec)?;
65
66 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 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 pub async fn from_examples(
91 examples: Vec<ExamplePair>,
92 config: IntelligentBehaviorConfig,
93 ) -> Result<Self> {
94 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 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 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 pub async fn process_request(&self, request: &Request) -> Result<Response> {
141 let session_id = self.extract_session_id(request);
143
144 let session_context = self.get_or_create_session_context(session_id).await?;
146
147 let response = self.generate_response(request, &session_context).await?;
149
150 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 fn extract_session_id(&self, request: &Request) -> Option<String> {
170 if let Some(session_id) = request.headers.get("X-Session-ID") {
172 return Some(session_id.clone());
173 }
174
175 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 async fn get_or_create_session_context(
192 &self,
193 session_id: Option<String>,
194 ) -> Result<StatefulAiContext> {
195 let Some(session_id) = session_id else {
200 let ephemeral_id = format!("session_{}", uuid::Uuid::new_v4());
201 return Ok(StatefulAiContext::new(ephemeral_id, self.config.clone()));
202 };
203
204 {
206 let contexts = self.session_contexts.read().await;
207 if let Some(context) = contexts.get(&session_id) {
208 return Ok(context.clone());
209 }
210 }
211
212 let new_context = StatefulAiContext::new(session_id.clone(), self.config.clone());
214
215 {
217 let mut contexts = self.session_contexts.write().await;
218 contexts.insert(session_id, new_context.clone());
219 }
220
221 Ok(new_context)
222 }
223
224 pub async fn generate_response(
229 &self,
230 request: &Request,
231 session_context: &StatefulAiContext,
232 ) -> Result<Response> {
233 let method_upper = request.method.to_uppercase();
236 let is_mutation_method =
237 matches!(method_upper.as_str(), "POST" | "PUT" | "PATCH" | "DELETE");
238
239 let history = session_context.get_history().await;
241 let previous_request = history.last().and_then(|interaction| interaction.request.clone());
242
243 let mutation_analysis = if is_mutation_method {
246 let current_body = request.body.clone().unwrap_or(serde_json::json!({}));
248 self.mutation_analyzer
249 .analyze_mutation(¤t_body, previous_request.as_ref(), session_context)
250 .await?
251 } else {
252 super::mutation_analyzer::MutationAnalysis {
256 mutation_type: super::mutation_analyzer::MutationType::NoChange, changed_fields: Vec::new(),
258 added_fields: Vec::new(),
259 removed_fields: Vec::new(),
260 validation_issues: Vec::new(),
261 confidence: 1.0,
262 }
263 };
264
265 if !mutation_analysis.validation_issues.is_empty() {
267 let issue = &mutation_analysis.validation_issues[0];
269 let request_context = RequestContext {
270 method: request.method.clone(),
271 path: request.path.clone(),
272 request_body: request.body.clone(),
273 query_params: request.query_params.clone(),
274 headers: request.headers.clone(),
275 };
276
277 let error_response = self
278 .validation_generator
279 .generate_validation_error(issue, &request_context)
280 .await?;
281
282 return Ok(Response {
283 status_code: error_response.status_code,
284 body: error_response.body,
285 headers: HashMap::new(),
286 });
287 }
288
289 if self.is_paginated_request(request) {
291 let pagination_meta =
292 self.generate_pagination_metadata(request, session_context).await?;
293
294 let body = self.build_paginated_response(&pagination_meta, request).await?;
295
296 return Ok(Response {
297 status_code: 200,
298 body,
299 headers: HashMap::new(),
300 });
301 }
302
303 let response_body = if is_mutation_method {
311 self.generate_response_body(&mutation_analysis, request, session_context)
312 .await?
313 } else {
314 tracing::debug!(
315 "Skipping mutation-based response generation for {} request - using OpenAPI response generation",
316 method_upper
317 );
318 serde_json::json!({})
319 };
320
321 Ok(Response {
322 status_code: 200,
323 body: response_body,
324 headers: HashMap::new(),
325 })
326 }
327
328 pub async fn learn_from_example(&mut self, example: ExamplePair) -> Result<()> {
332 let examples = vec![example];
334 let new_rules = self.rule_generator.generate_rules_from_examples(examples).await?;
335
336 self.merge_rules(new_rules);
338
339 Ok(())
340 }
341
342 pub fn rules(&self) -> &BehaviorRules {
344 &self.rules
345 }
346
347 pub fn update_rules(&mut self, rules: BehaviorRules) {
349 self.rules = rules;
350 self.mutation_analyzer = MutationAnalyzer::new().with_rules(self.rules.clone());
352 }
353
354 pub fn update_config(&mut self, config: IntelligentBehaviorConfig) {
362 self.config = config.clone();
363
364 let behavior_config = self.config.behavior_model.clone();
366 self.validation_generator = ValidationGenerator::new(behavior_config.clone());
367 self.pagination_intelligence = PaginationIntelligence::new(behavior_config);
368
369 }
372
373 pub async fn update_config_async(
381 this: &std::sync::Arc<tokio::sync::RwLock<Self>>,
382 config: IntelligentBehaviorConfig,
383 ) -> Result<()> {
384 let mut mockai = this.write().await;
385 mockai.update_config(config);
386 Ok(())
387 }
388
389 pub fn get_config(&self) -> &IntelligentBehaviorConfig {
393 &self.config
394 }
395
396 pub fn extract_examples_from_openapi(spec: &OpenApiSpec) -> Result<Vec<ExamplePair>> {
400 let mut examples = Vec::new();
401
402 let path_operations = spec.all_paths_and_operations();
404
405 for (path, operations) in path_operations {
406 for (method, operation) in operations {
407 let request = operation
409 .request_body
410 .as_ref()
411 .and_then(|rb| rb.as_item())
412 .and_then(|rb| rb.content.get("application/json"))
413 .and_then(|media| media.example.clone());
414
415 let response = operation.responses.responses.iter().find_map(|(status, resp)| {
417 if let openapiv3::StatusCode::Code(200) = status {
418 resp.as_item()
419 .and_then(|r| r.content.get("application/json"))
420 .and_then(|media| media.example.clone())
421 } else {
422 None
423 }
424 });
425
426 examples.push(ExamplePair {
427 method: method.clone(),
428 path: path.clone(),
429 request,
430 status: 200,
431 response,
432 query_params: HashMap::new(),
433 headers: HashMap::new(),
434 metadata: HashMap::new(),
435 });
436 }
437 }
438
439 Ok(examples)
440 }
441
442 fn is_paginated_request(&self, request: &Request) -> bool {
444 request.query_params.keys().any(|key| {
446 matches!(
447 key.to_lowercase().as_str(),
448 "page" | "limit" | "per_page" | "offset" | "cursor"
449 )
450 })
451 }
452
453 async fn generate_pagination_metadata(
455 &self,
456 request: &Request,
457 session_context: &StatefulAiContext,
458 ) -> Result<PaginationMetadata> {
459 let pagination_request = PaginationRequest {
460 path: request.path.clone(),
461 query_params: request.query_params.clone(),
462 request_body: request.body.clone(),
463 };
464
465 self.pagination_intelligence
466 .generate_pagination_metadata(&pagination_request, session_context)
467 .await
468 }
469
470 async fn build_paginated_response(
472 &self,
473 meta: &PaginationMetadata,
474 _request: &Request,
475 ) -> Result<Value> {
476 Ok(serde_json::json!({
478 "data": [], "pagination": {
480 "page": meta.page,
481 "page_size": meta.page_size,
482 "total": meta.total,
483 "total_pages": meta.total_pages,
484 "has_next": meta.has_next,
485 "has_prev": meta.has_prev,
486 "offset": meta.offset,
487 "next_cursor": meta.next_cursor,
488 "prev_cursor": meta.prev_cursor,
489 }
490 }))
491 }
492
493 async fn generate_response_body(
519 &self,
520 _mutation: &super::mutation_analyzer::MutationAnalysis,
521 _request: &Request,
522 _session_context: &StatefulAiContext,
523 ) -> Result<Value> {
524 tracing::debug!(
525 "MockAI mutation response: returning empty object so spec-driven OpenAPI response generation populates the body (#79 r31)"
526 );
527 Ok(serde_json::json!({}))
528 }
529
530 fn merge_rules(&mut self, new_rules: BehaviorRules) {
532 self.rules.consistency_rules.extend(new_rules.consistency_rules);
534
535 for (key, value) in new_rules.schemas {
537 self.rules.schemas.insert(key, value);
538 }
539
540 for (key, value) in new_rules.state_transitions {
542 self.rules.state_transitions.insert(key, value);
543 }
544
545 if new_rules.system_prompt.len() > self.rules.system_prompt.len() {
547 self.rules.system_prompt = new_rules.system_prompt;
548 }
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555 use serde_json::json;
556
557 #[tokio::test]
558 async fn test_is_paginated_request() {
559 if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
561 eprintln!("Skipping test: No API key found");
562 return;
563 }
564
565 let config = IntelligentBehaviorConfig::default();
566 let examples = vec![ExamplePair {
567 method: "GET".to_string(),
568 path: "/api/users".to_string(),
569 request: None,
570 status: 200,
571 response: Some(json!({})),
572 query_params: HashMap::new(),
573 headers: HashMap::new(),
574 metadata: HashMap::new(),
575 }];
576
577 let mockai = match MockAI::from_examples(examples, config).await {
578 Ok(m) => m,
579 Err(e) => {
580 eprintln!("Skipping test: Failed to create MockAI: {}", e);
581 return;
582 }
583 };
584
585 let mut query_params = HashMap::new();
586 query_params.insert("page".to_string(), "1".to_string());
587
588 let request = Request {
589 method: "GET".to_string(),
590 path: "/api/users".to_string(),
591 body: None,
592 query_params,
593 headers: HashMap::new(),
594 };
595
596 assert!(mockai.is_paginated_request(&request));
597 }
598
599 #[tokio::test]
600 async fn test_process_request() {
601 if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
603 eprintln!("Skipping test: No API key found");
604 return;
605 }
606
607 let config = IntelligentBehaviorConfig::default();
608 let examples = vec![ExamplePair {
609 method: "GET".to_string(),
610 path: "/api/users".to_string(),
611 request: None,
612 status: 200,
613 response: Some(json!({
614 "users": [],
615 "total": 0
616 })),
617 query_params: HashMap::new(),
618 headers: HashMap::new(),
619 metadata: HashMap::new(),
620 }];
621
622 let mockai = match MockAI::from_examples(examples, config).await {
623 Ok(m) => m,
624 Err(e) => {
625 eprintln!("Skipping test: Failed to create MockAI: {}", e);
626 return;
627 }
628 };
629
630 let request = Request {
631 method: "GET".to_string(),
632 path: "/api/users".to_string(),
633 body: None,
634 query_params: HashMap::new(),
635 headers: HashMap::new(),
636 };
637
638 let response = match mockai.process_request(&request).await {
639 Ok(r) => r,
640 Err(e) => {
641 eprintln!("Skipping test: Failed to process request: {}", e);
642 return;
643 }
644 };
645
646 assert_eq!(response.status_code, 200);
648 assert!(response.body.is_object() || response.body.is_array());
649 }
650
651 #[tokio::test]
652 async fn test_process_request_with_body() {
653 if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
655 eprintln!("Skipping test: No API key found");
656 return;
657 }
658
659 let config = IntelligentBehaviorConfig::default();
660 let examples = vec![ExamplePair {
661 method: "POST".to_string(),
662 path: "/api/users".to_string(),
663 request: Some(json!({
664 "name": "John Doe",
665 "email": "john@example.com"
666 })),
667 status: 201,
668 response: Some(json!({
669 "id": "123",
670 "name": "John Doe",
671 "email": "john@example.com"
672 })),
673 query_params: HashMap::new(),
674 headers: HashMap::new(),
675 metadata: HashMap::new(),
676 }];
677
678 let mockai = match MockAI::from_examples(examples, config).await {
679 Ok(m) => m,
680 Err(e) => {
681 eprintln!("Skipping test: Failed to create MockAI: {}", e);
682 return;
683 }
684 };
685
686 let request = Request {
687 method: "POST".to_string(),
688 path: "/api/users".to_string(),
689 body: Some(json!({
690 "name": "Jane Doe",
691 "email": "jane@example.com"
692 })),
693 query_params: HashMap::new(),
694 headers: HashMap::new(),
695 };
696
697 let response = match mockai.process_request(&request).await {
698 Ok(r) => r,
699 Err(e) => {
700 eprintln!("Skipping test: Failed to process request: {}", e);
701 return;
702 }
703 };
704
705 assert_eq!(response.status_code, 201);
707 assert!(response.body.is_object());
708 }
709
710 #[test]
711 fn test_request_creation() {
712 let mut query_params = HashMap::new();
713 query_params.insert("page".to_string(), "1".to_string());
714
715 let mut headers = HashMap::new();
716 headers.insert("Content-Type".to_string(), "application/json".to_string());
717
718 let request = Request {
719 method: "GET".to_string(),
720 path: "/api/users".to_string(),
721 body: Some(json!({"id": 1})),
722 query_params,
723 headers,
724 };
725
726 assert_eq!(request.method, "GET");
727 assert_eq!(request.path, "/api/users");
728 assert!(request.body.is_some());
729 }
730
731 #[test]
732 fn test_response_creation() {
733 let mut headers = HashMap::new();
734 headers.insert("Content-Type".to_string(), "application/json".to_string());
735
736 let response = Response {
737 status_code: 200,
738 body: json!({"message": "success"}),
739 headers,
740 };
741
742 assert_eq!(response.status_code, 200);
743 assert!(response.body.is_object());
744 }
745
746 #[test]
747 fn test_mockai_new() {
748 let config = IntelligentBehaviorConfig::default();
749 let mockai = MockAI::new(config);
750 let _ = mockai;
752 }
753
754 #[test]
755 fn test_mockai_rules() {
756 let config = IntelligentBehaviorConfig::default();
757 let mockai = MockAI::new(config);
758 let rules = mockai.rules();
759 let _ = rules;
761 }
762
763 #[test]
764 fn test_mockai_update_rules() {
765 let config = IntelligentBehaviorConfig::default();
766 let mut mockai = MockAI::new(config);
767 let new_rules = BehaviorRules::default();
768 mockai.update_rules(new_rules);
769 }
771
772 #[test]
773 fn test_mockai_get_config() {
774 let config = IntelligentBehaviorConfig::default();
775 let mockai = MockAI::new(config.clone());
776 let retrieved_config = mockai.get_config();
777 let _ = retrieved_config;
779 }
780
781 #[test]
782 fn test_mockai_update_config() {
783 let config = IntelligentBehaviorConfig::default();
784 let mut mockai = MockAI::new(config.clone());
785 let new_config = IntelligentBehaviorConfig::default();
786 mockai.update_config(new_config);
787 }
789
790 #[test]
791 fn test_extract_examples_from_openapi_empty_spec() {
792 let spec_json = json!({
793 "openapi": "3.0.0",
794 "info": {
795 "title": "Test API",
796 "version": "1.0.0"
797 },
798 "paths": {}
799 });
800 let spec = OpenApiSpec::from_json(spec_json).unwrap();
801 let examples = MockAI::extract_examples_from_openapi(&spec).unwrap();
802 assert!(examples.is_empty());
803 }
804
805 #[test]
806 fn test_request_with_all_fields() {
807 let mut headers = HashMap::new();
808 headers.insert("Authorization".to_string(), "Bearer token".to_string());
809 let mut query_params = HashMap::new();
810 query_params.insert("limit".to_string(), "10".to_string());
811
812 let request = Request {
813 method: "POST".to_string(),
814 path: "/api/data".to_string(),
815 body: Some(json!({"key": "value"})),
816 query_params: query_params.clone(),
817 headers: headers.clone(),
818 };
819
820 assert_eq!(request.method, "POST");
821 assert_eq!(request.path, "/api/data");
822 assert!(request.body.is_some());
823 assert_eq!(request.query_params.get("limit"), Some(&"10".to_string()));
824 assert_eq!(request.headers.get("Authorization"), Some(&"Bearer token".to_string()));
825 }
826
827 #[test]
828 fn test_response_with_headers() {
829 let mut headers = HashMap::new();
830 headers.insert("X-Total-Count".to_string(), "100".to_string());
831 headers.insert("Content-Type".to_string(), "application/json".to_string());
832
833 let response = Response {
834 status_code: 201,
835 body: json!({"id": "123", "created": true}),
836 headers: headers.clone(),
837 };
838
839 assert_eq!(response.status_code, 201);
840 assert!(response.body.is_object());
841 assert_eq!(response.headers.len(), 2);
842 assert_eq!(response.headers.get("X-Total-Count"), Some(&"100".to_string()));
843 }
844
845 #[test]
846 fn test_request_clone() {
847 let request1 = Request {
848 method: "GET".to_string(),
849 path: "/api/test".to_string(),
850 body: Some(json!({"id": 1})),
851 query_params: HashMap::new(),
852 headers: HashMap::new(),
853 };
854 let request2 = request1.clone();
855 assert_eq!(request1.method, request2.method);
856 assert_eq!(request1.path, request2.path);
857 }
858
859 #[test]
860 fn test_request_debug() {
861 let request = Request {
862 method: "POST".to_string(),
863 path: "/api/users".to_string(),
864 body: None,
865 query_params: HashMap::new(),
866 headers: HashMap::new(),
867 };
868 let debug_str = format!("{:?}", request);
869 assert!(debug_str.contains("Request"));
870 }
871
872 #[test]
873 fn test_response_clone() {
874 let response1 = Response {
875 status_code: 200,
876 body: json!({"status": "ok"}),
877 headers: HashMap::new(),
878 };
879 let response2 = response1.clone();
880 assert_eq!(response1.status_code, response2.status_code);
881 }
882
883 #[test]
884 fn test_response_debug() {
885 let response = Response {
886 status_code: 404,
887 body: json!({"error": "Not found"}),
888 headers: HashMap::new(),
889 };
890 let debug_str = format!("{:?}", response);
891 assert!(debug_str.contains("Response"));
892 }
893
894 #[test]
895 fn test_request_with_empty_fields() {
896 let request = Request {
897 method: "GET".to_string(),
898 path: "/api/test".to_string(),
899 body: None,
900 query_params: HashMap::new(),
901 headers: HashMap::new(),
902 };
903 assert!(request.body.is_none());
904 assert!(request.query_params.is_empty());
905 assert!(request.headers.is_empty());
906 }
907
908 #[test]
909 fn test_response_with_empty_headers() {
910 let response = Response {
911 status_code: 200,
912 body: json!({"data": []}),
913 headers: HashMap::new(),
914 };
915 assert!(response.headers.is_empty());
916 assert!(response.body.is_object());
917 }
918
919 #[test]
920 fn test_request_with_complex_body() {
921 let request = Request {
922 method: "PUT".to_string(),
923 path: "/api/users/123".to_string(),
924 body: Some(json!({
925 "name": "John Doe",
926 "email": "john@example.com",
927 "metadata": {
928 "role": "admin",
929 "permissions": ["read", "write"]
930 }
931 })),
932 query_params: HashMap::new(),
933 headers: HashMap::new(),
934 };
935 assert!(request.body.is_some());
936 let body = request.body.unwrap();
937 assert!(body.is_object());
938 assert!(body.get("metadata").is_some());
939 }
940
941 #[test]
942 fn test_response_with_array_body() {
943 let response = Response {
944 status_code: 200,
945 body: json!([
946 {"id": 1, "name": "Alice"},
947 {"id": 2, "name": "Bob"},
948 {"id": 3, "name": "Charlie"}
949 ]),
950 headers: HashMap::new(),
951 };
952 assert!(response.body.is_array());
953 let array = response.body.as_array().unwrap();
954 assert_eq!(array.len(), 3);
955 }
956
957 #[test]
958 fn test_request_with_multiple_query_params() {
959 let mut query_params = HashMap::new();
960 query_params.insert("page".to_string(), "1".to_string());
961 query_params.insert("limit".to_string(), "20".to_string());
962 query_params.insert("sort".to_string(), "name".to_string());
963 query_params.insert("order".to_string(), "asc".to_string());
964
965 let request = Request {
966 method: "GET".to_string(),
967 path: "/api/users".to_string(),
968 body: None,
969 query_params: query_params.clone(),
970 headers: HashMap::new(),
971 };
972
973 assert_eq!(request.query_params.len(), 4);
974 assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
975 assert_eq!(request.query_params.get("limit"), Some(&"20".to_string()));
976 }
977
978 #[test]
979 fn test_response_with_multiple_headers() {
980 let mut headers = HashMap::new();
981 headers.insert("Content-Type".to_string(), "application/json".to_string());
982 headers.insert("X-Request-ID".to_string(), "req-123".to_string());
983 headers.insert("X-Rate-Limit-Remaining".to_string(), "99".to_string());
984 headers.insert("Cache-Control".to_string(), "no-cache".to_string());
985
986 let response = Response {
987 status_code: 200,
988 body: json!({"data": "test"}),
989 headers: headers.clone(),
990 };
991
992 assert_eq!(response.headers.len(), 4);
993 assert_eq!(response.headers.get("X-Request-ID"), Some(&"req-123".to_string()));
994 }
995
996 #[test]
997 fn test_request_different_methods() {
998 let methods = vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
999 for method in methods {
1000 let request = Request {
1001 method: method.to_string(),
1002 path: "/api/test".to_string(),
1003 body: None,
1004 query_params: HashMap::new(),
1005 headers: HashMap::new(),
1006 };
1007 assert_eq!(request.method, method);
1008 }
1009 }
1010
1011 #[test]
1012 fn test_response_different_status_codes() {
1013 let status_codes = vec![200, 201, 204, 400, 401, 403, 404, 500, 503];
1014 for status_code in status_codes {
1015 let response = Response {
1016 status_code,
1017 body: json!({"status": status_code}),
1018 headers: HashMap::new(),
1019 };
1020 assert_eq!(response.status_code, status_code);
1021 }
1022 }
1023
1024 #[tokio::test]
1037 async fn generate_response_body_returns_empty_for_all_mutation_kinds() {
1038 use crate::intelligent_behavior::mutation_analyzer::{MutationAnalysis, MutationType};
1039 let mockai = MockAI::new(IntelligentBehaviorConfig::default());
1040 let req = Request {
1041 method: "POST".to_string(),
1042 path: "/api/anything".to_string(),
1043 body: Some(json!({"name": "X"})),
1044 query_params: HashMap::new(),
1045 headers: HashMap::new(),
1046 };
1047 let ctx = StatefulAiContext::new("test-session", IntelligentBehaviorConfig::default());
1048
1049 for kind in [
1050 MutationType::NoChange,
1051 MutationType::Create,
1052 MutationType::Update,
1053 MutationType::PartialUpdate,
1054 MutationType::Delete,
1055 ] {
1056 let kind_label = format!("{:?}", kind);
1057 let mutation = MutationAnalysis {
1058 mutation_type: kind,
1059 changed_fields: Vec::new(),
1060 added_fields: Vec::new(),
1061 removed_fields: Vec::new(),
1062 validation_issues: Vec::new(),
1063 confidence: 1.0,
1064 };
1065 let body = mockai
1066 .generate_response_body(&mutation, &req, &ctx)
1067 .await
1068 .expect("generate_response_body should not error");
1069 assert_eq!(
1070 body,
1071 json!({}),
1072 "MockAI must return empty object for mutation kind {} so the calling \
1073 site falls back to spec-driven response generation (regression #79 r31). \
1074 Got: {:?}",
1075 kind_label,
1076 body
1077 );
1078 }
1079 }
1080
1081 #[tokio::test]
1082 async fn test_requests_without_session_id_are_not_retained() {
1083 let mockai = MockAI::new(IntelligentBehaviorConfig::default());
1084
1085 for _ in 0..100 {
1086 mockai.get_or_create_session_context(None).await.unwrap();
1087 }
1088 assert!(
1089 mockai.session_contexts.read().await.is_empty(),
1090 "contexts without a client session ID can never be looked up again, so storing \
1091 them leaks one per request"
1092 );
1093
1094 let first = mockai.get_or_create_session_context(Some("s1".to_string())).await.unwrap();
1095 let again = mockai.get_or_create_session_context(Some("s1".to_string())).await.unwrap();
1096 assert_eq!(first.session_id(), again.session_id());
1097 assert_eq!(mockai.session_contexts.read().await.len(), 1);
1098 }
1099}