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)]
30pub enum Protocol {
31    /// HTTP/REST protocol for RESTful APIs
32    Http,
33    /// GraphQL protocol for GraphQL APIs
34    GraphQL,
35    /// gRPC protocol for gRPC services
36    Grpc,
37    /// WebSocket protocol for real-time bidirectional communication
38    WebSocket,
39    /// SMTP/Email protocol for email communication
40    Smtp,
41    /// MQTT protocol for IoT messaging and pub/sub
42    Mqtt,
43    /// FTP protocol for file transfer operations
44    Ftp,
45    /// Kafka protocol for distributed event streaming
46    Kafka,
47    /// RabbitMQ/AMQP protocol for message queuing
48    RabbitMq,
49    /// AMQP protocol for advanced message queuing scenarios
50    Amqp,
51    /// TCP protocol for raw TCP connections
52    Tcp,
53}
54
55impl fmt::Display for Protocol {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            Protocol::Http => write!(f, "HTTP"),
59            Protocol::GraphQL => write!(f, "GraphQL"),
60            Protocol::Grpc => write!(f, "gRPC"),
61            Protocol::WebSocket => write!(f, "WebSocket"),
62            Protocol::Smtp => write!(f, "SMTP"),
63            Protocol::Mqtt => write!(f, "MQTT"),
64            Protocol::Ftp => write!(f, "FTP"),
65            Protocol::Kafka => write!(f, "Kafka"),
66            Protocol::RabbitMq => write!(f, "RabbitMQ"),
67            Protocol::Amqp => write!(f, "AMQP"),
68            Protocol::Tcp => write!(f, "TCP"),
69        }
70    }
71}
72
73/// Message pattern enumeration for different communication patterns
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
75pub enum MessagePattern {
76    /// Request-Response pattern (HTTP, gRPC unary)
77    RequestResponse,
78    /// One-way/fire-and-forget pattern (MQTT publish, email)
79    OneWay,
80    /// Publish-Subscribe pattern (Kafka, RabbitMQ, MQTT)
81    PubSub,
82    /// Streaming pattern (gRPC streaming, WebSocket)
83    Streaming,
84}
85
86impl fmt::Display for MessagePattern {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        match self {
89            MessagePattern::RequestResponse => write!(f, "Request-Response"),
90            MessagePattern::OneWay => write!(f, "One-Way"),
91            MessagePattern::PubSub => write!(f, "Pub-Sub"),
92            MessagePattern::Streaming => write!(f, "Streaming"),
93        }
94    }
95}
96
97/// A protocol-agnostic request representation
98#[derive(Debug, Clone)]
99pub struct ProtocolRequest {
100    /// The protocol this request uses
101    pub protocol: Protocol,
102    /// Message pattern for this request
103    pub pattern: MessagePattern,
104    /// Method or operation (e.g., "GET", "Query.users", "greeter.SayHello")
105    pub operation: String,
106    /// Path, query name, or service/method name
107    pub path: String,
108    /// Topic for pub/sub protocols (MQTT, Kafka)
109    pub topic: Option<String>,
110    /// Routing key for message queuing protocols (AMQP, RabbitMQ)
111    pub routing_key: Option<String>,
112    /// Partition for partitioned protocols (Kafka)
113    pub partition: Option<i32>,
114    /// Quality of Service level (MQTT: 0, 1, 2)
115    pub qos: Option<u8>,
116    /// Request metadata (headers, metadata, etc.)
117    pub metadata: HashMap<String, String>,
118    /// Request body/payload as bytes
119    pub body: Option<Vec<u8>>,
120    /// Client IP address if available
121    pub client_ip: Option<String>,
122}
123
124impl Default for ProtocolRequest {
125    fn default() -> Self {
126        Self {
127            protocol: Protocol::Http,
128            pattern: MessagePattern::RequestResponse,
129            operation: String::new(),
130            path: String::new(),
131            topic: None,
132            routing_key: None,
133            partition: None,
134            qos: None,
135            metadata: HashMap::new(),
136            body: None,
137            client_ip: None,
138        }
139    }
140}
141
142/// A protocol-agnostic response representation
143#[derive(Debug, Clone)]
144pub struct ProtocolResponse {
145    /// Status code or success indicator (HTTP: 200, gRPC: OK, GraphQL: no errors)
146    pub status: ResponseStatus,
147    /// Response metadata (headers, metadata, etc.)
148    pub metadata: HashMap<String, String>,
149    /// Response body/payload
150    pub body: Vec<u8>,
151    /// Content type or serialization format
152    pub content_type: String,
153}
154
155/// Response status abstraction across protocols
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub enum ResponseStatus {
158    /// HTTP status code
159    HttpStatus(u16),
160    /// gRPC status code
161    GrpcStatus(i32),
162    /// GraphQL success (true) or error (false)
163    GraphQLStatus(bool),
164    /// WebSocket status
165    WebSocketStatus(bool),
166    /// SMTP status code (2xx = success, 4xx/5xx = error)
167    SmtpStatus(u16),
168    /// MQTT status (true = success, false = error)
169    MqttStatus(bool),
170    /// Kafka status code (0 = success, non-zero = error)
171    KafkaStatus(i16),
172    /// AMQP/RabbitMQ status code
173    AmqpStatus(u16),
174    /// FTP status code
175    FtpStatus(u16),
176    /// TCP status (true = success, false = error/close)
177    TcpStatus(bool),
178}
179
180impl ResponseStatus {
181    /// Check if the response is successful
182    pub fn is_success(&self) -> bool {
183        match self {
184            ResponseStatus::HttpStatus(code) => (200..300).contains(code),
185            ResponseStatus::GrpcStatus(code) => *code == 0, // gRPC OK = 0
186            ResponseStatus::GraphQLStatus(success) => *success,
187            ResponseStatus::WebSocketStatus(success) => *success,
188            ResponseStatus::SmtpStatus(code) => (200..300).contains(code), // 2xx codes are success
189            ResponseStatus::MqttStatus(success) => *success,
190            ResponseStatus::KafkaStatus(code) => *code == 0, // Kafka OK = 0
191            ResponseStatus::AmqpStatus(code) => (200..300).contains(code), // AMQP success codes
192            ResponseStatus::FtpStatus(code) => (200..300).contains(code), // FTP success codes
193            ResponseStatus::TcpStatus(success) => *success,  // TCP success flag
194        }
195    }
196
197    /// Get numeric representation if applicable
198    pub fn as_code(&self) -> Option<i32> {
199        match self {
200            ResponseStatus::HttpStatus(code) => Some(*code as i32),
201            ResponseStatus::GrpcStatus(code) => Some(*code),
202            ResponseStatus::SmtpStatus(code) => Some(*code as i32),
203            ResponseStatus::KafkaStatus(code) => Some(*code as i32),
204            ResponseStatus::AmqpStatus(code) => Some(*code as i32),
205            ResponseStatus::FtpStatus(code) => Some(*code as i32),
206            ResponseStatus::TcpStatus(_) => None, // TCP uses boolean status
207            ResponseStatus::GraphQLStatus(_)
208            | ResponseStatus::WebSocketStatus(_)
209            | ResponseStatus::MqttStatus(_) => None,
210        }
211    }
212}
213
214/// Trait for spec-driven mocking registries (OpenAPI, GraphQL schema, Proto files)
215pub trait SpecRegistry: Send + Sync {
216    /// Get the protocol this registry handles
217    fn protocol(&self) -> Protocol;
218
219    /// Get all available operations/routes in this spec
220    fn operations(&self) -> Vec<SpecOperation>;
221
222    /// Find an operation by path/name
223    fn find_operation(&self, operation: &str, path: &str) -> Option<SpecOperation>;
224
225    /// Validate a request against the spec
226    fn validate_request(&self, request: &ProtocolRequest) -> Result<ValidationResult>;
227
228    /// Generate a mock response for a request
229    fn generate_mock_response(&self, request: &ProtocolRequest) -> Result<ProtocolResponse>;
230}
231
232/// Represents a single operation in a spec (endpoint, query, RPC method)
233#[derive(Debug, Clone)]
234pub struct SpecOperation {
235    /// Operation name or identifier
236    pub name: String,
237    /// Path or fully qualified name
238    pub path: String,
239    /// Operation type (GET, POST, Query, Mutation, RPC)
240    pub operation_type: String,
241    /// Input schema/type information
242    pub input_schema: Option<String>,
243    /// Output schema/type information
244    pub output_schema: Option<String>,
245    /// Metadata from spec
246    pub metadata: HashMap<String, String>,
247}
248
249/// Result of request validation
250#[derive(Debug, Clone)]
251pub struct ValidationResult {
252    /// Whether validation passed
253    pub valid: bool,
254    /// Validation errors if any
255    pub errors: Vec<ValidationError>,
256    /// Validation warnings
257    pub warnings: Vec<String>,
258}
259
260/// A validation error
261#[derive(Debug, Clone)]
262pub struct ValidationError {
263    /// Error message
264    pub message: String,
265    /// Path to the error (e.g., "body.user.email")
266    pub path: Option<String>,
267    /// Error code
268    pub code: Option<String>,
269}
270
271impl ValidationResult {
272    /// Create a successful validation result
273    pub fn success() -> Self {
274        Self {
275            valid: true,
276            errors: Vec::new(),
277            warnings: Vec::new(),
278        }
279    }
280
281    /// Create a failed validation result with errors
282    pub fn failure(errors: Vec<ValidationError>) -> Self {
283        Self {
284            valid: false,
285            errors,
286            warnings: Vec::new(),
287        }
288    }
289
290    /// Add a warning to the result
291    pub fn with_warning(mut self, warning: String) -> Self {
292        self.warnings.push(warning);
293        self
294    }
295}
296
297/// Trait for protocol-agnostic middleware
298#[async_trait::async_trait]
299pub trait ProtocolMiddleware: Send + Sync {
300    /// Get the name of this middleware
301    fn name(&self) -> &str;
302
303    /// Process a request before it reaches the handler
304    async fn process_request(&self, request: &mut ProtocolRequest) -> Result<()>;
305
306    /// Process a response before it's returned to the client
307    async fn process_response(
308        &self,
309        request: &ProtocolRequest,
310        response: &mut ProtocolResponse,
311    ) -> Result<()>;
312
313    /// Check if this middleware should run for a given protocol
314    fn supports_protocol(&self, protocol: Protocol) -> bool;
315}
316
317/// Trait for request matching across protocols
318pub trait RequestMatcher: Send + Sync {
319    /// Match a request and return a score (higher = better match)
320    fn match_score(&self, request: &ProtocolRequest) -> f64;
321
322    /// Get the protocol this matcher handles
323    fn protocol(&self) -> Protocol;
324}
325
326/// Unified fixture format supporting all protocols
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct UnifiedFixture {
329    /// Unique identifier for this fixture
330    pub id: String,
331
332    /// Human-readable name
333    pub name: String,
334
335    /// Description of what this fixture does
336    #[serde(default)]
337    pub description: String,
338
339    /// Protocol this fixture applies to
340    pub protocol: Protocol,
341
342    /// Request matching criteria
343    pub request: FixtureRequest,
344
345    /// Response configuration
346    pub response: FixtureResponse,
347
348    /// Additional metadata
349    #[serde(default)]
350    pub metadata: HashMap<String, serde_json::Value>,
351
352    /// Whether this fixture is enabled
353    #[serde(default = "default_true")]
354    pub enabled: bool,
355
356    /// Priority for matching (higher = matched first)
357    #[serde(default)]
358    pub priority: i32,
359
360    /// Tags for organization
361    #[serde(default)]
362    pub tags: Vec<String>,
363}
364
365/// Request matching criteria for fixtures
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct FixtureRequest {
368    /// Message pattern to match
369    #[serde(default)]
370    pub pattern: Option<MessagePattern>,
371
372    /// Operation/method to match (exact or regex)
373    pub operation: Option<String>,
374
375    /// Path/route to match (exact or regex)
376    pub path: Option<String>,
377
378    /// Topic to match (for pub/sub protocols)
379    pub topic: Option<String>,
380
381    /// Routing key to match (for message queuing)
382    pub routing_key: Option<String>,
383
384    /// Partition to match
385    pub partition: Option<i32>,
386
387    /// QoS level to match
388    pub qos: Option<u8>,
389
390    /// Headers/metadata to match (key-value pairs)
391    #[serde(default)]
392    pub headers: HashMap<String, String>,
393
394    /// Request body pattern (regex for text, or exact match)
395    pub body_pattern: Option<String>,
396
397    /// Custom matching logic (script or expression)
398    pub custom_matcher: Option<String>,
399}
400
401/// Response configuration for fixtures
402#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct FixtureResponse {
404    /// Response status
405    pub status: FixtureStatus,
406
407    /// Response headers
408    #[serde(default)]
409    pub headers: HashMap<String, String>,
410
411    /// Response body (can be string, JSON, or base64-encoded binary)
412    pub body: Option<serde_json::Value>,
413
414    /// Content type
415    pub content_type: Option<String>,
416
417    /// Response delay in milliseconds
418    #[serde(default)]
419    pub delay_ms: u64,
420
421    /// Template variables for dynamic responses
422    #[serde(default)]
423    pub template_vars: HashMap<String, serde_json::Value>,
424}
425
426/// Status representation for fixtures
427#[derive(Debug, Clone, Serialize, Deserialize)]
428#[serde(untagged)]
429pub enum FixtureStatus {
430    /// HTTP status code
431    Http(u16),
432    /// gRPC status code
433    Grpc(i32),
434    /// Generic success/failure
435    Generic(bool),
436    /// Custom status with code and message
437    Custom {
438        /// Custom status code
439        code: i32,
440        /// Custom status message
441        message: String,
442    },
443}
444
445fn default_true() -> bool {
446    true
447}
448
449impl UnifiedFixture {
450    /// Check if this fixture matches the given protocol request
451    pub fn matches(&self, request: &ProtocolRequest) -> bool {
452        // Check protocol
453        if request.protocol != self.protocol {
454            return false;
455        }
456
457        // Check pattern
458        if let Some(pattern) = &self.request.pattern {
459            if request.pattern != *pattern {
460                return false;
461            }
462        }
463
464        // Check operation
465        if let Some(operation) = &self.request.operation {
466            if !self.matches_pattern(&request.operation, operation) {
467                return false;
468            }
469        }
470
471        // Check path
472        if let Some(path) = &self.request.path {
473            if !self.matches_pattern(&request.path, path) {
474                return false;
475            }
476        }
477
478        // Check topic
479        if let Some(topic) = &self.request.topic {
480            if !self.matches_pattern(request.topic.as_ref().unwrap_or(&String::new()), topic) {
481                return false;
482            }
483        }
484
485        // Check routing key
486        if let Some(routing_key) = &self.request.routing_key {
487            if !self.matches_pattern(
488                request.routing_key.as_ref().unwrap_or(&String::new()),
489                routing_key,
490            ) {
491                return false;
492            }
493        }
494
495        // Check partition
496        if let Some(partition) = self.request.partition {
497            if request.partition != Some(partition) {
498                return false;
499            }
500        }
501
502        // Check QoS
503        if let Some(qos) = self.request.qos {
504            if request.qos != Some(qos) {
505                return false;
506            }
507        }
508
509        // Check headers
510        for (key, expected_value) in &self.request.headers {
511            if let Some(actual_value) = request.metadata.get(key) {
512                if !self.matches_pattern(actual_value, expected_value) {
513                    return false;
514                }
515            } else {
516                return false;
517            }
518        }
519
520        // Check body pattern
521        if let Some(pattern) = &self.request.body_pattern {
522            if let Some(body) = &request.body {
523                let body_str = String::from_utf8_lossy(body);
524                if !self.matches_pattern(&body_str, pattern) {
525                    return false;
526                }
527            } else {
528                return false;
529            }
530        }
531
532        // Check custom matcher logic
533        if let Some(custom_matcher) = &self.request.custom_matcher {
534            if !self.evaluate_custom_matcher(custom_matcher, request) {
535                return false;
536            }
537        }
538
539        true
540    }
541
542    /// Helper method to match patterns (supports regex and exact match)
543    fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
544        use regex::Regex;
545
546        // Try regex first
547        if let Ok(re) = Regex::new(pattern) {
548            re.is_match(value)
549        } else {
550            // Fall back to exact match
551            value == pattern
552        }
553    }
554
555    /// Evaluate custom matcher expression
556    fn evaluate_custom_matcher(&self, expression: &str, request: &ProtocolRequest) -> bool {
557        // Simple expression evaluator for custom matching logic
558        // Supports basic conditions like:
559        // - operation == "GET"
560        // - path =~ "/api/.*"
561        // - headers.content-type == "application/json"
562        // - body contains "test"
563
564        let expr = expression.trim();
565
566        // Handle different types of expressions
567        if expr.contains("==") {
568            self.evaluate_equality(expr, request)
569        } else if expr.contains("=~") {
570            self.evaluate_regex_match(expr, request)
571        } else if expr.contains("contains") {
572            self.evaluate_contains(expr, request)
573        } else {
574            // Unknown expression format, log warning and return false
575            tracing::warn!("Unknown custom matcher expression format: {}", expr);
576            false
577        }
578    }
579
580    /// Evaluate equality expressions (field == "value")
581    fn evaluate_equality(&self, expr: &str, request: &ProtocolRequest) -> bool {
582        let parts: Vec<&str> = expr.split("==").map(|s| s.trim()).collect();
583        if parts.len() != 2 {
584            return false;
585        }
586
587        let field = parts[0];
588        let expected_value = parts[1].trim_matches('"');
589
590        match field {
591            "operation" => request.operation == expected_value,
592            "path" => request.path == expected_value,
593            "topic" => request.topic.as_ref().unwrap_or(&String::new()) == expected_value,
594            "routing_key" => {
595                request.routing_key.as_ref().unwrap_or(&String::new()) == expected_value
596            }
597            _ if field.starts_with("headers.") => {
598                let header_name = &field[8..]; // Remove "headers." prefix
599                request.metadata.get(header_name).is_some_and(|v| v == expected_value)
600            }
601            _ => {
602                tracing::warn!("Unknown field in equality expression: {}", field);
603                false
604            }
605        }
606    }
607
608    /// Evaluate regex match expressions (field =~ "pattern")
609    fn evaluate_regex_match(&self, expr: &str, request: &ProtocolRequest) -> bool {
610        let parts: Vec<&str> = expr.split("=~").map(|s| s.trim()).collect();
611        if parts.len() != 2 {
612            return false;
613        }
614
615        let field = parts[0];
616        let pattern = parts[1].trim_matches('"');
617
618        let value: String = match field {
619            "operation" => request.operation.clone(),
620            "path" => request.path.clone(),
621            "topic" => request.topic.clone().unwrap_or_default(),
622            "routing_key" => request.routing_key.clone().unwrap_or_default(),
623            _ if field.starts_with("headers.") => {
624                let header_name = &field[8..]; // Remove "headers." prefix
625                request.metadata.get(header_name).cloned().unwrap_or_default()
626            }
627            _ => {
628                tracing::warn!("Unknown field in regex expression: {}", field);
629                return false;
630            }
631        };
632
633        use regex::Regex;
634        match Regex::new(pattern) {
635            Ok(re) => re.is_match(&value),
636            Err(e) => {
637                tracing::warn!("Invalid regex pattern '{}': {}", pattern, e);
638                false
639            }
640        }
641    }
642
643    /// Evaluate contains expressions (field contains "substring")
644    fn evaluate_contains(&self, expr: &str, request: &ProtocolRequest) -> bool {
645        let parts: Vec<&str> = expr.split("contains").map(|s| s.trim()).collect();
646        if parts.len() != 2 {
647            return false;
648        }
649
650        let field = parts[0];
651        let substring = parts[1].trim_matches('"');
652
653        let value: String = match field {
654            "body" => {
655                if let Some(body) = &request.body {
656                    String::from_utf8_lossy(body).to_string()
657                } else {
658                    return false;
659                }
660            }
661            _ if field.starts_with("headers.") => {
662                let header_name = &field[8..]; // Remove "headers." prefix
663                request.metadata.get(header_name).cloned().unwrap_or_default()
664            }
665            _ => {
666                tracing::warn!("Unsupported field for contains expression: {}", field);
667                return false;
668            }
669        };
670
671        value.contains(substring)
672    }
673
674    /// Convert fixture response to ProtocolResponse
675    pub fn to_protocol_response(&self) -> Result<ProtocolResponse> {
676        let status = match &self.response.status {
677            FixtureStatus::Http(code) => ResponseStatus::HttpStatus(*code),
678            FixtureStatus::Grpc(code) => ResponseStatus::GrpcStatus(*code),
679            FixtureStatus::Generic(success) => ResponseStatus::GraphQLStatus(*success), // Using GraphQL as generic
680            FixtureStatus::Custom { code, .. } => ResponseStatus::GrpcStatus(*code), // Using gRPC as custom
681        };
682
683        let body = match &self.response.body {
684            Some(serde_json::Value::String(s)) => s.clone().into_bytes(),
685            Some(value) => serde_json::to_string(value)?.into_bytes(),
686            None => Vec::new(),
687        };
688
689        let content_type = self
690            .response
691            .content_type
692            .clone()
693            .unwrap_or_else(|| "application/json".to_string());
694
695        Ok(ProtocolResponse {
696            status,
697            metadata: self.response.headers.clone(),
698            body,
699            content_type,
700        })
701    }
702}
703
704/// Middleware chain for composing and executing multiple middleware in sequence
705pub struct MiddlewareChain {
706    /// Ordered list of middleware to execute
707    middleware: Vec<Arc<dyn ProtocolMiddleware>>,
708}
709
710impl MiddlewareChain {
711    /// Create a new middleware chain
712    pub fn new() -> Self {
713        Self {
714            middleware: Vec::new(),
715        }
716    }
717
718    /// Add middleware to the chain
719    pub fn with_middleware(mut self, middleware: Arc<dyn ProtocolMiddleware>) -> Self {
720        self.middleware.push(middleware);
721        self
722    }
723
724    /// Process a request through all middleware
725    pub async fn process_request(&self, request: &mut ProtocolRequest) -> Result<()> {
726        for middleware in &self.middleware {
727            if middleware.supports_protocol(request.protocol) {
728                middleware.process_request(request).await?;
729            }
730        }
731        Ok(())
732    }
733
734    /// Process a response through all middleware (in reverse order)
735    pub async fn process_response(
736        &self,
737        request: &ProtocolRequest,
738        response: &mut ProtocolResponse,
739    ) -> Result<()> {
740        for middleware in self.middleware.iter().rev() {
741            if middleware.supports_protocol(request.protocol) {
742                middleware.process_response(request, response).await?;
743            }
744        }
745        Ok(())
746    }
747}
748
749impl Default for MiddlewareChain {
750    fn default() -> Self {
751        Self::new()
752    }
753}
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758
759    #[test]
760    fn test_protocol_display() {
761        assert_eq!(Protocol::Http.to_string(), "HTTP");
762        assert_eq!(Protocol::GraphQL.to_string(), "GraphQL");
763        assert_eq!(Protocol::Grpc.to_string(), "gRPC");
764        assert_eq!(Protocol::WebSocket.to_string(), "WebSocket");
765        assert_eq!(Protocol::Smtp.to_string(), "SMTP");
766        assert_eq!(Protocol::Mqtt.to_string(), "MQTT");
767        assert_eq!(Protocol::Ftp.to_string(), "FTP");
768        assert_eq!(Protocol::Kafka.to_string(), "Kafka");
769        assert_eq!(Protocol::RabbitMq.to_string(), "RabbitMQ");
770        assert_eq!(Protocol::Amqp.to_string(), "AMQP");
771        assert_eq!(Protocol::Tcp.to_string(), "TCP");
772    }
773
774    #[test]
775    fn test_response_status_is_success() {
776        assert!(ResponseStatus::HttpStatus(200).is_success());
777        assert!(ResponseStatus::HttpStatus(204).is_success());
778        assert!(!ResponseStatus::HttpStatus(404).is_success());
779        assert!(!ResponseStatus::HttpStatus(500).is_success());
780
781        assert!(ResponseStatus::GrpcStatus(0).is_success());
782        assert!(!ResponseStatus::GrpcStatus(2).is_success());
783
784        assert!(ResponseStatus::GraphQLStatus(true).is_success());
785        assert!(!ResponseStatus::GraphQLStatus(false).is_success());
786    }
787
788    #[test]
789    fn test_response_status_as_code() {
790        assert_eq!(ResponseStatus::HttpStatus(200).as_code(), Some(200));
791        assert_eq!(ResponseStatus::GrpcStatus(0).as_code(), Some(0));
792        assert_eq!(ResponseStatus::GraphQLStatus(true).as_code(), None);
793    }
794
795    #[test]
796    fn test_validation_result_success() {
797        let result = ValidationResult::success();
798        assert!(result.valid);
799        assert_eq!(result.errors.len(), 0);
800        assert_eq!(result.warnings.len(), 0);
801    }
802
803    #[test]
804    fn test_validation_result_failure() {
805        let errors = vec![ValidationError {
806            message: "Invalid field".to_string(),
807            path: Some("body.field".to_string()),
808            code: Some("INVALID_FIELD".to_string()),
809        }];
810        let result = ValidationResult::failure(errors);
811        assert!(!result.valid);
812        assert_eq!(result.errors.len(), 1);
813    }
814
815    #[test]
816    fn test_validation_result_with_warning() {
817        let result = ValidationResult::success().with_warning("Deprecated field used".to_string());
818        assert!(result.valid);
819        assert_eq!(result.warnings.len(), 1);
820    }
821
822    #[test]
823    fn test_middleware_chain_creation() {
824        let chain = MiddlewareChain::new();
825        assert_eq!(chain.middleware.len(), 0);
826    }
827
828    #[test]
829    fn test_protocol_request_creation() {
830        let request = ProtocolRequest {
831            protocol: Protocol::Http,
832            operation: "GET".to_string(),
833            path: "/users".to_string(),
834            client_ip: Some("127.0.0.1".to_string()),
835            ..Default::default()
836        };
837        assert_eq!(request.protocol, Protocol::Http);
838        assert_eq!(request.pattern, MessagePattern::RequestResponse);
839        assert_eq!(request.operation, "GET");
840        assert_eq!(request.path, "/users");
841    }
842
843    #[test]
844    fn test_protocol_response_creation() {
845        let response = ProtocolResponse {
846            status: ResponseStatus::HttpStatus(200),
847            metadata: HashMap::new(),
848            body: b"{}".to_vec(),
849            content_type: "application/json".to_string(),
850        };
851        assert!(response.status.is_success());
852        assert_eq!(response.content_type, "application/json");
853    }
854
855    #[test]
856    fn test_unified_fixture_matching() {
857        let fixture = UnifiedFixture {
858            id: "test-fixture".to_string(),
859            name: "Test Fixture".to_string(),
860            description: "A test fixture".to_string(),
861            protocol: Protocol::Http,
862            request: FixtureRequest {
863                pattern: Some(MessagePattern::RequestResponse),
864                operation: Some("GET".to_string()),
865                path: Some("/api/users".to_string()),
866                topic: None,
867                routing_key: None,
868                partition: None,
869                qos: None,
870                headers: HashMap::new(),
871                body_pattern: None,
872                custom_matcher: None,
873            },
874            response: FixtureResponse {
875                status: FixtureStatus::Http(200),
876                headers: HashMap::new(),
877                body: Some(serde_json::json!({"users": ["john", "jane"]})),
878                content_type: Some("application/json".to_string()),
879                delay_ms: 0,
880                template_vars: HashMap::new(),
881            },
882            metadata: HashMap::new(),
883            enabled: true,
884            priority: 0,
885            tags: vec![],
886        };
887
888        let matching_request = ProtocolRequest {
889            protocol: Protocol::Http,
890            pattern: MessagePattern::RequestResponse,
891            operation: "GET".to_string(),
892            path: "/api/users".to_string(),
893            topic: None,
894            routing_key: None,
895            partition: None,
896            qos: None,
897            metadata: HashMap::new(),
898            body: None,
899            client_ip: None,
900        };
901
902        let non_matching_request = ProtocolRequest {
903            protocol: Protocol::Http,
904            pattern: MessagePattern::RequestResponse,
905            operation: "POST".to_string(),
906            path: "/api/users".to_string(),
907            topic: None,
908            routing_key: None,
909            partition: None,
910            qos: None,
911            metadata: HashMap::new(),
912            body: None,
913            client_ip: None,
914        };
915
916        assert!(fixture.matches(&matching_request));
917        assert!(!fixture.matches(&non_matching_request));
918    }
919
920    #[test]
921    fn test_fixture_to_protocol_response() {
922        let fixture = UnifiedFixture {
923            id: "test".to_string(),
924            name: "Test".to_string(),
925            description: "".to_string(),
926            protocol: Protocol::Http,
927            request: FixtureRequest {
928                pattern: None,
929                operation: None,
930                path: None,
931                topic: None,
932                routing_key: None,
933                partition: None,
934                qos: None,
935                headers: HashMap::new(),
936                body_pattern: None,
937                custom_matcher: None,
938            },
939            response: FixtureResponse {
940                status: FixtureStatus::Http(200),
941                headers: {
942                    let mut h = HashMap::new();
943                    h.insert("content-type".to_string(), "application/json".to_string());
944                    h
945                },
946                body: Some(serde_json::json!({"message": "ok"})),
947                content_type: Some("application/json".to_string()),
948                delay_ms: 0,
949                template_vars: HashMap::new(),
950            },
951            metadata: HashMap::new(),
952            enabled: true,
953            priority: 0,
954            tags: vec![],
955        };
956
957        let response = fixture.to_protocol_response().unwrap();
958        assert!(response.status.is_success());
959        assert_eq!(response.content_type, "application/json");
960        assert_eq!(response.metadata.get("content-type"), Some(&"application/json".to_string()));
961    }
962
963    #[test]
964    fn test_fixture_status_serialization() {
965        // Test HTTP status
966        let status = FixtureStatus::Http(404);
967        let serialized = serde_json::to_string(&status).unwrap();
968        assert_eq!(serialized, "404");
969
970        // Test gRPC status
971        let status = FixtureStatus::Grpc(5);
972        let serialized = serde_json::to_string(&status).unwrap();
973        assert_eq!(serialized, "5");
974
975        // Test generic status
976        let status = FixtureStatus::Generic(true);
977        let serialized = serde_json::to_string(&status).unwrap();
978        assert_eq!(serialized, "true");
979
980        // Test custom status
981        let status = FixtureStatus::Custom {
982            code: 500,
983            message: "Internal Error".to_string(),
984        };
985        let serialized = serde_json::to_string(&status).unwrap();
986        let expected: serde_json::Value =
987            serde_json::json!({"code": 500, "message": "Internal Error"});
988        assert_eq!(serde_json::from_str::<serde_json::Value>(&serialized).unwrap(), expected);
989    }
990
991    #[test]
992    fn test_fixture_pattern_matching() {
993        let fixture = UnifiedFixture {
994            id: "test".to_string(),
995            name: "Test".to_string(),
996            description: "".to_string(),
997            protocol: Protocol::Http,
998            request: FixtureRequest {
999                pattern: Some(MessagePattern::RequestResponse),
1000                operation: Some("GET".to_string()),
1001                path: Some("/api/.*".to_string()),
1002                topic: None,
1003                routing_key: None,
1004                partition: None,
1005                qos: None,
1006                headers: HashMap::new(),
1007                body_pattern: None,
1008                custom_matcher: None,
1009            },
1010            response: FixtureResponse {
1011                status: FixtureStatus::Http(200),
1012                headers: HashMap::new(),
1013                body: None,
1014                content_type: None,
1015                delay_ms: 0,
1016                template_vars: HashMap::new(),
1017            },
1018            metadata: HashMap::new(),
1019            enabled: true,
1020            priority: 0,
1021            tags: vec![],
1022        };
1023
1024        // Test matching request
1025        let request = ProtocolRequest {
1026            protocol: Protocol::Http,
1027            pattern: MessagePattern::RequestResponse,
1028            operation: "GET".to_string(),
1029            path: "/api/users".to_string(),
1030            topic: None,
1031            routing_key: None,
1032            partition: None,
1033            qos: None,
1034            metadata: HashMap::new(),
1035            body: None,
1036            client_ip: None,
1037        };
1038        assert!(fixture.matches(&request));
1039
1040        // Test non-matching protocol
1041        let grpc_request = ProtocolRequest {
1042            protocol: Protocol::Grpc,
1043            pattern: MessagePattern::RequestResponse,
1044            operation: "GET".to_string(),
1045            path: "/api/users".to_string(),
1046            topic: None,
1047            routing_key: None,
1048            partition: None,
1049            qos: None,
1050            metadata: HashMap::new(),
1051            body: None,
1052            client_ip: None,
1053        };
1054        assert!(!fixture.matches(&grpc_request));
1055
1056        // Test non-matching operation
1057        let post_request = ProtocolRequest {
1058            protocol: Protocol::Http,
1059            pattern: MessagePattern::RequestResponse,
1060            operation: "POST".to_string(),
1061            path: "/api/users".to_string(),
1062            topic: None,
1063            routing_key: None,
1064            partition: None,
1065            qos: None,
1066            metadata: HashMap::new(),
1067            body: None,
1068            client_ip: None,
1069        };
1070        assert!(!fixture.matches(&post_request));
1071    }
1072
1073    #[test]
1074    fn test_custom_matcher_equality() {
1075        let fixture = UnifiedFixture {
1076            id: "test".to_string(),
1077            name: "Test".to_string(),
1078            description: "".to_string(),
1079            protocol: Protocol::Http,
1080            request: FixtureRequest {
1081                pattern: Some(MessagePattern::RequestResponse),
1082                operation: Some("GET".to_string()),
1083                path: Some("/api/users".to_string()),
1084                topic: None,
1085                routing_key: None,
1086                partition: None,
1087                qos: None,
1088                headers: HashMap::new(),
1089                body_pattern: None,
1090                custom_matcher: Some("operation == \"GET\"".to_string()),
1091            },
1092            response: FixtureResponse {
1093                status: FixtureStatus::Http(200),
1094                headers: HashMap::new(),
1095                body: None,
1096                content_type: None,
1097                delay_ms: 0,
1098                template_vars: HashMap::new(),
1099            },
1100            metadata: HashMap::new(),
1101            enabled: true,
1102            priority: 0,
1103            tags: vec![],
1104        };
1105
1106        // Test matching request
1107        let request = ProtocolRequest {
1108            protocol: Protocol::Http,
1109            pattern: MessagePattern::RequestResponse,
1110            operation: "GET".to_string(),
1111            path: "/api/users".to_string(),
1112            topic: None,
1113            routing_key: None,
1114            partition: None,
1115            qos: None,
1116            metadata: HashMap::new(),
1117            body: None,
1118            client_ip: None,
1119        };
1120        assert!(fixture.matches(&request));
1121
1122        // Test non-matching request
1123        let post_request = ProtocolRequest {
1124            protocol: Protocol::Http,
1125            pattern: MessagePattern::RequestResponse,
1126            operation: "POST".to_string(),
1127            path: "/api/users".to_string(),
1128            topic: None,
1129            routing_key: None,
1130            partition: None,
1131            qos: None,
1132            metadata: HashMap::new(),
1133            body: None,
1134            client_ip: None,
1135        };
1136        assert!(!fixture.matches(&post_request));
1137    }
1138
1139    #[test]
1140    fn test_custom_matcher_regex() {
1141        let fixture = UnifiedFixture {
1142            id: "test".to_string(),
1143            name: "Test".to_string(),
1144            description: "".to_string(),
1145            protocol: Protocol::Http,
1146            request: FixtureRequest {
1147                pattern: Some(MessagePattern::RequestResponse),
1148                operation: Some("GET".to_string()),
1149                path: Some("/api/.*".to_string()),
1150                topic: None,
1151                routing_key: None,
1152                partition: None,
1153                qos: None,
1154                headers: HashMap::new(),
1155                body_pattern: None,
1156                custom_matcher: Some("path =~ \"/api/.*\"".to_string()),
1157            },
1158            response: FixtureResponse {
1159                status: FixtureStatus::Http(200),
1160                headers: HashMap::new(),
1161                body: None,
1162                content_type: None,
1163                delay_ms: 0,
1164                template_vars: HashMap::new(),
1165            },
1166            metadata: HashMap::new(),
1167            enabled: true,
1168            priority: 0,
1169            tags: vec![],
1170        };
1171
1172        // Test matching request
1173        let request = ProtocolRequest {
1174            protocol: Protocol::Http,
1175            pattern: MessagePattern::RequestResponse,
1176            operation: "GET".to_string(),
1177            path: "/api/users".to_string(),
1178            topic: None,
1179            routing_key: None,
1180            partition: None,
1181            qos: None,
1182            metadata: HashMap::new(),
1183            body: None,
1184            client_ip: None,
1185        };
1186        assert!(fixture.matches(&request));
1187
1188        // Test non-matching request
1189        let other_request = ProtocolRequest {
1190            protocol: Protocol::Http,
1191            pattern: MessagePattern::RequestResponse,
1192            operation: "GET".to_string(),
1193            path: "/other/path".to_string(),
1194            topic: None,
1195            routing_key: None,
1196            partition: None,
1197            qos: None,
1198            metadata: HashMap::new(),
1199            body: None,
1200            client_ip: None,
1201        };
1202        assert!(!fixture.matches(&other_request));
1203    }
1204
1205    #[test]
1206    fn test_custom_matcher_contains() {
1207        let fixture = UnifiedFixture {
1208            id: "test".to_string(),
1209            name: "Test".to_string(),
1210            description: "".to_string(),
1211            protocol: Protocol::Http,
1212            request: FixtureRequest {
1213                pattern: Some(MessagePattern::RequestResponse),
1214                operation: Some("POST".to_string()),
1215                path: Some("/api/users".to_string()),
1216                topic: None,
1217                routing_key: None,
1218                partition: None,
1219                qos: None,
1220                headers: HashMap::new(),
1221                body_pattern: None,
1222                custom_matcher: Some("body contains \"test\"".to_string()),
1223            },
1224            response: FixtureResponse {
1225                status: FixtureStatus::Http(200),
1226                headers: HashMap::new(),
1227                body: None,
1228                content_type: None,
1229                delay_ms: 0,
1230                template_vars: HashMap::new(),
1231            },
1232            metadata: HashMap::new(),
1233            enabled: true,
1234            priority: 0,
1235            tags: vec![],
1236        };
1237
1238        // Test matching request
1239        let request = ProtocolRequest {
1240            protocol: Protocol::Http,
1241            pattern: MessagePattern::RequestResponse,
1242            operation: "POST".to_string(),
1243            path: "/api/users".to_string(),
1244            topic: None,
1245            routing_key: None,
1246            partition: None,
1247            qos: None,
1248            metadata: HashMap::new(),
1249            body: Some(b"{\"name\": \"test user\"}".to_vec()),
1250            client_ip: None,
1251        };
1252        assert!(fixture.matches(&request));
1253
1254        // Test non-matching request
1255        let no_match_request = ProtocolRequest {
1256            protocol: Protocol::Http,
1257            pattern: MessagePattern::RequestResponse,
1258            operation: "POST".to_string(),
1259            path: "/api/users".to_string(),
1260            topic: None,
1261            routing_key: None,
1262            partition: None,
1263            qos: None,
1264            metadata: HashMap::new(),
1265            body: Some(b"{\"name\": \"other user\"}".to_vec()),
1266            client_ip: None,
1267        };
1268        assert!(!fixture.matches(&no_match_request));
1269    }
1270
1271    #[test]
1272    fn test_custom_matcher_header() {
1273        let fixture = UnifiedFixture {
1274            id: "test".to_string(),
1275            name: "Test".to_string(),
1276            description: "".to_string(),
1277            protocol: Protocol::Http,
1278            request: FixtureRequest {
1279                pattern: Some(MessagePattern::RequestResponse),
1280                operation: Some("GET".to_string()),
1281                path: Some("/api/data".to_string()),
1282                topic: None,
1283                routing_key: None,
1284                partition: None,
1285                qos: None,
1286                headers: HashMap::new(),
1287                body_pattern: None,
1288                custom_matcher: Some("headers.content-type == \"application/json\"".to_string()),
1289            },
1290            response: FixtureResponse {
1291                status: FixtureStatus::Http(200),
1292                headers: HashMap::new(),
1293                body: None,
1294                content_type: None,
1295                delay_ms: 0,
1296                template_vars: HashMap::new(),
1297            },
1298            metadata: HashMap::new(),
1299            enabled: true,
1300            priority: 0,
1301            tags: vec![],
1302        };
1303
1304        // Test matching request
1305        let mut headers = HashMap::new();
1306        headers.insert("content-type".to_string(), "application/json".to_string());
1307        let request = ProtocolRequest {
1308            protocol: Protocol::Http,
1309            pattern: MessagePattern::RequestResponse,
1310            operation: "GET".to_string(),
1311            path: "/api/data".to_string(),
1312            topic: None,
1313            routing_key: None,
1314            partition: None,
1315            qos: None,
1316            metadata: headers,
1317            body: None,
1318            client_ip: None,
1319        };
1320        assert!(fixture.matches(&request));
1321
1322        // Test non-matching request
1323        let mut wrong_headers = HashMap::new();
1324        wrong_headers.insert("content-type".to_string(), "text/plain".to_string());
1325        let wrong_request = ProtocolRequest {
1326            protocol: Protocol::Http,
1327            pattern: MessagePattern::RequestResponse,
1328            operation: "GET".to_string(),
1329            path: "/api/data".to_string(),
1330            topic: None,
1331            routing_key: None,
1332            partition: None,
1333            qos: None,
1334            metadata: wrong_headers,
1335            body: None,
1336            client_ip: None,
1337        };
1338        assert!(!fixture.matches(&wrong_request));
1339    }
1340}