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 session_id = session_id.unwrap_or_else(|| format!("session_{}", uuid::Uuid::new_v4()));
196
197 {
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 let new_context = StatefulAiContext::new(session_id.clone(), self.config.clone());
207
208 {
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 pub async fn generate_response(
222 &self,
223 request: &Request,
224 session_context: &StatefulAiContext,
225 ) -> Result<Response> {
226 let method_upper = request.method.to_uppercase();
229 let is_mutation_method =
230 matches!(method_upper.as_str(), "POST" | "PUT" | "PATCH" | "DELETE");
231
232 let history = session_context.get_history().await;
234 let previous_request = history.last().and_then(|interaction| interaction.request.clone());
235
236 let mutation_analysis = if is_mutation_method {
239 let current_body = request.body.clone().unwrap_or(serde_json::json!({}));
241 self.mutation_analyzer
242 .analyze_mutation(¤t_body, previous_request.as_ref(), session_context)
243 .await?
244 } else {
245 super::mutation_analyzer::MutationAnalysis {
249 mutation_type: super::mutation_analyzer::MutationType::NoChange, 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 if !mutation_analysis.validation_issues.is_empty() {
260 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 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 let response_body = if is_mutation_method {
304 self.generate_response_body(&mutation_analysis, request, session_context)
305 .await?
306 } else {
307 tracing::debug!(
308 "Skipping mutation-based response generation for {} request - using OpenAPI response generation",
309 method_upper
310 );
311 serde_json::json!({})
312 };
313
314 Ok(Response {
315 status_code: 200,
316 body: response_body,
317 headers: HashMap::new(),
318 })
319 }
320
321 pub async fn learn_from_example(&mut self, example: ExamplePair) -> Result<()> {
325 let examples = vec![example];
327 let new_rules = self.rule_generator.generate_rules_from_examples(examples).await?;
328
329 self.merge_rules(new_rules);
331
332 Ok(())
333 }
334
335 pub fn rules(&self) -> &BehaviorRules {
337 &self.rules
338 }
339
340 pub fn update_rules(&mut self, rules: BehaviorRules) {
342 self.rules = rules;
343 self.mutation_analyzer = MutationAnalyzer::new().with_rules(self.rules.clone());
345 }
346
347 pub fn update_config(&mut self, config: IntelligentBehaviorConfig) {
355 self.config = config.clone();
356
357 let behavior_config = self.config.behavior_model.clone();
359 self.validation_generator = ValidationGenerator::new(behavior_config.clone());
360 self.pagination_intelligence = PaginationIntelligence::new(behavior_config);
361
362 }
365
366 pub async fn update_config_async(
374 this: &std::sync::Arc<tokio::sync::RwLock<Self>>,
375 config: IntelligentBehaviorConfig,
376 ) -> Result<()> {
377 let mut mockai = this.write().await;
378 mockai.update_config(config);
379 Ok(())
380 }
381
382 pub fn get_config(&self) -> &IntelligentBehaviorConfig {
386 &self.config
387 }
388
389 pub fn extract_examples_from_openapi(spec: &OpenApiSpec) -> Result<Vec<ExamplePair>> {
393 let mut examples = Vec::new();
394
395 let path_operations = spec.all_paths_and_operations();
397
398 for (path, operations) in path_operations {
399 for (method, operation) in operations {
400 let request = operation
402 .request_body
403 .as_ref()
404 .and_then(|rb| rb.as_item())
405 .and_then(|rb| rb.content.get("application/json"))
406 .and_then(|media| media.example.clone());
407
408 let response = operation.responses.responses.iter().find_map(|(status, resp)| {
410 if let openapiv3::StatusCode::Code(200) = status {
411 resp.as_item()
412 .and_then(|r| r.content.get("application/json"))
413 .and_then(|media| media.example.clone())
414 } else {
415 None
416 }
417 });
418
419 examples.push(ExamplePair {
420 method: method.clone(),
421 path: path.clone(),
422 request,
423 status: 200,
424 response,
425 query_params: HashMap::new(),
426 headers: HashMap::new(),
427 metadata: HashMap::new(),
428 });
429 }
430 }
431
432 Ok(examples)
433 }
434
435 fn is_paginated_request(&self, request: &Request) -> bool {
437 request.query_params.keys().any(|key| {
439 matches!(
440 key.to_lowercase().as_str(),
441 "page" | "limit" | "per_page" | "offset" | "cursor"
442 )
443 })
444 }
445
446 async fn generate_pagination_metadata(
448 &self,
449 request: &Request,
450 session_context: &StatefulAiContext,
451 ) -> Result<PaginationMetadata> {
452 let pagination_request = PaginationRequest {
453 path: request.path.clone(),
454 query_params: request.query_params.clone(),
455 request_body: request.body.clone(),
456 };
457
458 self.pagination_intelligence
459 .generate_pagination_metadata(&pagination_request, session_context)
460 .await
461 }
462
463 async fn build_paginated_response(
465 &self,
466 meta: &PaginationMetadata,
467 _request: &Request,
468 ) -> Result<Value> {
469 Ok(serde_json::json!({
471 "data": [], "pagination": {
473 "page": meta.page,
474 "page_size": meta.page_size,
475 "total": meta.total,
476 "total_pages": meta.total_pages,
477 "has_next": meta.has_next,
478 "has_prev": meta.has_prev,
479 "offset": meta.offset,
480 "next_cursor": meta.next_cursor,
481 "prev_cursor": meta.prev_cursor,
482 }
483 }))
484 }
485
486 async fn generate_response_body(
512 &self,
513 _mutation: &super::mutation_analyzer::MutationAnalysis,
514 _request: &Request,
515 _session_context: &StatefulAiContext,
516 ) -> Result<Value> {
517 tracing::debug!(
518 "MockAI mutation response: returning empty object so spec-driven OpenAPI response generation populates the body (#79 r31)"
519 );
520 Ok(serde_json::json!({}))
521 }
522
523 fn merge_rules(&mut self, new_rules: BehaviorRules) {
525 self.rules.consistency_rules.extend(new_rules.consistency_rules);
527
528 for (key, value) in new_rules.schemas {
530 self.rules.schemas.insert(key, value);
531 }
532
533 for (key, value) in new_rules.state_transitions {
535 self.rules.state_transitions.insert(key, value);
536 }
537
538 if new_rules.system_prompt.len() > self.rules.system_prompt.len() {
540 self.rules.system_prompt = new_rules.system_prompt;
541 }
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548 use serde_json::json;
549
550 #[tokio::test]
551 async fn test_is_paginated_request() {
552 if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
554 eprintln!("Skipping test: No API key found");
555 return;
556 }
557
558 let config = IntelligentBehaviorConfig::default();
559 let examples = vec![ExamplePair {
560 method: "GET".to_string(),
561 path: "/api/users".to_string(),
562 request: None,
563 status: 200,
564 response: Some(json!({})),
565 query_params: HashMap::new(),
566 headers: HashMap::new(),
567 metadata: HashMap::new(),
568 }];
569
570 let mockai = match MockAI::from_examples(examples, config).await {
571 Ok(m) => m,
572 Err(e) => {
573 eprintln!("Skipping test: Failed to create MockAI: {}", e);
574 return;
575 }
576 };
577
578 let mut query_params = HashMap::new();
579 query_params.insert("page".to_string(), "1".to_string());
580
581 let request = Request {
582 method: "GET".to_string(),
583 path: "/api/users".to_string(),
584 body: None,
585 query_params,
586 headers: HashMap::new(),
587 };
588
589 assert!(mockai.is_paginated_request(&request));
590 }
591
592 #[tokio::test]
593 async fn test_process_request() {
594 if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
596 eprintln!("Skipping test: No API key found");
597 return;
598 }
599
600 let config = IntelligentBehaviorConfig::default();
601 let examples = vec![ExamplePair {
602 method: "GET".to_string(),
603 path: "/api/users".to_string(),
604 request: None,
605 status: 200,
606 response: Some(json!({
607 "users": [],
608 "total": 0
609 })),
610 query_params: HashMap::new(),
611 headers: HashMap::new(),
612 metadata: HashMap::new(),
613 }];
614
615 let mockai = match MockAI::from_examples(examples, config).await {
616 Ok(m) => m,
617 Err(e) => {
618 eprintln!("Skipping test: Failed to create MockAI: {}", e);
619 return;
620 }
621 };
622
623 let request = Request {
624 method: "GET".to_string(),
625 path: "/api/users".to_string(),
626 body: None,
627 query_params: HashMap::new(),
628 headers: HashMap::new(),
629 };
630
631 let response = match mockai.process_request(&request).await {
632 Ok(r) => r,
633 Err(e) => {
634 eprintln!("Skipping test: Failed to process request: {}", e);
635 return;
636 }
637 };
638
639 assert_eq!(response.status_code, 200);
641 assert!(response.body.is_object() || response.body.is_array());
642 }
643
644 #[tokio::test]
645 async fn test_process_request_with_body() {
646 if std::env::var("OPENAI_API_KEY").is_err() && std::env::var("ANTHROPIC_API_KEY").is_err() {
648 eprintln!("Skipping test: No API key found");
649 return;
650 }
651
652 let config = IntelligentBehaviorConfig::default();
653 let examples = vec![ExamplePair {
654 method: "POST".to_string(),
655 path: "/api/users".to_string(),
656 request: Some(json!({
657 "name": "John Doe",
658 "email": "john@example.com"
659 })),
660 status: 201,
661 response: Some(json!({
662 "id": "123",
663 "name": "John Doe",
664 "email": "john@example.com"
665 })),
666 query_params: HashMap::new(),
667 headers: HashMap::new(),
668 metadata: HashMap::new(),
669 }];
670
671 let mockai = match MockAI::from_examples(examples, config).await {
672 Ok(m) => m,
673 Err(e) => {
674 eprintln!("Skipping test: Failed to create MockAI: {}", e);
675 return;
676 }
677 };
678
679 let request = Request {
680 method: "POST".to_string(),
681 path: "/api/users".to_string(),
682 body: Some(json!({
683 "name": "Jane Doe",
684 "email": "jane@example.com"
685 })),
686 query_params: HashMap::new(),
687 headers: HashMap::new(),
688 };
689
690 let response = match mockai.process_request(&request).await {
691 Ok(r) => r,
692 Err(e) => {
693 eprintln!("Skipping test: Failed to process request: {}", e);
694 return;
695 }
696 };
697
698 assert_eq!(response.status_code, 201);
700 assert!(response.body.is_object());
701 }
702
703 #[test]
704 fn test_request_creation() {
705 let mut query_params = HashMap::new();
706 query_params.insert("page".to_string(), "1".to_string());
707
708 let mut headers = HashMap::new();
709 headers.insert("Content-Type".to_string(), "application/json".to_string());
710
711 let request = Request {
712 method: "GET".to_string(),
713 path: "/api/users".to_string(),
714 body: Some(json!({"id": 1})),
715 query_params,
716 headers,
717 };
718
719 assert_eq!(request.method, "GET");
720 assert_eq!(request.path, "/api/users");
721 assert!(request.body.is_some());
722 }
723
724 #[test]
725 fn test_response_creation() {
726 let mut headers = HashMap::new();
727 headers.insert("Content-Type".to_string(), "application/json".to_string());
728
729 let response = Response {
730 status_code: 200,
731 body: json!({"message": "success"}),
732 headers,
733 };
734
735 assert_eq!(response.status_code, 200);
736 assert!(response.body.is_object());
737 }
738
739 #[test]
740 fn test_mockai_new() {
741 let config = IntelligentBehaviorConfig::default();
742 let mockai = MockAI::new(config);
743 let _ = mockai;
745 }
746
747 #[test]
748 fn test_mockai_rules() {
749 let config = IntelligentBehaviorConfig::default();
750 let mockai = MockAI::new(config);
751 let rules = mockai.rules();
752 let _ = rules;
754 }
755
756 #[test]
757 fn test_mockai_update_rules() {
758 let config = IntelligentBehaviorConfig::default();
759 let mut mockai = MockAI::new(config);
760 let new_rules = BehaviorRules::default();
761 mockai.update_rules(new_rules);
762 }
764
765 #[test]
766 fn test_mockai_get_config() {
767 let config = IntelligentBehaviorConfig::default();
768 let mockai = MockAI::new(config.clone());
769 let retrieved_config = mockai.get_config();
770 let _ = retrieved_config;
772 }
773
774 #[test]
775 fn test_mockai_update_config() {
776 let config = IntelligentBehaviorConfig::default();
777 let mut mockai = MockAI::new(config.clone());
778 let new_config = IntelligentBehaviorConfig::default();
779 mockai.update_config(new_config);
780 }
782
783 #[test]
784 fn test_extract_examples_from_openapi_empty_spec() {
785 let spec_json = json!({
786 "openapi": "3.0.0",
787 "info": {
788 "title": "Test API",
789 "version": "1.0.0"
790 },
791 "paths": {}
792 });
793 let spec = OpenApiSpec::from_json(spec_json).unwrap();
794 let examples = MockAI::extract_examples_from_openapi(&spec).unwrap();
795 assert!(examples.is_empty());
796 }
797
798 #[test]
799 fn test_request_with_all_fields() {
800 let mut headers = HashMap::new();
801 headers.insert("Authorization".to_string(), "Bearer token".to_string());
802 let mut query_params = HashMap::new();
803 query_params.insert("limit".to_string(), "10".to_string());
804
805 let request = Request {
806 method: "POST".to_string(),
807 path: "/api/data".to_string(),
808 body: Some(json!({"key": "value"})),
809 query_params: query_params.clone(),
810 headers: headers.clone(),
811 };
812
813 assert_eq!(request.method, "POST");
814 assert_eq!(request.path, "/api/data");
815 assert!(request.body.is_some());
816 assert_eq!(request.query_params.get("limit"), Some(&"10".to_string()));
817 assert_eq!(request.headers.get("Authorization"), Some(&"Bearer token".to_string()));
818 }
819
820 #[test]
821 fn test_response_with_headers() {
822 let mut headers = HashMap::new();
823 headers.insert("X-Total-Count".to_string(), "100".to_string());
824 headers.insert("Content-Type".to_string(), "application/json".to_string());
825
826 let response = Response {
827 status_code: 201,
828 body: json!({"id": "123", "created": true}),
829 headers: headers.clone(),
830 };
831
832 assert_eq!(response.status_code, 201);
833 assert!(response.body.is_object());
834 assert_eq!(response.headers.len(), 2);
835 assert_eq!(response.headers.get("X-Total-Count"), Some(&"100".to_string()));
836 }
837
838 #[test]
839 fn test_request_clone() {
840 let request1 = Request {
841 method: "GET".to_string(),
842 path: "/api/test".to_string(),
843 body: Some(json!({"id": 1})),
844 query_params: HashMap::new(),
845 headers: HashMap::new(),
846 };
847 let request2 = request1.clone();
848 assert_eq!(request1.method, request2.method);
849 assert_eq!(request1.path, request2.path);
850 }
851
852 #[test]
853 fn test_request_debug() {
854 let request = Request {
855 method: "POST".to_string(),
856 path: "/api/users".to_string(),
857 body: None,
858 query_params: HashMap::new(),
859 headers: HashMap::new(),
860 };
861 let debug_str = format!("{:?}", request);
862 assert!(debug_str.contains("Request"));
863 }
864
865 #[test]
866 fn test_response_clone() {
867 let response1 = Response {
868 status_code: 200,
869 body: json!({"status": "ok"}),
870 headers: HashMap::new(),
871 };
872 let response2 = response1.clone();
873 assert_eq!(response1.status_code, response2.status_code);
874 }
875
876 #[test]
877 fn test_response_debug() {
878 let response = Response {
879 status_code: 404,
880 body: json!({"error": "Not found"}),
881 headers: HashMap::new(),
882 };
883 let debug_str = format!("{:?}", response);
884 assert!(debug_str.contains("Response"));
885 }
886
887 #[test]
888 fn test_request_with_empty_fields() {
889 let request = Request {
890 method: "GET".to_string(),
891 path: "/api/test".to_string(),
892 body: None,
893 query_params: HashMap::new(),
894 headers: HashMap::new(),
895 };
896 assert!(request.body.is_none());
897 assert!(request.query_params.is_empty());
898 assert!(request.headers.is_empty());
899 }
900
901 #[test]
902 fn test_response_with_empty_headers() {
903 let response = Response {
904 status_code: 200,
905 body: json!({"data": []}),
906 headers: HashMap::new(),
907 };
908 assert!(response.headers.is_empty());
909 assert!(response.body.is_object());
910 }
911
912 #[test]
913 fn test_request_with_complex_body() {
914 let request = Request {
915 method: "PUT".to_string(),
916 path: "/api/users/123".to_string(),
917 body: Some(json!({
918 "name": "John Doe",
919 "email": "john@example.com",
920 "metadata": {
921 "role": "admin",
922 "permissions": ["read", "write"]
923 }
924 })),
925 query_params: HashMap::new(),
926 headers: HashMap::new(),
927 };
928 assert!(request.body.is_some());
929 let body = request.body.unwrap();
930 assert!(body.is_object());
931 assert!(body.get("metadata").is_some());
932 }
933
934 #[test]
935 fn test_response_with_array_body() {
936 let response = Response {
937 status_code: 200,
938 body: json!([
939 {"id": 1, "name": "Alice"},
940 {"id": 2, "name": "Bob"},
941 {"id": 3, "name": "Charlie"}
942 ]),
943 headers: HashMap::new(),
944 };
945 assert!(response.body.is_array());
946 let array = response.body.as_array().unwrap();
947 assert_eq!(array.len(), 3);
948 }
949
950 #[test]
951 fn test_request_with_multiple_query_params() {
952 let mut query_params = HashMap::new();
953 query_params.insert("page".to_string(), "1".to_string());
954 query_params.insert("limit".to_string(), "20".to_string());
955 query_params.insert("sort".to_string(), "name".to_string());
956 query_params.insert("order".to_string(), "asc".to_string());
957
958 let request = Request {
959 method: "GET".to_string(),
960 path: "/api/users".to_string(),
961 body: None,
962 query_params: query_params.clone(),
963 headers: HashMap::new(),
964 };
965
966 assert_eq!(request.query_params.len(), 4);
967 assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
968 assert_eq!(request.query_params.get("limit"), Some(&"20".to_string()));
969 }
970
971 #[test]
972 fn test_response_with_multiple_headers() {
973 let mut headers = HashMap::new();
974 headers.insert("Content-Type".to_string(), "application/json".to_string());
975 headers.insert("X-Request-ID".to_string(), "req-123".to_string());
976 headers.insert("X-Rate-Limit-Remaining".to_string(), "99".to_string());
977 headers.insert("Cache-Control".to_string(), "no-cache".to_string());
978
979 let response = Response {
980 status_code: 200,
981 body: json!({"data": "test"}),
982 headers: headers.clone(),
983 };
984
985 assert_eq!(response.headers.len(), 4);
986 assert_eq!(response.headers.get("X-Request-ID"), Some(&"req-123".to_string()));
987 }
988
989 #[test]
990 fn test_request_different_methods() {
991 let methods = vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
992 for method in methods {
993 let request = Request {
994 method: method.to_string(),
995 path: "/api/test".to_string(),
996 body: None,
997 query_params: HashMap::new(),
998 headers: HashMap::new(),
999 };
1000 assert_eq!(request.method, method);
1001 }
1002 }
1003
1004 #[test]
1005 fn test_response_different_status_codes() {
1006 let status_codes = vec![200, 201, 204, 400, 401, 403, 404, 500, 503];
1007 for status_code in status_codes {
1008 let response = Response {
1009 status_code,
1010 body: json!({"status": status_code}),
1011 headers: HashMap::new(),
1012 };
1013 assert_eq!(response.status_code, status_code);
1014 }
1015 }
1016
1017 #[tokio::test]
1030 async fn generate_response_body_returns_empty_for_all_mutation_kinds() {
1031 use crate::intelligent_behavior::mutation_analyzer::{MutationAnalysis, MutationType};
1032 let mockai = MockAI::new(IntelligentBehaviorConfig::default());
1033 let req = Request {
1034 method: "POST".to_string(),
1035 path: "/api/anything".to_string(),
1036 body: Some(json!({"name": "X"})),
1037 query_params: HashMap::new(),
1038 headers: HashMap::new(),
1039 };
1040 let ctx = StatefulAiContext::new("test-session", IntelligentBehaviorConfig::default());
1041
1042 for kind in [
1043 MutationType::NoChange,
1044 MutationType::Create,
1045 MutationType::Update,
1046 MutationType::PartialUpdate,
1047 MutationType::Delete,
1048 ] {
1049 let kind_label = format!("{:?}", kind);
1050 let mutation = MutationAnalysis {
1051 mutation_type: kind,
1052 changed_fields: Vec::new(),
1053 added_fields: Vec::new(),
1054 removed_fields: Vec::new(),
1055 validation_issues: Vec::new(),
1056 confidence: 1.0,
1057 };
1058 let body = mockai
1059 .generate_response_body(&mutation, &req, &ctx)
1060 .await
1061 .expect("generate_response_body should not error");
1062 assert_eq!(
1063 body,
1064 json!({}),
1065 "MockAI must return empty object for mutation kind {} so the calling \
1066 site falls back to spec-driven response generation (regression #79 r31). \
1067 Got: {:?}",
1068 kind_label,
1069 body
1070 );
1071 }
1072 }
1073}