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