Skip to main content

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