1pub mod auth;
7pub mod matcher;
8pub mod middleware;
9pub mod protocol_registry;
10pub mod streaming;
11
12use crate::Result;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::fmt;
16use std::sync::Arc;
17
18pub use auth::{AuthMiddleware, AuthResult, Claims};
20pub use matcher::{FuzzyRequestMatcher, RequestFingerprint, SimpleRequestMatcher};
21pub use middleware::{LatencyMiddleware, LoggingMiddleware, MetricsMiddleware};
22pub use protocol_registry::{ProtocolHandler, ProtocolRegistry};
23pub use streaming::{
24 MessageBuilder, MessageStream, ProtocolMessage, StreamingMetadata, StreamingProtocol,
25 StreamingProtocolRegistry,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
30pub enum Protocol {
31 Http,
33 GraphQL,
35 Grpc,
37 WebSocket,
39 Smtp,
41 Mqtt,
43 Ftp,
45 Kafka,
47 RabbitMq,
49 Amqp,
51 Tcp,
53}
54
55impl fmt::Display for Protocol {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Protocol::Http => write!(f, "HTTP"),
59 Protocol::GraphQL => write!(f, "GraphQL"),
60 Protocol::Grpc => write!(f, "gRPC"),
61 Protocol::WebSocket => write!(f, "WebSocket"),
62 Protocol::Smtp => write!(f, "SMTP"),
63 Protocol::Mqtt => write!(f, "MQTT"),
64 Protocol::Ftp => write!(f, "FTP"),
65 Protocol::Kafka => write!(f, "Kafka"),
66 Protocol::RabbitMq => write!(f, "RabbitMQ"),
67 Protocol::Amqp => write!(f, "AMQP"),
68 Protocol::Tcp => write!(f, "TCP"),
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
75pub enum MessagePattern {
76 RequestResponse,
78 OneWay,
80 PubSub,
82 Streaming,
84}
85
86impl fmt::Display for MessagePattern {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 MessagePattern::RequestResponse => write!(f, "Request-Response"),
90 MessagePattern::OneWay => write!(f, "One-Way"),
91 MessagePattern::PubSub => write!(f, "Pub-Sub"),
92 MessagePattern::Streaming => write!(f, "Streaming"),
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct ProtocolRequest {
100 pub protocol: Protocol,
102 pub pattern: MessagePattern,
104 pub operation: String,
106 pub path: String,
108 pub topic: Option<String>,
110 pub routing_key: Option<String>,
112 pub partition: Option<i32>,
114 pub qos: Option<u8>,
116 pub metadata: HashMap<String, String>,
118 pub body: Option<Vec<u8>>,
120 pub client_ip: Option<String>,
122}
123
124impl Default for ProtocolRequest {
125 fn default() -> Self {
126 Self {
127 protocol: Protocol::Http,
128 pattern: MessagePattern::RequestResponse,
129 operation: String::new(),
130 path: String::new(),
131 topic: None,
132 routing_key: None,
133 partition: None,
134 qos: None,
135 metadata: HashMap::new(),
136 body: None,
137 client_ip: None,
138 }
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct ProtocolResponse {
145 pub status: ResponseStatus,
147 pub metadata: HashMap<String, String>,
149 pub body: Vec<u8>,
151 pub content_type: String,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
157pub enum ResponseStatus {
158 HttpStatus(u16),
160 GrpcStatus(i32),
162 GraphQLStatus(bool),
164 WebSocketStatus(bool),
166 SmtpStatus(u16),
168 MqttStatus(bool),
170 KafkaStatus(i16),
172 AmqpStatus(u16),
174 FtpStatus(u16),
176 TcpStatus(bool),
178}
179
180impl ResponseStatus {
181 pub fn is_success(&self) -> bool {
183 match self {
184 ResponseStatus::HttpStatus(code) => (200..300).contains(code),
185 ResponseStatus::GrpcStatus(code) => *code == 0, ResponseStatus::GraphQLStatus(success) => *success,
187 ResponseStatus::WebSocketStatus(success) => *success,
188 ResponseStatus::SmtpStatus(code) => (200..300).contains(code), ResponseStatus::MqttStatus(success) => *success,
190 ResponseStatus::KafkaStatus(code) => *code == 0, ResponseStatus::AmqpStatus(code) => (200..300).contains(code), ResponseStatus::FtpStatus(code) => (200..300).contains(code), ResponseStatus::TcpStatus(success) => *success, }
195 }
196
197 pub fn as_code(&self) -> Option<i32> {
199 match self {
200 ResponseStatus::HttpStatus(code) => Some(*code as i32),
201 ResponseStatus::GrpcStatus(code) => Some(*code),
202 ResponseStatus::SmtpStatus(code) => Some(*code as i32),
203 ResponseStatus::KafkaStatus(code) => Some(*code as i32),
204 ResponseStatus::AmqpStatus(code) => Some(*code as i32),
205 ResponseStatus::FtpStatus(code) => Some(*code as i32),
206 ResponseStatus::TcpStatus(_) => None, ResponseStatus::GraphQLStatus(_)
208 | ResponseStatus::WebSocketStatus(_)
209 | ResponseStatus::MqttStatus(_) => None,
210 }
211 }
212}
213
214pub trait SpecRegistry: Send + Sync {
216 fn protocol(&self) -> Protocol;
218
219 fn operations(&self) -> Vec<SpecOperation>;
221
222 fn find_operation(&self, operation: &str, path: &str) -> Option<SpecOperation>;
224
225 fn validate_request(&self, request: &ProtocolRequest) -> Result<ValidationResult>;
227
228 fn generate_mock_response(&self, request: &ProtocolRequest) -> Result<ProtocolResponse>;
230}
231
232#[derive(Debug, Clone)]
234pub struct SpecOperation {
235 pub name: String,
237 pub path: String,
239 pub operation_type: String,
241 pub input_schema: Option<String>,
243 pub output_schema: Option<String>,
245 pub metadata: HashMap<String, String>,
247}
248
249#[derive(Debug, Clone)]
251pub struct ValidationResult {
252 pub valid: bool,
254 pub errors: Vec<ValidationError>,
256 pub warnings: Vec<String>,
258}
259
260#[derive(Debug, Clone)]
262pub struct ValidationError {
263 pub message: String,
265 pub path: Option<String>,
267 pub code: Option<String>,
269}
270
271impl ValidationResult {
272 pub fn success() -> Self {
274 Self {
275 valid: true,
276 errors: Vec::new(),
277 warnings: Vec::new(),
278 }
279 }
280
281 pub fn failure(errors: Vec<ValidationError>) -> Self {
283 Self {
284 valid: false,
285 errors,
286 warnings: Vec::new(),
287 }
288 }
289
290 pub fn with_warning(mut self, warning: String) -> Self {
292 self.warnings.push(warning);
293 self
294 }
295}
296
297#[async_trait::async_trait]
299pub trait ProtocolMiddleware: Send + Sync {
300 fn name(&self) -> &str;
302
303 async fn process_request(&self, request: &mut ProtocolRequest) -> Result<()>;
305
306 async fn process_response(
308 &self,
309 request: &ProtocolRequest,
310 response: &mut ProtocolResponse,
311 ) -> Result<()>;
312
313 fn supports_protocol(&self, protocol: Protocol) -> bool;
315}
316
317pub trait RequestMatcher: Send + Sync {
319 fn match_score(&self, request: &ProtocolRequest) -> f64;
321
322 fn protocol(&self) -> Protocol;
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct UnifiedFixture {
329 pub id: String,
331
332 pub name: String,
334
335 #[serde(default)]
337 pub description: String,
338
339 pub protocol: Protocol,
341
342 pub request: FixtureRequest,
344
345 pub response: FixtureResponse,
347
348 #[serde(default)]
350 pub metadata: HashMap<String, serde_json::Value>,
351
352 #[serde(default = "default_true")]
354 pub enabled: bool,
355
356 #[serde(default)]
358 pub priority: i32,
359
360 #[serde(default)]
362 pub tags: Vec<String>,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct FixtureRequest {
368 #[serde(default)]
370 pub pattern: Option<MessagePattern>,
371
372 pub operation: Option<String>,
374
375 pub path: Option<String>,
377
378 pub topic: Option<String>,
380
381 pub routing_key: Option<String>,
383
384 pub partition: Option<i32>,
386
387 pub qos: Option<u8>,
389
390 #[serde(default)]
392 pub headers: HashMap<String, String>,
393
394 pub body_pattern: Option<String>,
396
397 pub custom_matcher: Option<String>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct FixtureResponse {
404 pub status: FixtureStatus,
406
407 #[serde(default)]
409 pub headers: HashMap<String, String>,
410
411 pub body: Option<serde_json::Value>,
413
414 pub content_type: Option<String>,
416
417 #[serde(default)]
419 pub delay_ms: u64,
420
421 #[serde(default)]
423 pub template_vars: HashMap<String, serde_json::Value>,
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
428#[serde(untagged)]
429pub enum FixtureStatus {
430 Http(u16),
432 Grpc(i32),
434 Generic(bool),
436 Custom {
438 code: i32,
440 message: String,
442 },
443}
444
445fn default_true() -> bool {
446 true
447}
448
449impl UnifiedFixture {
450 pub fn matches(&self, request: &ProtocolRequest) -> bool {
452 if request.protocol != self.protocol {
454 return false;
455 }
456
457 if let Some(pattern) = &self.request.pattern {
459 if request.pattern != *pattern {
460 return false;
461 }
462 }
463
464 if let Some(operation) = &self.request.operation {
466 if !self.matches_pattern(&request.operation, operation) {
467 return false;
468 }
469 }
470
471 if let Some(path) = &self.request.path {
473 if !self.matches_pattern(&request.path, path) {
474 return false;
475 }
476 }
477
478 if let Some(topic) = &self.request.topic {
480 if !self.matches_pattern(request.topic.as_ref().unwrap_or(&String::new()), topic) {
481 return false;
482 }
483 }
484
485 if let Some(routing_key) = &self.request.routing_key {
487 if !self.matches_pattern(
488 request.routing_key.as_ref().unwrap_or(&String::new()),
489 routing_key,
490 ) {
491 return false;
492 }
493 }
494
495 if let Some(partition) = self.request.partition {
497 if request.partition != Some(partition) {
498 return false;
499 }
500 }
501
502 if let Some(qos) = self.request.qos {
504 if request.qos != Some(qos) {
505 return false;
506 }
507 }
508
509 for (key, expected_value) in &self.request.headers {
511 if let Some(actual_value) = request.metadata.get(key) {
512 if !self.matches_pattern(actual_value, expected_value) {
513 return false;
514 }
515 } else {
516 return false;
517 }
518 }
519
520 if let Some(pattern) = &self.request.body_pattern {
522 if let Some(body) = &request.body {
523 let body_str = String::from_utf8_lossy(body);
524 if !self.matches_pattern(&body_str, pattern) {
525 return false;
526 }
527 } else {
528 return false;
529 }
530 }
531
532 if let Some(custom_matcher) = &self.request.custom_matcher {
534 if !self.evaluate_custom_matcher(custom_matcher, request) {
535 return false;
536 }
537 }
538
539 true
540 }
541
542 fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
544 use regex::Regex;
545
546 if let Ok(re) = Regex::new(pattern) {
548 re.is_match(value)
549 } else {
550 value == pattern
552 }
553 }
554
555 fn evaluate_custom_matcher(&self, expression: &str, request: &ProtocolRequest) -> bool {
557 let expr = expression.trim();
565
566 if expr.contains("==") {
568 self.evaluate_equality(expr, request)
569 } else if expr.contains("=~") {
570 self.evaluate_regex_match(expr, request)
571 } else if expr.contains("contains") {
572 self.evaluate_contains(expr, request)
573 } else {
574 tracing::warn!("Unknown custom matcher expression format: {}", expr);
576 false
577 }
578 }
579
580 fn evaluate_equality(&self, expr: &str, request: &ProtocolRequest) -> bool {
582 let parts: Vec<&str> = expr.split("==").map(|s| s.trim()).collect();
583 if parts.len() != 2 {
584 return false;
585 }
586
587 let field = parts[0];
588 let expected_value = parts[1].trim_matches('"');
589
590 match field {
591 "operation" => request.operation == expected_value,
592 "path" => request.path == expected_value,
593 "topic" => request.topic.as_ref().unwrap_or(&String::new()) == expected_value,
594 "routing_key" => {
595 request.routing_key.as_ref().unwrap_or(&String::new()) == expected_value
596 }
597 _ if field.starts_with("headers.") => {
598 let header_name = &field[8..]; request.metadata.get(header_name).is_some_and(|v| v == expected_value)
600 }
601 _ => {
602 tracing::warn!("Unknown field in equality expression: {}", field);
603 false
604 }
605 }
606 }
607
608 fn evaluate_regex_match(&self, expr: &str, request: &ProtocolRequest) -> bool {
610 let parts: Vec<&str> = expr.split("=~").map(|s| s.trim()).collect();
611 if parts.len() != 2 {
612 return false;
613 }
614
615 let field = parts[0];
616 let pattern = parts[1].trim_matches('"');
617
618 let value: String = match field {
619 "operation" => request.operation.clone(),
620 "path" => request.path.clone(),
621 "topic" => request.topic.clone().unwrap_or_default(),
622 "routing_key" => request.routing_key.clone().unwrap_or_default(),
623 _ if field.starts_with("headers.") => {
624 let header_name = &field[8..]; request.metadata.get(header_name).cloned().unwrap_or_default()
626 }
627 _ => {
628 tracing::warn!("Unknown field in regex expression: {}", field);
629 return false;
630 }
631 };
632
633 use regex::Regex;
634 match Regex::new(pattern) {
635 Ok(re) => re.is_match(&value),
636 Err(e) => {
637 tracing::warn!("Invalid regex pattern '{}': {}", pattern, e);
638 false
639 }
640 }
641 }
642
643 fn evaluate_contains(&self, expr: &str, request: &ProtocolRequest) -> bool {
645 let parts: Vec<&str> = expr.split("contains").map(|s| s.trim()).collect();
646 if parts.len() != 2 {
647 return false;
648 }
649
650 let field = parts[0];
651 let substring = parts[1].trim_matches('"');
652
653 let value: String = match field {
654 "body" => {
655 if let Some(body) = &request.body {
656 String::from_utf8_lossy(body).to_string()
657 } else {
658 return false;
659 }
660 }
661 _ if field.starts_with("headers.") => {
662 let header_name = &field[8..]; request.metadata.get(header_name).cloned().unwrap_or_default()
664 }
665 _ => {
666 tracing::warn!("Unsupported field for contains expression: {}", field);
667 return false;
668 }
669 };
670
671 value.contains(substring)
672 }
673
674 pub fn to_protocol_response(&self) -> Result<ProtocolResponse> {
676 let status = match &self.response.status {
677 FixtureStatus::Http(code) => ResponseStatus::HttpStatus(*code),
678 FixtureStatus::Grpc(code) => ResponseStatus::GrpcStatus(*code),
679 FixtureStatus::Generic(success) => ResponseStatus::GraphQLStatus(*success), FixtureStatus::Custom { code, .. } => ResponseStatus::GrpcStatus(*code), };
682
683 let body = match &self.response.body {
684 Some(serde_json::Value::String(s)) => s.clone().into_bytes(),
685 Some(value) => serde_json::to_string(value)?.into_bytes(),
686 None => Vec::new(),
687 };
688
689 let content_type = self
690 .response
691 .content_type
692 .clone()
693 .unwrap_or_else(|| "application/json".to_string());
694
695 Ok(ProtocolResponse {
696 status,
697 metadata: self.response.headers.clone(),
698 body,
699 content_type,
700 })
701 }
702}
703
704pub struct MiddlewareChain {
706 middleware: Vec<Arc<dyn ProtocolMiddleware>>,
708}
709
710impl MiddlewareChain {
711 pub fn new() -> Self {
713 Self {
714 middleware: Vec::new(),
715 }
716 }
717
718 pub fn with_middleware(mut self, middleware: Arc<dyn ProtocolMiddleware>) -> Self {
720 self.middleware.push(middleware);
721 self
722 }
723
724 pub async fn process_request(&self, request: &mut ProtocolRequest) -> Result<()> {
726 for middleware in &self.middleware {
727 if middleware.supports_protocol(request.protocol) {
728 middleware.process_request(request).await?;
729 }
730 }
731 Ok(())
732 }
733
734 pub async fn process_response(
736 &self,
737 request: &ProtocolRequest,
738 response: &mut ProtocolResponse,
739 ) -> Result<()> {
740 for middleware in self.middleware.iter().rev() {
741 if middleware.supports_protocol(request.protocol) {
742 middleware.process_response(request, response).await?;
743 }
744 }
745 Ok(())
746 }
747}
748
749impl Default for MiddlewareChain {
750 fn default() -> Self {
751 Self::new()
752 }
753}
754
755#[cfg(test)]
756mod tests {
757 use super::*;
758
759 #[test]
760 fn test_protocol_display() {
761 assert_eq!(Protocol::Http.to_string(), "HTTP");
762 assert_eq!(Protocol::GraphQL.to_string(), "GraphQL");
763 assert_eq!(Protocol::Grpc.to_string(), "gRPC");
764 assert_eq!(Protocol::WebSocket.to_string(), "WebSocket");
765 assert_eq!(Protocol::Smtp.to_string(), "SMTP");
766 assert_eq!(Protocol::Mqtt.to_string(), "MQTT");
767 assert_eq!(Protocol::Ftp.to_string(), "FTP");
768 assert_eq!(Protocol::Kafka.to_string(), "Kafka");
769 assert_eq!(Protocol::RabbitMq.to_string(), "RabbitMQ");
770 assert_eq!(Protocol::Amqp.to_string(), "AMQP");
771 assert_eq!(Protocol::Tcp.to_string(), "TCP");
772 }
773
774 #[test]
775 fn test_response_status_is_success() {
776 assert!(ResponseStatus::HttpStatus(200).is_success());
777 assert!(ResponseStatus::HttpStatus(204).is_success());
778 assert!(!ResponseStatus::HttpStatus(404).is_success());
779 assert!(!ResponseStatus::HttpStatus(500).is_success());
780
781 assert!(ResponseStatus::GrpcStatus(0).is_success());
782 assert!(!ResponseStatus::GrpcStatus(2).is_success());
783
784 assert!(ResponseStatus::GraphQLStatus(true).is_success());
785 assert!(!ResponseStatus::GraphQLStatus(false).is_success());
786 }
787
788 #[test]
789 fn test_response_status_as_code() {
790 assert_eq!(ResponseStatus::HttpStatus(200).as_code(), Some(200));
791 assert_eq!(ResponseStatus::GrpcStatus(0).as_code(), Some(0));
792 assert_eq!(ResponseStatus::GraphQLStatus(true).as_code(), None);
793 }
794
795 #[test]
796 fn test_validation_result_success() {
797 let result = ValidationResult::success();
798 assert!(result.valid);
799 assert_eq!(result.errors.len(), 0);
800 assert_eq!(result.warnings.len(), 0);
801 }
802
803 #[test]
804 fn test_validation_result_failure() {
805 let errors = vec![ValidationError {
806 message: "Invalid field".to_string(),
807 path: Some("body.field".to_string()),
808 code: Some("INVALID_FIELD".to_string()),
809 }];
810 let result = ValidationResult::failure(errors);
811 assert!(!result.valid);
812 assert_eq!(result.errors.len(), 1);
813 }
814
815 #[test]
816 fn test_validation_result_with_warning() {
817 let result = ValidationResult::success().with_warning("Deprecated field used".to_string());
818 assert!(result.valid);
819 assert_eq!(result.warnings.len(), 1);
820 }
821
822 #[test]
823 fn test_middleware_chain_creation() {
824 let chain = MiddlewareChain::new();
825 assert_eq!(chain.middleware.len(), 0);
826 }
827
828 #[test]
829 fn test_protocol_request_creation() {
830 let request = ProtocolRequest {
831 protocol: Protocol::Http,
832 operation: "GET".to_string(),
833 path: "/users".to_string(),
834 client_ip: Some("127.0.0.1".to_string()),
835 ..Default::default()
836 };
837 assert_eq!(request.protocol, Protocol::Http);
838 assert_eq!(request.pattern, MessagePattern::RequestResponse);
839 assert_eq!(request.operation, "GET");
840 assert_eq!(request.path, "/users");
841 }
842
843 #[test]
844 fn test_protocol_response_creation() {
845 let response = ProtocolResponse {
846 status: ResponseStatus::HttpStatus(200),
847 metadata: HashMap::new(),
848 body: b"{}".to_vec(),
849 content_type: "application/json".to_string(),
850 };
851 assert!(response.status.is_success());
852 assert_eq!(response.content_type, "application/json");
853 }
854
855 #[test]
856 fn test_unified_fixture_matching() {
857 let fixture = UnifiedFixture {
858 id: "test-fixture".to_string(),
859 name: "Test Fixture".to_string(),
860 description: "A test fixture".to_string(),
861 protocol: Protocol::Http,
862 request: FixtureRequest {
863 pattern: Some(MessagePattern::RequestResponse),
864 operation: Some("GET".to_string()),
865 path: Some("/api/users".to_string()),
866 topic: None,
867 routing_key: None,
868 partition: None,
869 qos: None,
870 headers: HashMap::new(),
871 body_pattern: None,
872 custom_matcher: None,
873 },
874 response: FixtureResponse {
875 status: FixtureStatus::Http(200),
876 headers: HashMap::new(),
877 body: Some(serde_json::json!({"users": ["john", "jane"]})),
878 content_type: Some("application/json".to_string()),
879 delay_ms: 0,
880 template_vars: HashMap::new(),
881 },
882 metadata: HashMap::new(),
883 enabled: true,
884 priority: 0,
885 tags: vec![],
886 };
887
888 let matching_request = ProtocolRequest {
889 protocol: Protocol::Http,
890 pattern: MessagePattern::RequestResponse,
891 operation: "GET".to_string(),
892 path: "/api/users".to_string(),
893 topic: None,
894 routing_key: None,
895 partition: None,
896 qos: None,
897 metadata: HashMap::new(),
898 body: None,
899 client_ip: None,
900 };
901
902 let non_matching_request = ProtocolRequest {
903 protocol: Protocol::Http,
904 pattern: MessagePattern::RequestResponse,
905 operation: "POST".to_string(),
906 path: "/api/users".to_string(),
907 topic: None,
908 routing_key: None,
909 partition: None,
910 qos: None,
911 metadata: HashMap::new(),
912 body: None,
913 client_ip: None,
914 };
915
916 assert!(fixture.matches(&matching_request));
917 assert!(!fixture.matches(&non_matching_request));
918 }
919
920 #[test]
921 fn test_fixture_to_protocol_response() {
922 let fixture = UnifiedFixture {
923 id: "test".to_string(),
924 name: "Test".to_string(),
925 description: "".to_string(),
926 protocol: Protocol::Http,
927 request: FixtureRequest {
928 pattern: None,
929 operation: None,
930 path: None,
931 topic: None,
932 routing_key: None,
933 partition: None,
934 qos: None,
935 headers: HashMap::new(),
936 body_pattern: None,
937 custom_matcher: None,
938 },
939 response: FixtureResponse {
940 status: FixtureStatus::Http(200),
941 headers: {
942 let mut h = HashMap::new();
943 h.insert("content-type".to_string(), "application/json".to_string());
944 h
945 },
946 body: Some(serde_json::json!({"message": "ok"})),
947 content_type: Some("application/json".to_string()),
948 delay_ms: 0,
949 template_vars: HashMap::new(),
950 },
951 metadata: HashMap::new(),
952 enabled: true,
953 priority: 0,
954 tags: vec![],
955 };
956
957 let response = fixture.to_protocol_response().unwrap();
958 assert!(response.status.is_success());
959 assert_eq!(response.content_type, "application/json");
960 assert_eq!(response.metadata.get("content-type"), Some(&"application/json".to_string()));
961 }
962
963 #[test]
964 fn test_fixture_status_serialization() {
965 let status = FixtureStatus::Http(404);
967 let serialized = serde_json::to_string(&status).unwrap();
968 assert_eq!(serialized, "404");
969
970 let status = FixtureStatus::Grpc(5);
972 let serialized = serde_json::to_string(&status).unwrap();
973 assert_eq!(serialized, "5");
974
975 let status = FixtureStatus::Generic(true);
977 let serialized = serde_json::to_string(&status).unwrap();
978 assert_eq!(serialized, "true");
979
980 let status = FixtureStatus::Custom {
982 code: 500,
983 message: "Internal Error".to_string(),
984 };
985 let serialized = serde_json::to_string(&status).unwrap();
986 let expected: serde_json::Value =
987 serde_json::json!({"code": 500, "message": "Internal Error"});
988 assert_eq!(serde_json::from_str::<serde_json::Value>(&serialized).unwrap(), expected);
989 }
990
991 #[test]
992 fn test_fixture_pattern_matching() {
993 let fixture = UnifiedFixture {
994 id: "test".to_string(),
995 name: "Test".to_string(),
996 description: "".to_string(),
997 protocol: Protocol::Http,
998 request: FixtureRequest {
999 pattern: Some(MessagePattern::RequestResponse),
1000 operation: Some("GET".to_string()),
1001 path: Some("/api/.*".to_string()),
1002 topic: None,
1003 routing_key: None,
1004 partition: None,
1005 qos: None,
1006 headers: HashMap::new(),
1007 body_pattern: None,
1008 custom_matcher: None,
1009 },
1010 response: FixtureResponse {
1011 status: FixtureStatus::Http(200),
1012 headers: HashMap::new(),
1013 body: None,
1014 content_type: None,
1015 delay_ms: 0,
1016 template_vars: HashMap::new(),
1017 },
1018 metadata: HashMap::new(),
1019 enabled: true,
1020 priority: 0,
1021 tags: vec![],
1022 };
1023
1024 let request = ProtocolRequest {
1026 protocol: Protocol::Http,
1027 pattern: MessagePattern::RequestResponse,
1028 operation: "GET".to_string(),
1029 path: "/api/users".to_string(),
1030 topic: None,
1031 routing_key: None,
1032 partition: None,
1033 qos: None,
1034 metadata: HashMap::new(),
1035 body: None,
1036 client_ip: None,
1037 };
1038 assert!(fixture.matches(&request));
1039
1040 let grpc_request = ProtocolRequest {
1042 protocol: Protocol::Grpc,
1043 pattern: MessagePattern::RequestResponse,
1044 operation: "GET".to_string(),
1045 path: "/api/users".to_string(),
1046 topic: None,
1047 routing_key: None,
1048 partition: None,
1049 qos: None,
1050 metadata: HashMap::new(),
1051 body: None,
1052 client_ip: None,
1053 };
1054 assert!(!fixture.matches(&grpc_request));
1055
1056 let post_request = ProtocolRequest {
1058 protocol: Protocol::Http,
1059 pattern: MessagePattern::RequestResponse,
1060 operation: "POST".to_string(),
1061 path: "/api/users".to_string(),
1062 topic: None,
1063 routing_key: None,
1064 partition: None,
1065 qos: None,
1066 metadata: HashMap::new(),
1067 body: None,
1068 client_ip: None,
1069 };
1070 assert!(!fixture.matches(&post_request));
1071 }
1072
1073 #[test]
1074 fn test_custom_matcher_equality() {
1075 let fixture = UnifiedFixture {
1076 id: "test".to_string(),
1077 name: "Test".to_string(),
1078 description: "".to_string(),
1079 protocol: Protocol::Http,
1080 request: FixtureRequest {
1081 pattern: Some(MessagePattern::RequestResponse),
1082 operation: Some("GET".to_string()),
1083 path: Some("/api/users".to_string()),
1084 topic: None,
1085 routing_key: None,
1086 partition: None,
1087 qos: None,
1088 headers: HashMap::new(),
1089 body_pattern: None,
1090 custom_matcher: Some("operation == \"GET\"".to_string()),
1091 },
1092 response: FixtureResponse {
1093 status: FixtureStatus::Http(200),
1094 headers: HashMap::new(),
1095 body: None,
1096 content_type: None,
1097 delay_ms: 0,
1098 template_vars: HashMap::new(),
1099 },
1100 metadata: HashMap::new(),
1101 enabled: true,
1102 priority: 0,
1103 tags: vec![],
1104 };
1105
1106 let request = ProtocolRequest {
1108 protocol: Protocol::Http,
1109 pattern: MessagePattern::RequestResponse,
1110 operation: "GET".to_string(),
1111 path: "/api/users".to_string(),
1112 topic: None,
1113 routing_key: None,
1114 partition: None,
1115 qos: None,
1116 metadata: HashMap::new(),
1117 body: None,
1118 client_ip: None,
1119 };
1120 assert!(fixture.matches(&request));
1121
1122 let post_request = ProtocolRequest {
1124 protocol: Protocol::Http,
1125 pattern: MessagePattern::RequestResponse,
1126 operation: "POST".to_string(),
1127 path: "/api/users".to_string(),
1128 topic: None,
1129 routing_key: None,
1130 partition: None,
1131 qos: None,
1132 metadata: HashMap::new(),
1133 body: None,
1134 client_ip: None,
1135 };
1136 assert!(!fixture.matches(&post_request));
1137 }
1138
1139 #[test]
1140 fn test_custom_matcher_regex() {
1141 let fixture = UnifiedFixture {
1142 id: "test".to_string(),
1143 name: "Test".to_string(),
1144 description: "".to_string(),
1145 protocol: Protocol::Http,
1146 request: FixtureRequest {
1147 pattern: Some(MessagePattern::RequestResponse),
1148 operation: Some("GET".to_string()),
1149 path: Some("/api/.*".to_string()),
1150 topic: None,
1151 routing_key: None,
1152 partition: None,
1153 qos: None,
1154 headers: HashMap::new(),
1155 body_pattern: None,
1156 custom_matcher: Some("path =~ \"/api/.*\"".to_string()),
1157 },
1158 response: FixtureResponse {
1159 status: FixtureStatus::Http(200),
1160 headers: HashMap::new(),
1161 body: None,
1162 content_type: None,
1163 delay_ms: 0,
1164 template_vars: HashMap::new(),
1165 },
1166 metadata: HashMap::new(),
1167 enabled: true,
1168 priority: 0,
1169 tags: vec![],
1170 };
1171
1172 let request = ProtocolRequest {
1174 protocol: Protocol::Http,
1175 pattern: MessagePattern::RequestResponse,
1176 operation: "GET".to_string(),
1177 path: "/api/users".to_string(),
1178 topic: None,
1179 routing_key: None,
1180 partition: None,
1181 qos: None,
1182 metadata: HashMap::new(),
1183 body: None,
1184 client_ip: None,
1185 };
1186 assert!(fixture.matches(&request));
1187
1188 let other_request = ProtocolRequest {
1190 protocol: Protocol::Http,
1191 pattern: MessagePattern::RequestResponse,
1192 operation: "GET".to_string(),
1193 path: "/other/path".to_string(),
1194 topic: None,
1195 routing_key: None,
1196 partition: None,
1197 qos: None,
1198 metadata: HashMap::new(),
1199 body: None,
1200 client_ip: None,
1201 };
1202 assert!(!fixture.matches(&other_request));
1203 }
1204
1205 #[test]
1206 fn test_custom_matcher_contains() {
1207 let fixture = UnifiedFixture {
1208 id: "test".to_string(),
1209 name: "Test".to_string(),
1210 description: "".to_string(),
1211 protocol: Protocol::Http,
1212 request: FixtureRequest {
1213 pattern: Some(MessagePattern::RequestResponse),
1214 operation: Some("POST".to_string()),
1215 path: Some("/api/users".to_string()),
1216 topic: None,
1217 routing_key: None,
1218 partition: None,
1219 qos: None,
1220 headers: HashMap::new(),
1221 body_pattern: None,
1222 custom_matcher: Some("body contains \"test\"".to_string()),
1223 },
1224 response: FixtureResponse {
1225 status: FixtureStatus::Http(200),
1226 headers: HashMap::new(),
1227 body: None,
1228 content_type: None,
1229 delay_ms: 0,
1230 template_vars: HashMap::new(),
1231 },
1232 metadata: HashMap::new(),
1233 enabled: true,
1234 priority: 0,
1235 tags: vec![],
1236 };
1237
1238 let request = ProtocolRequest {
1240 protocol: Protocol::Http,
1241 pattern: MessagePattern::RequestResponse,
1242 operation: "POST".to_string(),
1243 path: "/api/users".to_string(),
1244 topic: None,
1245 routing_key: None,
1246 partition: None,
1247 qos: None,
1248 metadata: HashMap::new(),
1249 body: Some(b"{\"name\": \"test user\"}".to_vec()),
1250 client_ip: None,
1251 };
1252 assert!(fixture.matches(&request));
1253
1254 let no_match_request = ProtocolRequest {
1256 protocol: Protocol::Http,
1257 pattern: MessagePattern::RequestResponse,
1258 operation: "POST".to_string(),
1259 path: "/api/users".to_string(),
1260 topic: None,
1261 routing_key: None,
1262 partition: None,
1263 qos: None,
1264 metadata: HashMap::new(),
1265 body: Some(b"{\"name\": \"other user\"}".to_vec()),
1266 client_ip: None,
1267 };
1268 assert!(!fixture.matches(&no_match_request));
1269 }
1270
1271 #[test]
1272 fn test_custom_matcher_header() {
1273 let fixture = UnifiedFixture {
1274 id: "test".to_string(),
1275 name: "Test".to_string(),
1276 description: "".to_string(),
1277 protocol: Protocol::Http,
1278 request: FixtureRequest {
1279 pattern: Some(MessagePattern::RequestResponse),
1280 operation: Some("GET".to_string()),
1281 path: Some("/api/data".to_string()),
1282 topic: None,
1283 routing_key: None,
1284 partition: None,
1285 qos: None,
1286 headers: HashMap::new(),
1287 body_pattern: None,
1288 custom_matcher: Some("headers.content-type == \"application/json\"".to_string()),
1289 },
1290 response: FixtureResponse {
1291 status: FixtureStatus::Http(200),
1292 headers: HashMap::new(),
1293 body: None,
1294 content_type: None,
1295 delay_ms: 0,
1296 template_vars: HashMap::new(),
1297 },
1298 metadata: HashMap::new(),
1299 enabled: true,
1300 priority: 0,
1301 tags: vec![],
1302 };
1303
1304 let mut headers = HashMap::new();
1306 headers.insert("content-type".to_string(), "application/json".to_string());
1307 let request = ProtocolRequest {
1308 protocol: Protocol::Http,
1309 pattern: MessagePattern::RequestResponse,
1310 operation: "GET".to_string(),
1311 path: "/api/data".to_string(),
1312 topic: None,
1313 routing_key: None,
1314 partition: None,
1315 qos: None,
1316 metadata: headers,
1317 body: None,
1318 client_ip: None,
1319 };
1320 assert!(fixture.matches(&request));
1321
1322 let mut wrong_headers = HashMap::new();
1324 wrong_headers.insert("content-type".to_string(), "text/plain".to_string());
1325 let wrong_request = ProtocolRequest {
1326 protocol: Protocol::Http,
1327 pattern: MessagePattern::RequestResponse,
1328 operation: "GET".to_string(),
1329 path: "/api/data".to_string(),
1330 topic: None,
1331 routing_key: None,
1332 partition: None,
1333 qos: None,
1334 metadata: wrong_headers,
1335 body: None,
1336 client_ip: None,
1337 };
1338 assert!(!fixture.matches(&wrong_request));
1339 }
1340}