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 {
300 self.generate_response_body(&mutation_analysis, request, session_context)
302 .await?
303 } else {
304 tracing::debug!(
307 "Skipping mutation-based response generation for {} request - using OpenAPI response generation",
308 method_upper
309 );
310 serde_json::json!({}) };
312
313 Ok(Response {
314 status_code: 200,
315 body: response_body,
316 headers: HashMap::new(),
317 })
318 }
319
320 pub async fn learn_from_example(&mut self, example: ExamplePair) -> Result<()> {
324 let examples = vec![example];
326 let new_rules = self.rule_generator.generate_rules_from_examples(examples).await?;
327
328 self.merge_rules(new_rules);
330
331 Ok(())
332 }
333
334 pub fn rules(&self) -> &BehaviorRules {
336 &self.rules
337 }
338
339 pub fn update_rules(&mut self, rules: BehaviorRules) {
341 self.rules = rules;
342 self.mutation_analyzer = MutationAnalyzer::new().with_rules(self.rules.clone());
344 }
345
346 pub fn update_config(&mut self, config: IntelligentBehaviorConfig) {
354 self.config = config.clone();
355
356 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 }
364
365 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 pub fn get_config(&self) -> &IntelligentBehaviorConfig {
385 &self.config
386 }
387
388 pub fn extract_examples_from_openapi(spec: &OpenApiSpec) -> Result<Vec<ExamplePair>> {
392 let mut examples = Vec::new();
393
394 let path_operations = spec.all_paths_and_operations();
396
397 for (path, operations) in path_operations {
398 for (method, operation) in operations {
399 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 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 fn is_paginated_request(&self, request: &Request) -> bool {
436 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 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 async fn build_paginated_response(
464 &self,
465 meta: &PaginationMetadata,
466 _request: &Request,
467 ) -> Result<Value> {
468 Ok(serde_json::json!({
470 "data": [], "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 async fn generate_response_body(
487 &self,
488 mutation: &super::mutation_analyzer::MutationAnalysis,
489 request: &Request,
490 _session_context: &StatefulAiContext,
491 ) -> Result<Value> {
492 match mutation.mutation_type {
496 super::mutation_analyzer::MutationType::NoChange => {
497 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 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 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 Ok(serde_json::json!({
522 "status": "deleted",
523 "message": "Resource deleted successfully"
524 }))
525 }
526 _ => {
527 Ok(serde_json::json!({
529 "status": "success",
530 "data": request.body.clone().unwrap_or(serde_json::json!({}))
531 }))
532 }
533 }
534 }
535
536 fn merge_rules(&mut self, new_rules: BehaviorRules) {
538 self.rules.consistency_rules.extend(new_rules.consistency_rules);
540
541 for (key, value) in new_rules.schemas {
543 self.rules.schemas.insert(key, value);
544 }
545
546 for (key, value) in new_rules.state_transitions {
548 self.rules.state_transitions.insert(key, value);
549 }
550
551 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 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 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 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 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 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 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 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 }
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 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 }
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}