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