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