mockforge_core/protocol_abstraction/
mod.rs

1//! Protocol-agnostic abstractions for unified mocking across HTTP, GraphQL, gRPC, and WebSocket
2//!
3//! This module provides traits and types that abstract common patterns across different
4//! protocols, enabling code reuse for spec-driven mocking, middleware, and request matching.
5
6pub 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
18// Re-export middleware types
19pub 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/// Protocol type enumeration for multi-protocol support
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
30#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
31pub enum Protocol {
32    /// HTTP/REST protocol for RESTful APIs
33    Http,
34    /// GraphQL protocol for GraphQL APIs
35    GraphQL,
36    /// gRPC protocol for gRPC services
37    Grpc,
38    /// WebSocket protocol for real-time bidirectional communication
39    WebSocket,
40    /// SMTP/Email protocol for email communication
41    Smtp,
42    /// MQTT protocol for IoT messaging and pub/sub
43    Mqtt,
44    /// FTP protocol for file transfer operations
45    Ftp,
46    /// Kafka protocol for distributed event streaming
47    Kafka,
48    /// RabbitMQ/AMQP protocol for message queuing
49    RabbitMq,
50    /// AMQP protocol for advanced message queuing scenarios
51    Amqp,
52    /// TCP protocol for raw TCP connections
53    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/// Message pattern enumeration for different communication patterns
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
76pub enum MessagePattern {
77    /// Request-Response pattern (HTTP, gRPC unary)
78    RequestResponse,
79    /// One-way/fire-and-forget pattern (MQTT publish, email)
80    OneWay,
81    /// Publish-Subscribe pattern (Kafka, RabbitMQ, MQTT)
82    PubSub,
83    /// Streaming pattern (gRPC streaming, WebSocket)
84    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/// A protocol-agnostic request representation
99#[derive(Debug, Clone)]
100pub struct ProtocolRequest {
101    /// The protocol this request uses
102    pub protocol: Protocol,
103    /// Message pattern for this request
104    pub pattern: MessagePattern,
105    /// Method or operation (e.g., "GET", "Query.users", "greeter.SayHello")
106    pub operation: String,
107    /// Path, query name, or service/method name
108    pub path: String,
109    /// Topic for pub/sub protocols (MQTT, Kafka)
110    pub topic: Option<String>,
111    /// Routing key for message queuing protocols (AMQP, RabbitMQ)
112    pub routing_key: Option<String>,
113    /// Partition for partitioned protocols (Kafka)
114    pub partition: Option<i32>,
115    /// Quality of Service level (MQTT: 0, 1, 2)
116    pub qos: Option<u8>,
117    /// Request metadata (headers, metadata, etc.)
118    pub metadata: HashMap<String, String>,
119    /// Request body/payload as bytes
120    pub body: Option<Vec<u8>>,
121    /// Client IP address if available
122    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/// A protocol-agnostic response representation
144#[derive(Debug, Clone)]
145pub struct ProtocolResponse {
146    /// Status code or success indicator (HTTP: 200, gRPC: OK, GraphQL: no errors)
147    pub status: ResponseStatus,
148    /// Response metadata (headers, metadata, etc.)
149    pub metadata: HashMap<String, String>,
150    /// Response body/payload
151    pub body: Vec<u8>,
152    /// Content type or serialization format
153    pub content_type: String,
154}
155
156/// Response status abstraction across protocols
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum ResponseStatus {
159    /// HTTP status code
160    HttpStatus(u16),
161    /// gRPC status code
162    GrpcStatus(i32),
163    /// GraphQL success (true) or error (false)
164    GraphQLStatus(bool),
165    /// WebSocket status
166    WebSocketStatus(bool),
167    /// SMTP status code (2xx = success, 4xx/5xx = error)
168    SmtpStatus(u16),
169    /// MQTT status (true = success, false = error)
170    MqttStatus(bool),
171    /// Kafka status code (0 = success, non-zero = error)
172    KafkaStatus(i16),
173    /// AMQP/RabbitMQ status code
174    AmqpStatus(u16),
175    /// FTP status code
176    FtpStatus(u16),
177    /// TCP status (true = success, false = error/close)
178    TcpStatus(bool),
179}
180
181impl ResponseStatus {
182    /// Check if the response is successful
183    pub fn is_success(&self) -> bool {
184        match self {
185            ResponseStatus::HttpStatus(code) => (200..300).contains(code),
186            ResponseStatus::GrpcStatus(code) => *code == 0, // gRPC OK = 0
187            ResponseStatus::GraphQLStatus(success) => *success,
188            ResponseStatus::WebSocketStatus(success) => *success,
189            ResponseStatus::SmtpStatus(code) => (200..300).contains(code), // 2xx codes are success
190            ResponseStatus::MqttStatus(success) => *success,
191            ResponseStatus::KafkaStatus(code) => *code == 0, // Kafka OK = 0
192            ResponseStatus::AmqpStatus(code) => (200..300).contains(code), // AMQP success codes
193            ResponseStatus::FtpStatus(code) => (200..300).contains(code), // FTP success codes
194            ResponseStatus::TcpStatus(success) => *success,  // TCP success flag
195        }
196    }
197
198    /// Get numeric representation if applicable
199    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, // TCP uses boolean status
208            ResponseStatus::GraphQLStatus(_)
209            | ResponseStatus::WebSocketStatus(_)
210            | ResponseStatus::MqttStatus(_) => None,
211        }
212    }
213}
214
215/// Trait for spec-driven mocking registries (OpenAPI, GraphQL schema, Proto files)
216pub trait SpecRegistry: Send + Sync {
217    /// Get the protocol this registry handles
218    fn protocol(&self) -> Protocol;
219
220    /// Get all available operations/routes in this spec
221    fn operations(&self) -> Vec<SpecOperation>;
222
223    /// Find an operation by path/name
224    fn find_operation(&self, operation: &str, path: &str) -> Option<SpecOperation>;
225
226    /// Validate a request against the spec
227    fn validate_request(&self, request: &ProtocolRequest) -> Result<ValidationResult>;
228
229    /// Generate a mock response for a request
230    fn generate_mock_response(&self, request: &ProtocolRequest) -> Result<ProtocolResponse>;
231}
232
233/// Represents a single operation in a spec (endpoint, query, RPC method)
234#[derive(Debug, Clone)]
235pub struct SpecOperation {
236    /// Operation name or identifier
237    pub name: String,
238    /// Path or fully qualified name
239    pub path: String,
240    /// Operation type (GET, POST, Query, Mutation, RPC)
241    pub operation_type: String,
242    /// Input schema/type information
243    pub input_schema: Option<String>,
244    /// Output schema/type information
245    pub output_schema: Option<String>,
246    /// Metadata from spec
247    pub metadata: HashMap<String, String>,
248}
249
250/// Result of request validation
251#[derive(Debug, Clone)]
252pub struct ValidationResult {
253    /// Whether validation passed
254    pub valid: bool,
255    /// Validation errors if any
256    pub errors: Vec<ValidationError>,
257    /// Validation warnings
258    pub warnings: Vec<String>,
259}
260
261/// A validation error
262#[derive(Debug, Clone)]
263pub struct ValidationError {
264    /// Error message
265    pub message: String,
266    /// Path to the error (e.g., "body.user.email")
267    pub path: Option<String>,
268    /// Error code
269    pub code: Option<String>,
270}
271
272impl ValidationResult {
273    /// Create a successful validation result
274    pub fn success() -> Self {
275        Self {
276            valid: true,
277            errors: Vec::new(),
278            warnings: Vec::new(),
279        }
280    }
281
282    /// Create a failed validation result with errors
283    pub fn failure(errors: Vec<ValidationError>) -> Self {
284        Self {
285            valid: false,
286            errors,
287            warnings: Vec::new(),
288        }
289    }
290
291    /// Add a warning to the result
292    pub fn with_warning(mut self, warning: String) -> Self {
293        self.warnings.push(warning);
294        self
295    }
296}
297
298/// Trait for protocol-agnostic middleware
299#[async_trait::async_trait]
300pub trait ProtocolMiddleware: Send + Sync {
301    /// Get the name of this middleware
302    fn name(&self) -> &str;
303
304    /// Process a request before it reaches the handler
305    async fn process_request(&self, request: &mut ProtocolRequest) -> Result<()>;
306
307    /// Process a response before it's returned to the client
308    async fn process_response(
309        &self,
310        request: &ProtocolRequest,
311        response: &mut ProtocolResponse,
312    ) -> Result<()>;
313
314    /// Check if this middleware should run for a given protocol
315    fn supports_protocol(&self, protocol: Protocol) -> bool;
316}
317
318/// Trait for request matching across protocols
319pub trait RequestMatcher: Send + Sync {
320    /// Match a request and return a score (higher = better match)
321    fn match_score(&self, request: &ProtocolRequest) -> f64;
322
323    /// Get the protocol this matcher handles
324    fn protocol(&self) -> Protocol;
325}
326
327/// Unified fixture format supporting all protocols
328#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct UnifiedFixture {
330    /// Unique identifier for this fixture
331    pub id: String,
332
333    /// Human-readable name
334    pub name: String,
335
336    /// Description of what this fixture does
337    #[serde(default)]
338    pub description: String,
339
340    /// Protocol this fixture applies to
341    pub protocol: Protocol,
342
343    /// Request matching criteria
344    pub request: FixtureRequest,
345
346    /// Response configuration
347    pub response: FixtureResponse,
348
349    /// Additional metadata
350    #[serde(default)]
351    pub metadata: HashMap<String, serde_json::Value>,
352
353    /// Whether this fixture is enabled
354    #[serde(default = "default_true")]
355    pub enabled: bool,
356
357    /// Priority for matching (higher = matched first)
358    #[serde(default)]
359    pub priority: i32,
360
361    /// Tags for organization
362    #[serde(default)]
363    pub tags: Vec<String>,
364}
365
366/// Request matching criteria for fixtures
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct FixtureRequest {
369    /// Message pattern to match
370    #[serde(default)]
371    pub pattern: Option<MessagePattern>,
372
373    /// Operation/method to match (exact or regex)
374    pub operation: Option<String>,
375
376    /// Path/route to match (exact or regex)
377    pub path: Option<String>,
378
379    /// Topic to match (for pub/sub protocols)
380    pub topic: Option<String>,
381
382    /// Routing key to match (for message queuing)
383    pub routing_key: Option<String>,
384
385    /// Partition to match
386    pub partition: Option<i32>,
387
388    /// QoS level to match
389    pub qos: Option<u8>,
390
391    /// Headers/metadata to match (key-value pairs)
392    #[serde(default)]
393    pub headers: HashMap<String, String>,
394
395    /// Request body pattern (regex for text, or exact match)
396    pub body_pattern: Option<String>,
397
398    /// Custom matching logic (script or expression)
399    pub custom_matcher: Option<String>,
400}
401
402/// Response configuration for fixtures
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct FixtureResponse {
405    /// Response status
406    pub status: FixtureStatus,
407
408    /// Response headers
409    #[serde(default)]
410    pub headers: HashMap<String, String>,
411
412    /// Response body (can be string, JSON, or base64-encoded binary)
413    pub body: Option<serde_json::Value>,
414
415    /// Content type
416    pub content_type: Option<String>,
417
418    /// Response delay in milliseconds
419    #[serde(default)]
420    pub delay_ms: u64,
421
422    /// Template variables for dynamic responses
423    #[serde(default)]
424    pub template_vars: HashMap<String, serde_json::Value>,
425}
426
427/// Status representation for fixtures
428#[derive(Debug, Clone, Serialize, Deserialize)]
429#[serde(untagged)]
430pub enum FixtureStatus {
431    /// HTTP status code
432    Http(u16),
433    /// gRPC status code
434    Grpc(i32),
435    /// Generic success/failure
436    Generic(bool),
437    /// Custom status with code and message
438    Custom {
439        /// Custom status code
440        code: i32,
441        /// Custom status message
442        message: String,
443    },
444}
445
446fn default_true() -> bool {
447    true
448}
449
450impl UnifiedFixture {
451    /// Check if this fixture matches the given protocol request
452    pub fn matches(&self, request: &ProtocolRequest) -> bool {
453        // Check protocol
454        if request.protocol != self.protocol {
455            return false;
456        }
457
458        // Check pattern
459        if let Some(pattern) = &self.request.pattern {
460            if request.pattern != *pattern {
461                return false;
462            }
463        }
464
465        // Check operation
466        if let Some(operation) = &self.request.operation {
467            if !self.matches_pattern(&request.operation, operation) {
468                return false;
469            }
470        }
471
472        // Check path
473        if let Some(path) = &self.request.path {
474            if !self.matches_pattern(&request.path, path) {
475                return false;
476            }
477        }
478
479        // Check topic
480        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        // Check routing key
487        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        // Check partition
497        if let Some(partition) = self.request.partition {
498            if request.partition != Some(partition) {
499                return false;
500            }
501        }
502
503        // Check QoS
504        if let Some(qos) = self.request.qos {
505            if request.qos != Some(qos) {
506                return false;
507            }
508        }
509
510        // Check headers
511        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        // Check body pattern
522        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        // Check custom matcher logic
534        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    /// Helper method to match patterns (supports regex and exact match)
544    fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
545        use regex::Regex;
546
547        // Try regex first
548        if let Ok(re) = Regex::new(pattern) {
549            re.is_match(value)
550        } else {
551            // Fall back to exact match
552            value == pattern
553        }
554    }
555
556    /// Evaluate custom matcher expression
557    fn evaluate_custom_matcher(&self, expression: &str, request: &ProtocolRequest) -> bool {
558        // Simple expression evaluator for custom matching logic
559        // Supports basic conditions like:
560        // - operation == "GET"
561        // - path =~ "/api/.*"
562        // - headers.content-type == "application/json"
563        // - body contains "test"
564
565        let expr = expression.trim();
566
567        // Handle different types of expressions
568        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            // Unknown expression format, log warning and return false
576            tracing::warn!("Unknown custom matcher expression format: {}", expr);
577            false
578        }
579    }
580
581    /// Evaluate equality expressions (field == "value")
582    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..]; // Remove "headers." prefix
600                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    /// Evaluate regex match expressions (field =~ "pattern")
610    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..]; // Remove "headers." prefix
626                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    /// Evaluate contains expressions (field contains "substring")
645    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..]; // Remove "headers." prefix
664                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    /// Convert fixture response to ProtocolResponse
676    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), // Using GraphQL as generic
681            FixtureStatus::Custom { code, .. } => ResponseStatus::GrpcStatus(*code), // Using gRPC as custom
682        };
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
705/// Middleware chain for composing and executing multiple middleware in sequence
706pub struct MiddlewareChain {
707    /// Ordered list of middleware to execute
708    middleware: Vec<Arc<dyn ProtocolMiddleware>>,
709}
710
711impl MiddlewareChain {
712    /// Create a new middleware chain
713    pub fn new() -> Self {
714        Self {
715            middleware: Vec::new(),
716        }
717    }
718
719    /// Add middleware to the chain
720    pub fn with_middleware(mut self, middleware: Arc<dyn ProtocolMiddleware>) -> Self {
721        self.middleware.push(middleware);
722        self
723    }
724
725    /// Process a request through all middleware
726    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    /// Process a response through all middleware (in reverse order)
736    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        // Test HTTP status
967        let status = FixtureStatus::Http(404);
968        let serialized = serde_json::to_string(&status).unwrap();
969        assert_eq!(serialized, "404");
970
971        // Test gRPC status
972        let status = FixtureStatus::Grpc(5);
973        let serialized = serde_json::to_string(&status).unwrap();
974        assert_eq!(serialized, "5");
975
976        // Test generic status
977        let status = FixtureStatus::Generic(true);
978        let serialized = serde_json::to_string(&status).unwrap();
979        assert_eq!(serialized, "true");
980
981        // Test custom status
982        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        // Test matching request
1026        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        // Test non-matching protocol
1042        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        // Test non-matching operation
1058        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        // Test matching request
1108        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        // Test non-matching request
1124        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        // Test matching request
1174        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        // Test non-matching request
1190        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        // Test matching request
1240        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        // Test non-matching request
1256        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        // Test matching request
1306        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        // Test non-matching request
1324        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}