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