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 { code: i32, message: String },
431}
432
433fn default_true() -> bool {
434 true
435}
436
437impl UnifiedFixture {
438 pub fn matches(&self, request: &ProtocolRequest) -> bool {
440 if request.protocol != self.protocol {
442 return false;
443 }
444
445 if let Some(pattern) = &self.request.pattern {
447 if request.pattern != *pattern {
448 return false;
449 }
450 }
451
452 if let Some(operation) = &self.request.operation {
454 if !self.matches_pattern(&request.operation, operation) {
455 return false;
456 }
457 }
458
459 if let Some(path) = &self.request.path {
461 if !self.matches_pattern(&request.path, path) {
462 return false;
463 }
464 }
465
466 if let Some(topic) = &self.request.topic {
468 if !self.matches_pattern(request.topic.as_ref().unwrap_or(&String::new()), topic) {
469 return false;
470 }
471 }
472
473 if let Some(routing_key) = &self.request.routing_key {
475 if !self.matches_pattern(
476 request.routing_key.as_ref().unwrap_or(&String::new()),
477 routing_key,
478 ) {
479 return false;
480 }
481 }
482
483 if let Some(partition) = self.request.partition {
485 if request.partition != Some(partition) {
486 return false;
487 }
488 }
489
490 if let Some(qos) = self.request.qos {
492 if request.qos != Some(qos) {
493 return false;
494 }
495 }
496
497 for (key, expected_value) in &self.request.headers {
499 if let Some(actual_value) = request.metadata.get(key) {
500 if !self.matches_pattern(actual_value, expected_value) {
501 return false;
502 }
503 } else {
504 return false;
505 }
506 }
507
508 if let Some(pattern) = &self.request.body_pattern {
510 if let Some(body) = &request.body {
511 let body_str = String::from_utf8_lossy(body);
512 if !self.matches_pattern(&body_str, pattern) {
513 return false;
514 }
515 } else {
516 return false;
517 }
518 }
519
520 if let Some(custom_matcher) = &self.request.custom_matcher {
522 if !self.evaluate_custom_matcher(custom_matcher, request) {
523 return false;
524 }
525 }
526
527 true
528 }
529
530 fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
532 use regex::Regex;
533
534 if let Ok(re) = Regex::new(pattern) {
536 re.is_match(value)
537 } else {
538 value == pattern
540 }
541 }
542
543 fn evaluate_custom_matcher(&self, expression: &str, request: &ProtocolRequest) -> bool {
545 let expr = expression.trim();
553
554 if expr.contains("==") {
556 self.evaluate_equality(expr, request)
557 } else if expr.contains("=~") {
558 self.evaluate_regex_match(expr, request)
559 } else if expr.contains("contains") {
560 self.evaluate_contains(expr, request)
561 } else {
562 tracing::warn!("Unknown custom matcher expression format: {}", expr);
564 false
565 }
566 }
567
568 fn evaluate_equality(&self, expr: &str, request: &ProtocolRequest) -> bool {
570 let parts: Vec<&str> = expr.split("==").map(|s| s.trim()).collect();
571 if parts.len() != 2 {
572 return false;
573 }
574
575 let field = parts[0];
576 let expected_value = parts[1].trim_matches('"');
577
578 match field {
579 "operation" => request.operation == expected_value,
580 "path" => request.path == expected_value,
581 "topic" => request.topic.as_ref().unwrap_or(&String::new()) == expected_value,
582 "routing_key" => {
583 request.routing_key.as_ref().unwrap_or(&String::new()) == expected_value
584 }
585 _ if field.starts_with("headers.") => {
586 let header_name = &field[8..]; request.metadata.get(header_name).is_some_and(|v| v == expected_value)
588 }
589 _ => {
590 tracing::warn!("Unknown field in equality expression: {}", field);
591 false
592 }
593 }
594 }
595
596 fn evaluate_regex_match(&self, expr: &str, request: &ProtocolRequest) -> bool {
598 let parts: Vec<&str> = expr.split("=~").map(|s| s.trim()).collect();
599 if parts.len() != 2 {
600 return false;
601 }
602
603 let field = parts[0];
604 let pattern = parts[1].trim_matches('"');
605
606 let value: String = match field {
607 "operation" => request.operation.clone(),
608 "path" => request.path.clone(),
609 "topic" => request.topic.clone().unwrap_or_default(),
610 "routing_key" => request.routing_key.clone().unwrap_or_default(),
611 _ if field.starts_with("headers.") => {
612 let header_name = &field[8..]; request.metadata.get(header_name).cloned().unwrap_or_default()
614 }
615 _ => {
616 tracing::warn!("Unknown field in regex expression: {}", field);
617 return false;
618 }
619 };
620
621 use regex::Regex;
622 match Regex::new(pattern) {
623 Ok(re) => re.is_match(&value),
624 Err(e) => {
625 tracing::warn!("Invalid regex pattern '{}': {}", pattern, e);
626 false
627 }
628 }
629 }
630
631 fn evaluate_contains(&self, expr: &str, request: &ProtocolRequest) -> bool {
633 let parts: Vec<&str> = expr.split("contains").map(|s| s.trim()).collect();
634 if parts.len() != 2 {
635 return false;
636 }
637
638 let field = parts[0];
639 let substring = parts[1].trim_matches('"');
640
641 let value: String = match field {
642 "body" => {
643 if let Some(body) = &request.body {
644 String::from_utf8_lossy(body).to_string()
645 } else {
646 return false;
647 }
648 }
649 _ if field.starts_with("headers.") => {
650 let header_name = &field[8..]; request.metadata.get(header_name).cloned().unwrap_or_default()
652 }
653 _ => {
654 tracing::warn!("Unsupported field for contains expression: {}", field);
655 return false;
656 }
657 };
658
659 value.contains(substring)
660 }
661
662 pub fn to_protocol_response(&self) -> Result<ProtocolResponse> {
664 let status = match &self.response.status {
665 FixtureStatus::Http(code) => ResponseStatus::HttpStatus(*code),
666 FixtureStatus::Grpc(code) => ResponseStatus::GrpcStatus(*code),
667 FixtureStatus::Generic(success) => ResponseStatus::GraphQLStatus(*success), FixtureStatus::Custom { code, .. } => ResponseStatus::GrpcStatus(*code), };
670
671 let body = match &self.response.body {
672 Some(serde_json::Value::String(s)) => s.clone().into_bytes(),
673 Some(value) => serde_json::to_string(value)?.into_bytes(),
674 None => Vec::new(),
675 };
676
677 let content_type = self
678 .response
679 .content_type
680 .clone()
681 .unwrap_or_else(|| "application/json".to_string());
682
683 Ok(ProtocolResponse {
684 status,
685 metadata: self.response.headers.clone(),
686 body,
687 content_type,
688 })
689 }
690}
691
692pub struct MiddlewareChain {
694 middleware: Vec<Arc<dyn ProtocolMiddleware>>,
695}
696
697impl MiddlewareChain {
698 pub fn new() -> Self {
700 Self {
701 middleware: Vec::new(),
702 }
703 }
704
705 pub fn with_middleware(mut self, middleware: Arc<dyn ProtocolMiddleware>) -> Self {
707 self.middleware.push(middleware);
708 self
709 }
710
711 pub async fn process_request(&self, request: &mut ProtocolRequest) -> Result<()> {
713 for middleware in &self.middleware {
714 if middleware.supports_protocol(request.protocol) {
715 middleware.process_request(request).await?;
716 }
717 }
718 Ok(())
719 }
720
721 pub async fn process_response(
723 &self,
724 request: &ProtocolRequest,
725 response: &mut ProtocolResponse,
726 ) -> Result<()> {
727 for middleware in self.middleware.iter().rev() {
728 if middleware.supports_protocol(request.protocol) {
729 middleware.process_response(request, response).await?;
730 }
731 }
732 Ok(())
733 }
734}
735
736impl Default for MiddlewareChain {
737 fn default() -> Self {
738 Self::new()
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745
746 #[test]
747 fn test_protocol_display() {
748 assert_eq!(Protocol::Http.to_string(), "HTTP");
749 assert_eq!(Protocol::GraphQL.to_string(), "GraphQL");
750 assert_eq!(Protocol::Grpc.to_string(), "gRPC");
751 assert_eq!(Protocol::WebSocket.to_string(), "WebSocket");
752 assert_eq!(Protocol::Smtp.to_string(), "SMTP");
753 assert_eq!(Protocol::Mqtt.to_string(), "MQTT");
754 assert_eq!(Protocol::Ftp.to_string(), "FTP");
755 assert_eq!(Protocol::Kafka.to_string(), "Kafka");
756 assert_eq!(Protocol::RabbitMq.to_string(), "RabbitMQ");
757 assert_eq!(Protocol::Amqp.to_string(), "AMQP");
758 }
759
760 #[test]
761 fn test_response_status_is_success() {
762 assert!(ResponseStatus::HttpStatus(200).is_success());
763 assert!(ResponseStatus::HttpStatus(204).is_success());
764 assert!(!ResponseStatus::HttpStatus(404).is_success());
765 assert!(!ResponseStatus::HttpStatus(500).is_success());
766
767 assert!(ResponseStatus::GrpcStatus(0).is_success());
768 assert!(!ResponseStatus::GrpcStatus(2).is_success());
769
770 assert!(ResponseStatus::GraphQLStatus(true).is_success());
771 assert!(!ResponseStatus::GraphQLStatus(false).is_success());
772 }
773
774 #[test]
775 fn test_response_status_as_code() {
776 assert_eq!(ResponseStatus::HttpStatus(200).as_code(), Some(200));
777 assert_eq!(ResponseStatus::GrpcStatus(0).as_code(), Some(0));
778 assert_eq!(ResponseStatus::GraphQLStatus(true).as_code(), None);
779 }
780
781 #[test]
782 fn test_validation_result_success() {
783 let result = ValidationResult::success();
784 assert!(result.valid);
785 assert_eq!(result.errors.len(), 0);
786 assert_eq!(result.warnings.len(), 0);
787 }
788
789 #[test]
790 fn test_validation_result_failure() {
791 let errors = vec![ValidationError {
792 message: "Invalid field".to_string(),
793 path: Some("body.field".to_string()),
794 code: Some("INVALID_FIELD".to_string()),
795 }];
796 let result = ValidationResult::failure(errors);
797 assert!(!result.valid);
798 assert_eq!(result.errors.len(), 1);
799 }
800
801 #[test]
802 fn test_validation_result_with_warning() {
803 let result = ValidationResult::success().with_warning("Deprecated field used".to_string());
804 assert!(result.valid);
805 assert_eq!(result.warnings.len(), 1);
806 }
807
808 #[test]
809 fn test_middleware_chain_creation() {
810 let chain = MiddlewareChain::new();
811 assert_eq!(chain.middleware.len(), 0);
812 }
813
814 #[test]
815 fn test_protocol_request_creation() {
816 let request = ProtocolRequest {
817 protocol: Protocol::Http,
818 operation: "GET".to_string(),
819 path: "/users".to_string(),
820 client_ip: Some("127.0.0.1".to_string()),
821 ..Default::default()
822 };
823 assert_eq!(request.protocol, Protocol::Http);
824 assert_eq!(request.pattern, MessagePattern::RequestResponse);
825 assert_eq!(request.operation, "GET");
826 assert_eq!(request.path, "/users");
827 }
828
829 #[test]
830 fn test_protocol_response_creation() {
831 let response = ProtocolResponse {
832 status: ResponseStatus::HttpStatus(200),
833 metadata: HashMap::new(),
834 body: b"{}".to_vec(),
835 content_type: "application/json".to_string(),
836 };
837 assert!(response.status.is_success());
838 assert_eq!(response.content_type, "application/json");
839 }
840
841 #[test]
842 fn test_unified_fixture_matching() {
843 let fixture = UnifiedFixture {
844 id: "test-fixture".to_string(),
845 name: "Test Fixture".to_string(),
846 description: "A test fixture".to_string(),
847 protocol: Protocol::Http,
848 request: FixtureRequest {
849 pattern: Some(MessagePattern::RequestResponse),
850 operation: Some("GET".to_string()),
851 path: Some("/api/users".to_string()),
852 topic: None,
853 routing_key: None,
854 partition: None,
855 qos: None,
856 headers: HashMap::new(),
857 body_pattern: None,
858 custom_matcher: None,
859 },
860 response: FixtureResponse {
861 status: FixtureStatus::Http(200),
862 headers: HashMap::new(),
863 body: Some(serde_json::json!({"users": ["john", "jane"]})),
864 content_type: Some("application/json".to_string()),
865 delay_ms: 0,
866 template_vars: HashMap::new(),
867 },
868 metadata: HashMap::new(),
869 enabled: true,
870 priority: 0,
871 tags: vec![],
872 };
873
874 let matching_request = ProtocolRequest {
875 protocol: Protocol::Http,
876 pattern: MessagePattern::RequestResponse,
877 operation: "GET".to_string(),
878 path: "/api/users".to_string(),
879 topic: None,
880 routing_key: None,
881 partition: None,
882 qos: None,
883 metadata: HashMap::new(),
884 body: None,
885 client_ip: None,
886 };
887
888 let non_matching_request = ProtocolRequest {
889 protocol: Protocol::Http,
890 pattern: MessagePattern::RequestResponse,
891 operation: "POST".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 assert!(fixture.matches(&matching_request));
903 assert!(!fixture.matches(&non_matching_request));
904 }
905
906 #[test]
907 fn test_fixture_to_protocol_response() {
908 let fixture = UnifiedFixture {
909 id: "test".to_string(),
910 name: "Test".to_string(),
911 description: "".to_string(),
912 protocol: Protocol::Http,
913 request: FixtureRequest {
914 pattern: None,
915 operation: None,
916 path: None,
917 topic: None,
918 routing_key: None,
919 partition: None,
920 qos: None,
921 headers: HashMap::new(),
922 body_pattern: None,
923 custom_matcher: None,
924 },
925 response: FixtureResponse {
926 status: FixtureStatus::Http(200),
927 headers: {
928 let mut h = HashMap::new();
929 h.insert("content-type".to_string(), "application/json".to_string());
930 h
931 },
932 body: Some(serde_json::json!({"message": "ok"})),
933 content_type: Some("application/json".to_string()),
934 delay_ms: 0,
935 template_vars: HashMap::new(),
936 },
937 metadata: HashMap::new(),
938 enabled: true,
939 priority: 0,
940 tags: vec![],
941 };
942
943 let response = fixture.to_protocol_response().unwrap();
944 assert!(response.status.is_success());
945 assert_eq!(response.content_type, "application/json");
946 assert_eq!(response.metadata.get("content-type"), Some(&"application/json".to_string()));
947 }
948
949 #[test]
950 fn test_fixture_status_serialization() {
951 let status = FixtureStatus::Http(404);
953 let serialized = serde_json::to_string(&status).unwrap();
954 assert_eq!(serialized, "404");
955
956 let status = FixtureStatus::Grpc(5);
958 let serialized = serde_json::to_string(&status).unwrap();
959 assert_eq!(serialized, "5");
960
961 let status = FixtureStatus::Generic(true);
963 let serialized = serde_json::to_string(&status).unwrap();
964 assert_eq!(serialized, "true");
965
966 let status = FixtureStatus::Custom {
968 code: 500,
969 message: "Internal Error".to_string(),
970 };
971 let serialized = serde_json::to_string(&status).unwrap();
972 let expected: serde_json::Value =
973 serde_json::json!({"code": 500, "message": "Internal Error"});
974 assert_eq!(serde_json::from_str::<serde_json::Value>(&serialized).unwrap(), expected);
975 }
976
977 #[test]
978 fn test_fixture_pattern_matching() {
979 let fixture = UnifiedFixture {
980 id: "test".to_string(),
981 name: "Test".to_string(),
982 description: "".to_string(),
983 protocol: Protocol::Http,
984 request: FixtureRequest {
985 pattern: Some(MessagePattern::RequestResponse),
986 operation: Some("GET".to_string()),
987 path: Some("/api/.*".to_string()),
988 topic: None,
989 routing_key: None,
990 partition: None,
991 qos: None,
992 headers: HashMap::new(),
993 body_pattern: None,
994 custom_matcher: None,
995 },
996 response: FixtureResponse {
997 status: FixtureStatus::Http(200),
998 headers: HashMap::new(),
999 body: None,
1000 content_type: None,
1001 delay_ms: 0,
1002 template_vars: HashMap::new(),
1003 },
1004 metadata: HashMap::new(),
1005 enabled: true,
1006 priority: 0,
1007 tags: vec![],
1008 };
1009
1010 let request = ProtocolRequest {
1012 protocol: Protocol::Http,
1013 pattern: MessagePattern::RequestResponse,
1014 operation: "GET".to_string(),
1015 path: "/api/users".to_string(),
1016 topic: None,
1017 routing_key: None,
1018 partition: None,
1019 qos: None,
1020 metadata: HashMap::new(),
1021 body: None,
1022 client_ip: None,
1023 };
1024 assert!(fixture.matches(&request));
1025
1026 let grpc_request = ProtocolRequest {
1028 protocol: Protocol::Grpc,
1029 pattern: MessagePattern::RequestResponse,
1030 operation: "GET".to_string(),
1031 path: "/api/users".to_string(),
1032 topic: None,
1033 routing_key: None,
1034 partition: None,
1035 qos: None,
1036 metadata: HashMap::new(),
1037 body: None,
1038 client_ip: None,
1039 };
1040 assert!(!fixture.matches(&grpc_request));
1041
1042 let post_request = ProtocolRequest {
1044 protocol: Protocol::Http,
1045 pattern: MessagePattern::RequestResponse,
1046 operation: "POST".to_string(),
1047 path: "/api/users".to_string(),
1048 topic: None,
1049 routing_key: None,
1050 partition: None,
1051 qos: None,
1052 metadata: HashMap::new(),
1053 body: None,
1054 client_ip: None,
1055 };
1056 assert!(!fixture.matches(&post_request));
1057 }
1058
1059 #[test]
1060 fn test_custom_matcher_equality() {
1061 let fixture = UnifiedFixture {
1062 id: "test".to_string(),
1063 name: "Test".to_string(),
1064 description: "".to_string(),
1065 protocol: Protocol::Http,
1066 request: FixtureRequest {
1067 pattern: Some(MessagePattern::RequestResponse),
1068 operation: Some("GET".to_string()),
1069 path: Some("/api/users".to_string()),
1070 topic: None,
1071 routing_key: None,
1072 partition: None,
1073 qos: None,
1074 headers: HashMap::new(),
1075 body_pattern: None,
1076 custom_matcher: Some("operation == \"GET\"".to_string()),
1077 },
1078 response: FixtureResponse {
1079 status: FixtureStatus::Http(200),
1080 headers: HashMap::new(),
1081 body: None,
1082 content_type: None,
1083 delay_ms: 0,
1084 template_vars: HashMap::new(),
1085 },
1086 metadata: HashMap::new(),
1087 enabled: true,
1088 priority: 0,
1089 tags: vec![],
1090 };
1091
1092 let request = ProtocolRequest {
1094 protocol: Protocol::Http,
1095 pattern: MessagePattern::RequestResponse,
1096 operation: "GET".to_string(),
1097 path: "/api/users".to_string(),
1098 topic: None,
1099 routing_key: None,
1100 partition: None,
1101 qos: None,
1102 metadata: HashMap::new(),
1103 body: None,
1104 client_ip: None,
1105 };
1106 assert!(fixture.matches(&request));
1107
1108 let post_request = ProtocolRequest {
1110 protocol: Protocol::Http,
1111 pattern: MessagePattern::RequestResponse,
1112 operation: "POST".to_string(),
1113 path: "/api/users".to_string(),
1114 topic: None,
1115 routing_key: None,
1116 partition: None,
1117 qos: None,
1118 metadata: HashMap::new(),
1119 body: None,
1120 client_ip: None,
1121 };
1122 assert!(!fixture.matches(&post_request));
1123 }
1124
1125 #[test]
1126 fn test_custom_matcher_regex() {
1127 let fixture = UnifiedFixture {
1128 id: "test".to_string(),
1129 name: "Test".to_string(),
1130 description: "".to_string(),
1131 protocol: Protocol::Http,
1132 request: FixtureRequest {
1133 pattern: Some(MessagePattern::RequestResponse),
1134 operation: Some("GET".to_string()),
1135 path: Some("/api/.*".to_string()),
1136 topic: None,
1137 routing_key: None,
1138 partition: None,
1139 qos: None,
1140 headers: HashMap::new(),
1141 body_pattern: None,
1142 custom_matcher: Some("path =~ \"/api/.*\"".to_string()),
1143 },
1144 response: FixtureResponse {
1145 status: FixtureStatus::Http(200),
1146 headers: HashMap::new(),
1147 body: None,
1148 content_type: None,
1149 delay_ms: 0,
1150 template_vars: HashMap::new(),
1151 },
1152 metadata: HashMap::new(),
1153 enabled: true,
1154 priority: 0,
1155 tags: vec![],
1156 };
1157
1158 let request = ProtocolRequest {
1160 protocol: Protocol::Http,
1161 pattern: MessagePattern::RequestResponse,
1162 operation: "GET".to_string(),
1163 path: "/api/users".to_string(),
1164 topic: None,
1165 routing_key: None,
1166 partition: None,
1167 qos: None,
1168 metadata: HashMap::new(),
1169 body: None,
1170 client_ip: None,
1171 };
1172 assert!(fixture.matches(&request));
1173
1174 let other_request = ProtocolRequest {
1176 protocol: Protocol::Http,
1177 pattern: MessagePattern::RequestResponse,
1178 operation: "GET".to_string(),
1179 path: "/other/path".to_string(),
1180 topic: None,
1181 routing_key: None,
1182 partition: None,
1183 qos: None,
1184 metadata: HashMap::new(),
1185 body: None,
1186 client_ip: None,
1187 };
1188 assert!(!fixture.matches(&other_request));
1189 }
1190
1191 #[test]
1192 fn test_custom_matcher_contains() {
1193 let fixture = UnifiedFixture {
1194 id: "test".to_string(),
1195 name: "Test".to_string(),
1196 description: "".to_string(),
1197 protocol: Protocol::Http,
1198 request: FixtureRequest {
1199 pattern: Some(MessagePattern::RequestResponse),
1200 operation: Some("POST".to_string()),
1201 path: Some("/api/users".to_string()),
1202 topic: None,
1203 routing_key: None,
1204 partition: None,
1205 qos: None,
1206 headers: HashMap::new(),
1207 body_pattern: None,
1208 custom_matcher: Some("body contains \"test\"".to_string()),
1209 },
1210 response: FixtureResponse {
1211 status: FixtureStatus::Http(200),
1212 headers: HashMap::new(),
1213 body: None,
1214 content_type: None,
1215 delay_ms: 0,
1216 template_vars: HashMap::new(),
1217 },
1218 metadata: HashMap::new(),
1219 enabled: true,
1220 priority: 0,
1221 tags: vec![],
1222 };
1223
1224 let request = ProtocolRequest {
1226 protocol: Protocol::Http,
1227 pattern: MessagePattern::RequestResponse,
1228 operation: "POST".to_string(),
1229 path: "/api/users".to_string(),
1230 topic: None,
1231 routing_key: None,
1232 partition: None,
1233 qos: None,
1234 metadata: HashMap::new(),
1235 body: Some(b"{\"name\": \"test user\"}".to_vec()),
1236 client_ip: None,
1237 };
1238 assert!(fixture.matches(&request));
1239
1240 let no_match_request = ProtocolRequest {
1242 protocol: Protocol::Http,
1243 pattern: MessagePattern::RequestResponse,
1244 operation: "POST".to_string(),
1245 path: "/api/users".to_string(),
1246 topic: None,
1247 routing_key: None,
1248 partition: None,
1249 qos: None,
1250 metadata: HashMap::new(),
1251 body: Some(b"{\"name\": \"other user\"}".to_vec()),
1252 client_ip: None,
1253 };
1254 assert!(!fixture.matches(&no_match_request));
1255 }
1256
1257 #[test]
1258 fn test_custom_matcher_header() {
1259 let fixture = UnifiedFixture {
1260 id: "test".to_string(),
1261 name: "Test".to_string(),
1262 description: "".to_string(),
1263 protocol: Protocol::Http,
1264 request: FixtureRequest {
1265 pattern: Some(MessagePattern::RequestResponse),
1266 operation: Some("GET".to_string()),
1267 path: Some("/api/data".to_string()),
1268 topic: None,
1269 routing_key: None,
1270 partition: None,
1271 qos: None,
1272 headers: HashMap::new(),
1273 body_pattern: None,
1274 custom_matcher: Some("headers.content-type == \"application/json\"".to_string()),
1275 },
1276 response: FixtureResponse {
1277 status: FixtureStatus::Http(200),
1278 headers: HashMap::new(),
1279 body: None,
1280 content_type: None,
1281 delay_ms: 0,
1282 template_vars: HashMap::new(),
1283 },
1284 metadata: HashMap::new(),
1285 enabled: true,
1286 priority: 0,
1287 tags: vec![],
1288 };
1289
1290 let mut headers = HashMap::new();
1292 headers.insert("content-type".to_string(), "application/json".to_string());
1293 let request = ProtocolRequest {
1294 protocol: Protocol::Http,
1295 pattern: MessagePattern::RequestResponse,
1296 operation: "GET".to_string(),
1297 path: "/api/data".to_string(),
1298 topic: None,
1299 routing_key: None,
1300 partition: None,
1301 qos: None,
1302 metadata: headers,
1303 body: None,
1304 client_ip: None,
1305 };
1306 assert!(fixture.matches(&request));
1307
1308 let mut wrong_headers = HashMap::new();
1310 wrong_headers.insert("content-type".to_string(), "text/plain".to_string());
1311 let wrong_request = ProtocolRequest {
1312 protocol: Protocol::Http,
1313 pattern: MessagePattern::RequestResponse,
1314 operation: "GET".to_string(),
1315 path: "/api/data".to_string(),
1316 topic: None,
1317 routing_key: None,
1318 partition: None,
1319 qos: None,
1320 metadata: wrong_headers,
1321 body: None,
1322 client_ip: None,
1323 };
1324 assert!(!fixture.matches(&wrong_request));
1325 }
1326}