Skip to main content

mockforge_core/
priority_handler.rs

1//! Priority-based HTTP request handler implementing the full priority chain:
2//! Custom Fixtures → Replay → Stateful → Route Chaos (per-route fault/latency) → Global Fail → Proxy → Mock → Record
3
4use crate::behavioral_economics::BehavioralEconomicsEngine;
5use crate::stateful_handler::StatefulResponseHandler;
6use crate::{
7    CustomFixtureLoader, Error, FailureInjector, ProxyHandler, RealityContinuumEngine,
8    RecordReplayHandler, RequestFingerprint, ResponsePriority, ResponseSource, Result,
9};
10// RouteChaosInjector moved to mockforge-route-chaos crate to avoid Send issues
11// We define a trait here that RouteChaosInjector can implement to avoid circular dependency
12use async_trait::async_trait;
13use axum::http::{HeaderMap, Method, StatusCode, Uri};
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18/// Fault injection response (defined in mockforge-core to avoid circular dependency)
19#[derive(Debug, Clone)]
20pub struct RouteFaultResponse {
21    /// HTTP status code
22    pub status_code: u16,
23    /// Error message
24    pub error_message: String,
25    /// Fault type identifier
26    pub fault_type: String,
27}
28
29/// Trait for route chaos injection (fault injection and latency)
30/// This trait is defined in mockforge-core to avoid circular dependency.
31/// The concrete RouteChaosInjector in mockforge-route-chaos implements this trait.
32#[async_trait]
33pub trait RouteChaosInjectorTrait: Send + Sync {
34    /// Inject latency for this request
35    async fn inject_latency(&self, method: &Method, uri: &Uri) -> Result<()>;
36
37    /// Get fault injection response for a request
38    fn get_fault_response(&self, method: &Method, uri: &Uri) -> Option<RouteFaultResponse>;
39}
40
41/// Trait for behavioral scenario replay engines
42#[async_trait]
43pub trait BehavioralScenarioReplay: Send + Sync {
44    /// Try to replay a request against active scenarios
45    async fn try_replay(
46        &self,
47        method: &Method,
48        uri: &Uri,
49        headers: &HeaderMap,
50        body: Option<&[u8]>,
51        session_id: Option<&str>,
52    ) -> Result<Option<BehavioralReplayResponse>>;
53}
54
55/// Response from behavioral scenario replay
56#[derive(Debug, Clone)]
57pub struct BehavioralReplayResponse {
58    /// HTTP status code
59    pub status_code: u16,
60    /// Response headers
61    pub headers: HashMap<String, String>,
62    /// Response body
63    pub body: Vec<u8>,
64    /// Timing delay in milliseconds
65    pub timing_ms: Option<u64>,
66    /// Content type
67    pub content_type: String,
68}
69
70/// Priority-based HTTP request handler
71pub struct PriorityHttpHandler {
72    /// Custom fixture loader (simple format fixtures)
73    custom_fixture_loader: Option<Arc<CustomFixtureLoader>>,
74    /// Record/replay handler
75    record_replay: RecordReplayHandler,
76    /// Behavioral scenario replay engine (for journey-level simulations)
77    behavioral_scenario_replay: Option<Arc<dyn BehavioralScenarioReplay + Send + Sync>>,
78    /// Stateful response handler
79    stateful_handler: Option<Arc<StatefulResponseHandler>>,
80    /// Per-route chaos injector (fault injection and latency)
81    /// Uses trait object to avoid circular dependency with mockforge-route-chaos
82    route_chaos_injector: Option<Arc<dyn RouteChaosInjectorTrait>>,
83    /// Failure injector (global/tag-based)
84    failure_injector: Option<FailureInjector>,
85    /// Proxy handler
86    proxy_handler: Option<ProxyHandler>,
87    /// Mock response generator (from OpenAPI spec)
88    mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
89    /// OpenAPI spec for tag extraction
90    openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
91    /// Reality Continuum engine for blending mock and real responses
92    continuum_engine: Option<Arc<RealityContinuumEngine>>,
93    /// Behavioral Economics Engine for reactive mock behavior
94    behavioral_economics_engine: Option<Arc<RwLock<BehavioralEconomicsEngine>>>,
95    /// Request tracking for metrics (endpoint -> (request_count, error_count, last_request_time))
96    request_metrics: Arc<RwLock<HashMap<String, (u64, u64, std::time::Instant)>>>,
97}
98
99/// Trait for mock response generation
100pub trait MockGenerator {
101    /// Generate a mock response for the given request
102    fn generate_mock_response(
103        &self,
104        fingerprint: &RequestFingerprint,
105        headers: &HeaderMap,
106        body: Option<&[u8]>,
107    ) -> Result<Option<MockResponse>>;
108}
109
110/// Mock response
111#[derive(Debug, Clone)]
112pub struct MockResponse {
113    /// Response status code
114    pub status_code: u16,
115    /// Response headers
116    pub headers: HashMap<String, String>,
117    /// Response body
118    pub body: String,
119    /// Content type
120    pub content_type: String,
121}
122
123impl PriorityHttpHandler {
124    /// Create a new priority HTTP handler
125    pub fn new(
126        record_replay: RecordReplayHandler,
127        failure_injector: Option<FailureInjector>,
128        proxy_handler: Option<ProxyHandler>,
129        mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
130    ) -> Self {
131        Self {
132            custom_fixture_loader: None,
133            record_replay,
134            behavioral_scenario_replay: None,
135            stateful_handler: None,
136            route_chaos_injector: None,
137            failure_injector,
138            proxy_handler,
139            mock_generator,
140            openapi_spec: None,
141            continuum_engine: None,
142            behavioral_economics_engine: None,
143            request_metrics: Arc::new(RwLock::new(HashMap::new())),
144        }
145    }
146
147    /// Create a new priority HTTP handler with OpenAPI spec
148    pub fn new_with_openapi(
149        record_replay: RecordReplayHandler,
150        failure_injector: Option<FailureInjector>,
151        proxy_handler: Option<ProxyHandler>,
152        mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
153        openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
154    ) -> Self {
155        Self {
156            custom_fixture_loader: None,
157            record_replay,
158            behavioral_scenario_replay: None,
159            stateful_handler: None,
160            route_chaos_injector: None,
161            failure_injector,
162            proxy_handler,
163            mock_generator,
164            openapi_spec,
165            continuum_engine: None,
166            behavioral_economics_engine: None,
167            request_metrics: Arc::new(RwLock::new(HashMap::new())),
168        }
169    }
170
171    /// Set custom fixture loader
172    pub fn with_custom_fixture_loader(mut self, loader: Arc<CustomFixtureLoader>) -> Self {
173        self.custom_fixture_loader = Some(loader);
174        self
175    }
176
177    /// Set stateful response handler
178    pub fn with_stateful_handler(mut self, handler: Arc<StatefulResponseHandler>) -> Self {
179        self.stateful_handler = Some(handler);
180        self
181    }
182
183    /// Set per-route chaos injector
184    pub fn with_route_chaos_injector(mut self, injector: Arc<dyn RouteChaosInjectorTrait>) -> Self {
185        self.route_chaos_injector = Some(injector);
186        self
187    }
188
189    /// Set Reality Continuum engine
190    pub fn with_continuum_engine(mut self, engine: Arc<RealityContinuumEngine>) -> Self {
191        self.continuum_engine = Some(engine);
192        self
193    }
194
195    /// Set Behavioral Economics Engine
196    pub fn with_behavioral_economics_engine(
197        mut self,
198        engine: Arc<RwLock<BehavioralEconomicsEngine>>,
199    ) -> Self {
200        self.behavioral_economics_engine = Some(engine);
201        self
202    }
203
204    /// Set behavioral scenario replay engine
205    pub fn with_behavioral_scenario_replay(
206        mut self,
207        replay_engine: Arc<dyn BehavioralScenarioReplay + Send + Sync>,
208    ) -> Self {
209        self.behavioral_scenario_replay = Some(replay_engine);
210        self
211    }
212
213    /// Process a request through the priority chain
214    pub async fn process_request(
215        &self,
216        method: &Method,
217        uri: &Uri,
218        headers: &HeaderMap,
219        body: Option<&[u8]>,
220    ) -> Result<PriorityResponse> {
221        // Normalize the URI path before creating fingerprint to match fixture normalization
222        // This ensures fixtures are matched correctly
223        let normalized_path = CustomFixtureLoader::normalize_path(uri.path());
224        let normalized_uri_str = if let Some(query) = uri.query() {
225            format!("{}?{}", normalized_path, query)
226        } else {
227            normalized_path
228        };
229        let normalized_uri = normalized_uri_str.parse::<Uri>().unwrap_or_else(|_| uri.clone());
230
231        let fingerprint = RequestFingerprint::new(method.clone(), &normalized_uri, headers, body);
232
233        // 0. CUSTOM FIXTURES: Check if we have a custom fixture (highest priority)
234        if let Some(ref custom_loader) = self.custom_fixture_loader {
235            if let Some(custom_fixture) = custom_loader.load_fixture(&fingerprint) {
236                // Apply delay if specified
237                if custom_fixture.delay_ms > 0 {
238                    tokio::time::sleep(tokio::time::Duration::from_millis(custom_fixture.delay_ms))
239                        .await;
240                }
241
242                // Convert response to JSON string if it's not already a string
243                let response_body = if custom_fixture.response.is_string() {
244                    custom_fixture.response.as_str().unwrap().to_string()
245                } else {
246                    serde_json::to_string(&custom_fixture.response).map_err(|e| {
247                        Error::generic(format!(
248                            "Failed to serialize custom fixture response: {}",
249                            e
250                        ))
251                    })?
252                };
253
254                // Determine content type
255                let content_type = custom_fixture
256                    .headers
257                    .get("content-type")
258                    .cloned()
259                    .unwrap_or_else(|| "application/json".to_string());
260
261                return Ok(PriorityResponse {
262                    source: ResponseSource::new(
263                        ResponsePriority::Replay,
264                        "custom_fixture".to_string(),
265                    )
266                    .with_metadata("fixture_path".to_string(), custom_fixture.path.clone()),
267                    status_code: custom_fixture.status,
268                    headers: custom_fixture.headers.clone(),
269                    body: response_body.into_bytes(),
270                    content_type,
271                });
272            }
273        }
274
275        // 1. REPLAY: Check if we have a recorded fixture
276        if let Some(recorded_request) =
277            self.record_replay.replay_handler().load_fixture(&fingerprint).await?
278        {
279            let content_type = recorded_request
280                .response_headers
281                .get("content-type")
282                .unwrap_or(&"application/json".to_string())
283                .clone();
284
285            return Ok(PriorityResponse {
286                source: ResponseSource::new(ResponsePriority::Replay, "replay".to_string())
287                    .with_metadata("fixture_path".to_string(), "recorded".to_string()),
288                status_code: recorded_request.status_code,
289                headers: recorded_request.response_headers,
290                body: recorded_request.response_body.into_bytes(),
291                content_type,
292            });
293        }
294
295        // 1.5. BEHAVIORAL SCENARIO REPLAY: Check for active behavioral scenarios
296        if let Some(ref scenario_replay) = self.behavioral_scenario_replay {
297            // Extract session ID from headers or cookies
298            let session_id = headers
299                .get("x-session-id")
300                .or_else(|| headers.get("session-id"))
301                .and_then(|v| v.to_str().ok())
302                .map(|s| s.to_string());
303
304            if let Ok(Some(replay_response)) = scenario_replay
305                .try_replay(method, uri, headers, body, session_id.as_deref())
306                .await
307            {
308                // Apply timing delay if specified
309                if let Some(timing_ms) = replay_response.timing_ms {
310                    tokio::time::sleep(tokio::time::Duration::from_millis(timing_ms)).await;
311                }
312                return Ok(PriorityResponse {
313                    source: ResponseSource::new(
314                        ResponsePriority::Replay,
315                        "behavioral_scenario".to_string(),
316                    )
317                    .with_metadata("replay_type".to_string(), "scenario".to_string()),
318                    status_code: replay_response.status_code,
319                    headers: replay_response.headers,
320                    body: replay_response.body,
321                    content_type: replay_response.content_type,
322                });
323            }
324        }
325
326        // 2. STATEFUL: Check for stateful response handling
327        if let Some(ref stateful_handler) = self.stateful_handler {
328            if let Some(stateful_response) =
329                stateful_handler.process_request(method, uri, headers, body).await?
330            {
331                return Ok(PriorityResponse {
332                    source: ResponseSource::new(ResponsePriority::Stateful, "stateful".to_string())
333                        .with_metadata("state".to_string(), stateful_response.state)
334                        .with_metadata("resource_id".to_string(), stateful_response.resource_id),
335                    status_code: stateful_response.status_code,
336                    headers: stateful_response.headers,
337                    body: stateful_response.body.into_bytes(),
338                    content_type: stateful_response.content_type,
339                });
340            }
341        }
342
343        // 2.5. ROUTE CHAOS: Check for per-route fault injection and latency
344        if let Some(ref route_chaos) = self.route_chaos_injector {
345            // Inject latency first (before fault injection)
346            if let Err(e) = route_chaos.inject_latency(method, uri).await {
347                tracing::warn!("Failed to inject per-route latency: {}", e);
348            }
349
350            // Check for per-route fault injection
351            if let Some(fault_response) = route_chaos.get_fault_response(method, uri) {
352                let error_response = serde_json::json!({
353                    "error": fault_response.error_message,
354                    "injected_failure": true,
355                    "fault_type": fault_response.fault_type,
356                    "timestamp": chrono::Utc::now().to_rfc3339()
357                });
358
359                return Ok(PriorityResponse {
360                    source: ResponseSource::new(
361                        ResponsePriority::Fail,
362                        "route_fault_injection".to_string(),
363                    )
364                    .with_metadata("fault_type".to_string(), fault_response.fault_type)
365                    .with_metadata("error_message".to_string(), fault_response.error_message),
366                    status_code: fault_response.status_code,
367                    headers: HashMap::new(),
368                    body: serde_json::to_string(&error_response)?.into_bytes(),
369                    content_type: "application/json".to_string(),
370                });
371            }
372        }
373
374        // 3. FAIL: Check for global/tag-based failure injection
375        if let Some(ref failure_injector) = self.failure_injector {
376            let tags = if let Some(ref spec) = self.openapi_spec {
377                fingerprint.openapi_tags(spec).unwrap_or_else(|| fingerprint.tags())
378            } else {
379                fingerprint.tags()
380            };
381            if let Some((status_code, error_message)) = failure_injector.process_request(&tags) {
382                let error_response = serde_json::json!({
383                    "error": error_message,
384                    "injected_failure": true,
385                    "timestamp": chrono::Utc::now().to_rfc3339()
386                });
387
388                return Ok(PriorityResponse {
389                    source: ResponseSource::new(
390                        ResponsePriority::Fail,
391                        "failure_injection".to_string(),
392                    )
393                    .with_metadata("error_message".to_string(), error_message),
394                    status_code,
395                    headers: HashMap::new(),
396                    body: serde_json::to_string(&error_response)?.into_bytes(),
397                    content_type: "application/json".to_string(),
398                });
399            }
400        }
401
402        // Check if Reality Continuum is enabled and should blend responses
403        let should_blend = if let Some(ref continuum_engine) = self.continuum_engine {
404            continuum_engine.is_enabled().await
405        } else {
406            false
407        };
408
409        // 4. PROXY: Check if request should be proxied (respecting migration mode)
410        if let Some(ref proxy_handler) = self.proxy_handler {
411            // Check migration mode first
412            let migration_mode = if proxy_handler.config.migration_enabled {
413                proxy_handler.config.get_effective_migration_mode(uri.path())
414            } else {
415                None
416            };
417
418            // If migration mode is Mock, skip proxy and continue to mock generator
419            if let Some(crate::proxy::config::MigrationMode::Mock) = migration_mode {
420                // Force mock mode - skip proxy
421            } else if proxy_handler.config.should_proxy_with_condition(method, uri, headers, body) {
422                // Check if this is shadow mode (proxy + generate mock for comparison)
423                let is_shadow = proxy_handler.config.should_shadow(uri.path());
424
425                // If continuum is enabled, we need both mock and real responses
426                if should_blend {
427                    // Fetch both responses in parallel
428                    let proxy_future = proxy_handler.proxy_request(method, uri, headers, body);
429                    let mock_result = if let Some(ref mock_generator) = self.mock_generator {
430                        mock_generator.generate_mock_response(&fingerprint, headers, body)
431                    } else {
432                        Ok(None)
433                    };
434
435                    // Wait for proxy response
436                    let proxy_result = proxy_future.await;
437
438                    // Handle blending
439                    match (proxy_result, mock_result) {
440                        (Ok(proxy_response), Ok(Some(mock_response))) => {
441                            // Both succeeded - blend them
442                            if let Some(ref continuum_engine) = self.continuum_engine {
443                                let blend_ratio =
444                                    continuum_engine.get_blend_ratio(uri.path()).await;
445                                let blender = continuum_engine.blender();
446
447                                // Parse JSON bodies
448                                let mock_body_str = &mock_response.body;
449                                let real_body_bytes =
450                                    proxy_response.body.clone().unwrap_or_default();
451                                let real_body_str = String::from_utf8_lossy(&real_body_bytes);
452
453                                let mock_json: serde_json::Value =
454                                    serde_json::from_str(mock_body_str)
455                                        .unwrap_or_else(|_| serde_json::json!({}));
456                                let real_json: serde_json::Value =
457                                    serde_json::from_str(&real_body_str)
458                                        .unwrap_or_else(|_| serde_json::json!({}));
459
460                                // Blend the JSON responses
461                                let blended_json =
462                                    blender.blend_responses(&mock_json, &real_json, blend_ratio);
463                                let blended_body = serde_json::to_string(&blended_json)
464                                    .unwrap_or_else(|_| real_body_str.to_string());
465
466                                // Blend status codes
467                                let blended_status = blender.blend_status_code(
468                                    mock_response.status_code,
469                                    proxy_response.status_code,
470                                    blend_ratio,
471                                );
472
473                                // Blend headers
474                                let mut proxy_headers = HashMap::new();
475                                for (key, value) in proxy_response.headers.iter() {
476                                    if let Ok(value_str) = value.to_str() {
477                                        proxy_headers.insert(
478                                            key.as_str().to_string(),
479                                            value_str.to_string(),
480                                        );
481                                    }
482                                }
483                                let blended_headers = blender.blend_headers(
484                                    &mock_response.headers,
485                                    &proxy_headers,
486                                    blend_ratio,
487                                );
488
489                                let content_type = blended_headers
490                                    .get("content-type")
491                                    .cloned()
492                                    .or_else(|| {
493                                        proxy_response
494                                            .headers
495                                            .get("content-type")
496                                            .and_then(|v| v.to_str().ok())
497                                            .map(|s| s.to_string())
498                                    })
499                                    .unwrap_or_else(|| "application/json".to_string());
500
501                                tracing::info!(
502                                    path = %uri.path(),
503                                    blend_ratio = blend_ratio,
504                                    "Reality Continuum: blended mock and real responses"
505                                );
506
507                                let mut source = ResponseSource::new(
508                                    ResponsePriority::Proxy,
509                                    "continuum".to_string(),
510                                )
511                                .with_metadata("blend_ratio".to_string(), blend_ratio.to_string())
512                                .with_metadata(
513                                    "upstream_url".to_string(),
514                                    proxy_handler.config.get_upstream_url(uri.path()),
515                                );
516
517                                if let Some(mode) = migration_mode {
518                                    source = source.with_metadata(
519                                        "migration_mode".to_string(),
520                                        format!("{:?}", mode),
521                                    );
522                                }
523
524                                return Ok(PriorityResponse {
525                                    source,
526                                    status_code: blended_status,
527                                    headers: blended_headers,
528                                    body: blended_body.into_bytes(),
529                                    content_type,
530                                });
531                            }
532                        }
533                        (Ok(_proxy_response), Ok(None)) => {
534                            // Only proxy succeeded - use it (fallback behavior)
535                            tracing::debug!(
536                                path = %uri.path(),
537                                "Continuum: mock generation failed, using real response"
538                            );
539                            // Fall through to normal proxy handling
540                        }
541                        (Ok(_proxy_response), Err(_)) => {
542                            // Only proxy succeeded - use it (fallback behavior)
543                            tracing::debug!(
544                                path = %uri.path(),
545                                "Continuum: mock generation failed, using real response"
546                            );
547                            // Fall through to normal proxy handling
548                        }
549                        (Err(e), Ok(Some(mock_response))) => {
550                            // Only mock succeeded - use it (fallback behavior)
551                            tracing::debug!(
552                                path = %uri.path(),
553                                error = %e,
554                                "Continuum: proxy failed, using mock response"
555                            );
556                            // Fall through to normal mock handling below
557                            let mut source = ResponseSource::new(
558                                ResponsePriority::Mock,
559                                "continuum_fallback".to_string(),
560                            )
561                            .with_metadata("generated_from".to_string(), "openapi_spec".to_string())
562                            .with_metadata(
563                                "fallback_reason".to_string(),
564                                "proxy_failed".to_string(),
565                            );
566
567                            if let Some(mode) = migration_mode {
568                                source = source.with_metadata(
569                                    "migration_mode".to_string(),
570                                    format!("{:?}", mode),
571                                );
572                            }
573
574                            return Ok(PriorityResponse {
575                                source,
576                                status_code: mock_response.status_code,
577                                headers: mock_response.headers,
578                                body: mock_response.body.into_bytes(),
579                                content_type: mock_response.content_type,
580                            });
581                        }
582                        (Err(e), _) => {
583                            // Both failed
584                            tracing::warn!(
585                                path = %uri.path(),
586                                error = %e,
587                                "Continuum: both proxy and mock failed"
588                            );
589                            // If migration mode is Real, fail hard
590                            if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode
591                            {
592                                return Err(Error::generic(format!(
593                                    "Proxy request failed in real mode: {}",
594                                    e
595                                )));
596                            }
597                            // Continue to next handler
598                        }
599                    }
600                }
601
602                // Normal proxy handling (when continuum is not enabled or blending failed)
603                match proxy_handler.proxy_request(method, uri, headers, body).await {
604                    Ok(proxy_response) => {
605                        let mut response_headers = HashMap::new();
606                        for (key, value) in proxy_response.headers.iter() {
607                            let key_str = key.as_str();
608                            if let Ok(value_str) = value.to_str() {
609                                response_headers.insert(key_str.to_string(), value_str.to_string());
610                            }
611                        }
612
613                        let content_type = response_headers
614                            .get("content-type")
615                            .unwrap_or(&"application/json".to_string())
616                            .clone();
617
618                        // If shadow mode, also generate mock response for comparison
619                        if is_shadow {
620                            if let Some(ref mock_generator) = self.mock_generator {
621                                if let Ok(Some(mock_response)) = mock_generator
622                                    .generate_mock_response(&fingerprint, headers, body)
623                                {
624                                    // Log comparison between real and mock
625                                    tracing::info!(
626                                        path = %uri.path(),
627                                        real_status = proxy_response.status_code,
628                                        mock_status = mock_response.status_code,
629                                        "Shadow mode: comparing real and mock responses"
630                                    );
631
632                                    // Compare response bodies (basic comparison)
633                                    let real_body_bytes =
634                                        proxy_response.body.clone().unwrap_or_default();
635                                    let real_body = String::from_utf8_lossy(&real_body_bytes);
636                                    let mock_body = &mock_response.body;
637
638                                    if real_body != *mock_body {
639                                        tracing::warn!(
640                                            path = %uri.path(),
641                                            "Shadow mode: real and mock responses differ"
642                                        );
643                                    }
644                                }
645                            }
646                        }
647
648                        let mut source = ResponseSource::new(
649                            ResponsePriority::Proxy,
650                            if is_shadow {
651                                "shadow".to_string()
652                            } else {
653                                "proxy".to_string()
654                            },
655                        )
656                        .with_metadata(
657                            "upstream_url".to_string(),
658                            proxy_handler.config.get_upstream_url(uri.path()),
659                        );
660
661                        if let Some(mode) = migration_mode {
662                            source = source
663                                .with_metadata("migration_mode".to_string(), format!("{:?}", mode));
664                        }
665
666                        return Ok(PriorityResponse {
667                            source,
668                            status_code: proxy_response.status_code,
669                            headers: response_headers,
670                            body: proxy_response.body.unwrap_or_default(),
671                            content_type,
672                        });
673                    }
674                    Err(e) => {
675                        tracing::warn!("Proxy request failed: {}", e);
676                        // If migration mode is Real, fail hard (don't fall back to mock)
677                        if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode {
678                            return Err(Error::generic(format!(
679                                "Proxy request failed in real mode: {}",
680                                e
681                            )));
682                        }
683                        // Continue to next handler for other modes
684                    }
685                }
686            }
687        }
688
689        // 4. MOCK: Generate mock response from OpenAPI spec
690        if let Some(ref mock_generator) = self.mock_generator {
691            // Check if we're in mock mode (forced by migration)
692            let migration_mode = if let Some(ref proxy_handler) = self.proxy_handler {
693                if proxy_handler.config.migration_enabled {
694                    proxy_handler.config.get_effective_migration_mode(uri.path())
695                } else {
696                    None
697                }
698            } else {
699                None
700            };
701
702            if let Some(mock_response) =
703                mock_generator.generate_mock_response(&fingerprint, headers, body)?
704            {
705                let mut source = ResponseSource::new(ResponsePriority::Mock, "mock".to_string())
706                    .with_metadata("generated_from".to_string(), "openapi_spec".to_string());
707
708                if let Some(mode) = migration_mode {
709                    source =
710                        source.with_metadata("migration_mode".to_string(), format!("{:?}", mode));
711                }
712
713                return Ok(PriorityResponse {
714                    source,
715                    status_code: mock_response.status_code,
716                    headers: mock_response.headers,
717                    body: mock_response.body.into_bytes(),
718                    content_type: mock_response.content_type,
719                });
720            }
721        }
722
723        // 5. RECORD: Record the request for future replay
724        if self.record_replay.record_handler().should_record(method) {
725            // For now, return a default response and record it
726            let default_response = serde_json::json!({
727                "message": "Request recorded for future replay",
728                "timestamp": chrono::Utc::now().to_rfc3339(),
729                "fingerprint": fingerprint.to_hash()
730            });
731
732            let response_body = serde_json::to_string(&default_response)?;
733            let status_code = 200;
734
735            // Record the request
736            self.record_replay
737                .record_handler()
738                .record_request(&fingerprint, status_code, headers, &response_body, None)
739                .await?;
740
741            return Ok(PriorityResponse {
742                source: ResponseSource::new(ResponsePriority::Record, "record".to_string())
743                    .with_metadata("recorded".to_string(), "true".to_string()),
744                status_code,
745                headers: HashMap::new(),
746                body: response_body.into_bytes(),
747                content_type: "application/json".to_string(),
748            });
749        }
750
751        // If we reach here, no handler could process the request
752        Err(Error::generic("No handler could process the request".to_string()))
753    }
754
755    /// Apply behavioral economics rules to a response
756    ///
757    /// Updates condition evaluator with current metrics and evaluates rules,
758    /// then applies any matching actions to modify the response.
759    async fn apply_behavioral_economics(
760        &self,
761        response: PriorityResponse,
762        _method: &Method,
763        uri: &Uri,
764        latency_ms: Option<u64>,
765    ) -> Result<PriorityResponse> {
766        if let Some(ref engine) = self.behavioral_economics_engine {
767            let engine = engine.read().await;
768            let evaluator = engine.condition_evaluator();
769
770            // Update condition evaluator with current metrics
771            {
772                let mut eval = evaluator.write().await;
773                if let Some(latency) = latency_ms {
774                    eval.update_latency(uri.path(), latency);
775                }
776
777                // Update load and error rates
778                let endpoint = uri.path().to_string();
779                let mut metrics = self.request_metrics.write().await;
780                let now = std::time::Instant::now();
781
782                // Get or create metrics entry for this endpoint
783                let (request_count, error_count, last_request_time) =
784                    metrics.entry(endpoint.clone()).or_insert_with(|| (0, 0, now));
785
786                // Increment request count
787                *request_count += 1;
788
789                // Check if this is an error response (status >= 400)
790                if response.status_code >= 400 {
791                    *error_count += 1;
792                }
793
794                // Calculate error rate
795                let error_rate = if *request_count > 0 {
796                    *error_count as f64 / *request_count as f64
797                } else {
798                    0.0
799                };
800                eval.update_error_rate(&endpoint, error_rate);
801
802                // Calculate load (requests per second) based on time window
803                let time_elapsed = now.duration_since(*last_request_time).as_secs_f64();
804                if time_elapsed > 0.0 {
805                    let rps = *request_count as f64 / time_elapsed.max(1.0);
806                    eval.update_load(rps);
807                }
808
809                // Reset metrics periodically (every 60 seconds) to avoid unbounded growth
810                if time_elapsed > 60.0 {
811                    *request_count = 1;
812                    *error_count = if response.status_code >= 400 { 1 } else { 0 };
813                    *last_request_time = now;
814                } else {
815                    *last_request_time = now;
816                }
817            }
818
819            // Evaluate rules and get executed actions
820            let executed_actions = engine.evaluate().await?;
821
822            // Apply actions to response if any were executed
823            if !executed_actions.is_empty() {
824                tracing::debug!(
825                    "Behavioral economics engine executed {} actions",
826                    executed_actions.len()
827                );
828                // Actions are executed by the engine, but we may need to modify
829                // the response based on action results. For now, the engine
830                // handles action execution internally.
831            }
832        }
833
834        Ok(response)
835    }
836}
837
838/// Priority response
839#[derive(Debug, Clone)]
840pub struct PriorityResponse {
841    /// Response source information
842    pub source: ResponseSource,
843    /// HTTP status code
844    pub status_code: u16,
845    /// Response headers
846    pub headers: HashMap<String, String>,
847    /// Response body
848    pub body: Vec<u8>,
849    /// Content type
850    pub content_type: String,
851}
852
853impl PriorityResponse {
854    /// Convert to Axum response
855    pub fn to_axum_response(self) -> axum::response::Response {
856        let mut response = axum::response::Response::new(axum::body::Body::from(self.body));
857        *response.status_mut() = StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::OK);
858
859        // Add headers
860        for (key, value) in self.headers {
861            if let (Ok(header_name), Ok(header_value)) =
862                (key.parse::<axum::http::HeaderName>(), value.parse::<axum::http::HeaderValue>())
863            {
864                response.headers_mut().insert(header_name, header_value);
865            }
866        }
867
868        // Set content type if not already set
869        if !response.headers().contains_key("content-type") {
870            if let Ok(header_value) = self.content_type.parse::<axum::http::HeaderValue>() {
871                response.headers_mut().insert("content-type", header_value);
872            }
873        }
874
875        response
876    }
877}
878
879/// Simple mock generator for testing
880pub struct SimpleMockGenerator {
881    /// Default status code
882    pub default_status: u16,
883    /// Default response body
884    pub default_body: String,
885}
886
887impl SimpleMockGenerator {
888    /// Create a new simple mock generator
889    pub fn new(default_status: u16, default_body: String) -> Self {
890        Self {
891            default_status,
892            default_body,
893        }
894    }
895}
896
897impl MockGenerator for SimpleMockGenerator {
898    fn generate_mock_response(
899        &self,
900        _fingerprint: &RequestFingerprint,
901        _headers: &HeaderMap,
902        _body: Option<&[u8]>,
903    ) -> Result<Option<MockResponse>> {
904        Ok(Some(MockResponse {
905            status_code: self.default_status,
906            headers: HashMap::new(),
907            body: self.default_body.clone(),
908            content_type: "application/json".to_string(),
909        }))
910    }
911}
912
913#[cfg(test)]
914mod tests {
915    use super::*;
916    use tempfile::TempDir;
917
918    // Mock implementations for testing
919    struct MockRouteChaosInjector;
920
921    #[async_trait]
922    impl RouteChaosInjectorTrait for MockRouteChaosInjector {
923        async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
924            Ok(())
925        }
926
927        fn get_fault_response(&self, _method: &Method, _uri: &Uri) -> Option<RouteFaultResponse> {
928            Some(RouteFaultResponse {
929                status_code: 503,
930                error_message: "Service unavailable".to_string(),
931                fault_type: "test_fault".to_string(),
932            })
933        }
934    }
935
936    struct MockBehavioralScenarioReplay;
937
938    #[async_trait]
939    impl BehavioralScenarioReplay for MockBehavioralScenarioReplay {
940        async fn try_replay(
941            &self,
942            _method: &Method,
943            _uri: &Uri,
944            _headers: &HeaderMap,
945            _body: Option<&[u8]>,
946            _session_id: Option<&str>,
947        ) -> Result<Option<BehavioralReplayResponse>> {
948            Ok(Some(BehavioralReplayResponse {
949                status_code: 200,
950                headers: HashMap::new(),
951                body: b"scenario response".to_vec(),
952                timing_ms: Some(100),
953                content_type: "application/json".to_string(),
954            }))
955        }
956    }
957
958    #[tokio::test]
959    async fn test_priority_chain() {
960        let temp_dir = TempDir::new().unwrap();
961        let fixtures_dir = temp_dir.path().to_path_buf();
962
963        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
964        let mock_generator =
965            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock response"}"#.to_string()));
966
967        let handler = PriorityHttpHandler::new_with_openapi(
968            record_replay,
969            None, // No failure injection
970            None, // No proxy
971            Some(mock_generator),
972            None, // No OpenAPI spec
973        );
974
975        let method = Method::GET;
976        let uri = Uri::from_static("/api/test");
977        let headers = HeaderMap::new();
978
979        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
980
981        assert_eq!(response.status_code, 200);
982        assert_eq!(response.source.source_type, "mock");
983    }
984
985    #[tokio::test]
986    async fn test_builder_methods() {
987        let temp_dir = TempDir::new().unwrap();
988        let fixtures_dir = temp_dir.path().to_path_buf();
989        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
990        let mock_generator = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
991
992        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator));
993
994        // Test with_custom_fixture_loader
995        let custom_loader = Arc::new(CustomFixtureLoader::new(temp_dir.path().to_path_buf(), true));
996        let handler = handler.with_custom_fixture_loader(custom_loader);
997        assert!(handler.custom_fixture_loader.is_some());
998
999        // Test with_stateful_handler
1000        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1001        let handler = handler.with_stateful_handler(stateful_handler);
1002        assert!(handler.stateful_handler.is_some());
1003
1004        // Test with_route_chaos_injector
1005        let route_chaos = Arc::new(MockRouteChaosInjector);
1006        let handler = handler.with_route_chaos_injector(route_chaos);
1007        assert!(handler.route_chaos_injector.is_some());
1008
1009        // Test with_continuum_engine
1010        let continuum_engine = Arc::new(RealityContinuumEngine::new(
1011            crate::reality_continuum::config::ContinuumConfig::default(),
1012        ));
1013        let handler = handler.with_continuum_engine(continuum_engine);
1014        assert!(handler.continuum_engine.is_some());
1015
1016        // Test with_behavioral_economics_engine
1017        let behavioral_engine = Arc::new(RwLock::new(
1018            BehavioralEconomicsEngine::new(
1019                crate::behavioral_economics::config::BehavioralEconomicsConfig::default(),
1020            )
1021            .unwrap(),
1022        ));
1023        let handler = handler.with_behavioral_economics_engine(behavioral_engine);
1024        assert!(handler.behavioral_economics_engine.is_some());
1025
1026        // Test with_behavioral_scenario_replay
1027        let scenario_replay = Arc::new(MockBehavioralScenarioReplay);
1028        let handler = handler.with_behavioral_scenario_replay(scenario_replay);
1029        assert!(handler.behavioral_scenario_replay.is_some());
1030    }
1031
1032    #[tokio::test]
1033    async fn test_custom_fixture_priority() {
1034        let temp_dir = TempDir::new().unwrap();
1035        let fixtures_dir = temp_dir.path().to_path_buf();
1036        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1037        let custom_loader = Arc::new(CustomFixtureLoader::new(temp_dir.path().to_path_buf(), true));
1038
1039        // Create a custom fixture
1040        let fixture_path = temp_dir.path().join("custom_fixture.json");
1041        std::fs::write(
1042            &fixture_path,
1043            r#"{"status": 201, "response": {"message": "custom"}, "headers": {"x-custom": "value"}}"#,
1044        )
1045        .unwrap();
1046
1047        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1048            .with_custom_fixture_loader(custom_loader);
1049
1050        let _method = Method::GET;
1051        let _uri = Uri::from_static("/api/test");
1052        let _headers = HeaderMap::new();
1053
1054        // Custom fixture should be checked first, but won't match without proper fingerprint
1055        // This tests the custom fixture loader path
1056        let _handler = handler; // Handler is ready for custom fixture lookup
1057    }
1058
1059    #[tokio::test]
1060    async fn test_route_chaos_injection() {
1061        let temp_dir = TempDir::new().unwrap();
1062        let fixtures_dir = temp_dir.path().to_path_buf();
1063        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
1064        let route_chaos = Arc::new(MockRouteChaosInjector);
1065
1066        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1067            .with_route_chaos_injector(route_chaos);
1068
1069        let method = Method::GET;
1070        let uri = Uri::from_static("/api/test");
1071        let headers = HeaderMap::new();
1072
1073        let response = handler.process_request(&method, &uri, &headers, None).await;
1074
1075        // Should get fault response from route chaos injector
1076        if let Ok(resp) = response {
1077            assert_eq!(resp.status_code, 503);
1078            assert_eq!(resp.source.source_type, "route_fault_injection");
1079        }
1080    }
1081
1082    #[tokio::test]
1083    async fn test_behavioral_scenario_replay() {
1084        let temp_dir = TempDir::new().unwrap();
1085        let fixtures_dir = temp_dir.path().to_path_buf();
1086        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
1087        let scenario_replay = Arc::new(MockBehavioralScenarioReplay);
1088
1089        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1090            .with_behavioral_scenario_replay(scenario_replay);
1091
1092        let method = Method::GET;
1093        let uri = Uri::from_static("/api/test");
1094        let mut headers = HeaderMap::new();
1095        headers.insert("x-session-id", "test-session".parse().unwrap());
1096
1097        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1098
1099        assert_eq!(response.status_code, 200);
1100        assert_eq!(response.source.source_type, "behavioral_scenario");
1101        assert_eq!(response.body, b"scenario response");
1102    }
1103
1104    #[tokio::test]
1105    async fn test_priority_response_to_axum() {
1106        let response = PriorityResponse {
1107            source: ResponseSource::new(ResponsePriority::Mock, "test".to_string()),
1108            status_code: 201,
1109            headers: {
1110                let mut h = HashMap::new();
1111                h.insert("x-custom".to_string(), "value".to_string());
1112                h
1113            },
1114            body: b"test body".to_vec(),
1115            content_type: "application/json".to_string(),
1116        };
1117
1118        let axum_response = response.to_axum_response();
1119        assert_eq!(axum_response.status(), StatusCode::CREATED);
1120    }
1121
1122    #[tokio::test]
1123    async fn test_simple_mock_generator() {
1124        let generator = SimpleMockGenerator::new(404, r#"{"error": "not found"}"#.to_string());
1125        let fingerprint = RequestFingerprint::new(
1126            Method::GET,
1127            &Uri::from_static("/api/test"),
1128            &HeaderMap::new(),
1129            None,
1130        );
1131
1132        let response =
1133            generator.generate_mock_response(&fingerprint, &HeaderMap::new(), None).unwrap();
1134
1135        assert!(response.is_some());
1136        let mock_response = response.unwrap();
1137        assert_eq!(mock_response.status_code, 404);
1138        assert_eq!(mock_response.body, r#"{"error": "not found"}"#);
1139    }
1140
1141    #[tokio::test]
1142    async fn test_new_vs_new_with_openapi() {
1143        let temp_dir = TempDir::new().unwrap();
1144        let fixtures_dir = temp_dir.path().to_path_buf();
1145        let _record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1146        let _mock_generator = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
1147
1148        // Test new()
1149        let record_replay1 = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1150        let mock_generator1 = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
1151        let handler1 = PriorityHttpHandler::new(record_replay1, None, None, Some(mock_generator1));
1152        assert!(handler1.openapi_spec.is_none());
1153
1154        // Test new_with_openapi()
1155        let record_replay2 = RecordReplayHandler::new(fixtures_dir, true, true, false);
1156        let mock_generator2 = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
1157        let openapi_spec = crate::openapi::spec::OpenApiSpec::from_string(
1158            r#"openapi: 3.0.0
1159info:
1160  title: Test API
1161  version: 1.0.0
1162paths:
1163  /test:
1164    get:
1165      responses:
1166        '200':
1167          description: OK
1168"#,
1169            Some("yaml"),
1170        )
1171        .unwrap();
1172        let handler2 = PriorityHttpHandler::new_with_openapi(
1173            record_replay2,
1174            None,
1175            None,
1176            Some(mock_generator2),
1177            Some(openapi_spec),
1178        );
1179        assert!(handler2.openapi_spec.is_some());
1180    }
1181
1182    #[tokio::test]
1183    async fn test_custom_fixture_with_delay() {
1184        let temp_dir = TempDir::new().unwrap();
1185        let fixtures_dir = temp_dir.path().to_path_buf();
1186        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1187
1188        // Create a custom fixture with delay
1189        let fixture_content = r#"{
1190  "method": "GET",
1191  "path": "/api/test",
1192  "status": 200,
1193  "response": {"message": "delayed response"},
1194  "delay_ms": 10
1195}"#;
1196        let fixture_file = fixtures_dir.join("test.json");
1197        std::fs::write(&fixture_file, fixture_content).unwrap();
1198
1199        let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
1200        custom_loader.load_fixtures().await.unwrap();
1201
1202        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1203            .with_custom_fixture_loader(Arc::new(custom_loader));
1204
1205        let method = Method::GET;
1206        let uri = Uri::from_static("/api/test");
1207        let headers = HeaderMap::new();
1208
1209        let start = std::time::Instant::now();
1210        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1211        let elapsed = start.elapsed();
1212
1213        assert_eq!(response.status_code, 200);
1214        assert_eq!(response.source.source_type, "custom_fixture");
1215        assert!(elapsed.as_millis() >= 10); // Should have delay
1216    }
1217
1218    #[tokio::test]
1219    async fn test_custom_fixture_with_non_string_response() {
1220        let temp_dir = TempDir::new().unwrap();
1221        let fixtures_dir = temp_dir.path().to_path_buf();
1222        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1223
1224        // Create a custom fixture with object response (not string)
1225        let fixture_content = r#"{
1226  "method": "GET",
1227  "path": "/api/test",
1228  "status": 201,
1229  "response": {"id": 123, "name": "test"},
1230  "headers": {"content-type": "application/json"}
1231}"#;
1232        let fixture_file = fixtures_dir.join("test.json");
1233        std::fs::write(&fixture_file, fixture_content).unwrap();
1234
1235        let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
1236        custom_loader.load_fixtures().await.unwrap();
1237
1238        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1239            .with_custom_fixture_loader(Arc::new(custom_loader));
1240
1241        let method = Method::GET;
1242        let uri = Uri::from_static("/api/test");
1243        let headers = HeaderMap::new();
1244
1245        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1246
1247        assert_eq!(response.status_code, 201);
1248        assert_eq!(response.source.source_type, "custom_fixture");
1249        assert!(response.body.len() > 0);
1250        let body_str = String::from_utf8_lossy(&response.body);
1251        assert!(body_str.contains("id"));
1252    }
1253
1254    #[tokio::test]
1255    async fn test_custom_fixture_with_custom_content_type() {
1256        let temp_dir = TempDir::new().unwrap();
1257        let fixtures_dir = temp_dir.path().to_path_buf();
1258        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1259
1260        // Create a custom fixture with custom content-type
1261        let fixture_content = r#"{
1262  "method": "GET",
1263  "path": "/api/test",
1264  "status": 200,
1265  "response": "text response",
1266  "headers": {"content-type": "text/plain"}
1267}"#;
1268        let fixture_file = fixtures_dir.join("test.json");
1269        std::fs::write(&fixture_file, fixture_content).unwrap();
1270
1271        let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
1272        custom_loader.load_fixtures().await.unwrap();
1273
1274        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1275            .with_custom_fixture_loader(Arc::new(custom_loader));
1276
1277        let method = Method::GET;
1278        let uri = Uri::from_static("/api/test");
1279        let headers = HeaderMap::new();
1280
1281        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1282
1283        assert_eq!(response.status_code, 200);
1284        assert_eq!(response.content_type, "text/plain");
1285    }
1286
1287    #[tokio::test]
1288    async fn test_stateful_handler_path() {
1289        let temp_dir = TempDir::new().unwrap();
1290        let fixtures_dir = temp_dir.path().to_path_buf();
1291        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1292
1293        // Create a stateful handler that returns a response
1294        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1295
1296        // Add a stateful rule that matches our request
1297        // Note: This is a simplified test - in reality we'd need to set up stateful rules
1298        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1299            .with_stateful_handler(stateful_handler);
1300
1301        let method = Method::GET;
1302        let uri = Uri::from_static("/api/test");
1303        let headers = HeaderMap::new();
1304
1305        // Stateful handler might not match, so this will fall through to mock/record
1306        // But we're testing the stateful handler path is checked
1307        let _response = handler.process_request(&method, &uri, &headers, None).await;
1308        // This may error if no handler matches, which is expected
1309    }
1310
1311    #[tokio::test]
1312    async fn test_route_chaos_latency_injection() {
1313        let temp_dir = TempDir::new().unwrap();
1314        let fixtures_dir = temp_dir.path().to_path_buf();
1315        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1316
1317        // Create a route chaos injector that injects latency
1318        struct LatencyInjector;
1319        #[async_trait]
1320        impl RouteChaosInjectorTrait for LatencyInjector {
1321            async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
1322                tokio::time::sleep(tokio::time::Duration::from_millis(20)).await;
1323                Ok(())
1324            }
1325            fn get_fault_response(
1326                &self,
1327                _method: &Method,
1328                _uri: &Uri,
1329            ) -> Option<RouteFaultResponse> {
1330                None
1331            }
1332        }
1333
1334        let route_chaos = Arc::new(LatencyInjector);
1335        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1336            .with_route_chaos_injector(route_chaos);
1337
1338        let method = Method::GET;
1339        let uri = Uri::from_static("/api/test");
1340        let headers = HeaderMap::new();
1341
1342        let start = std::time::Instant::now();
1343        let _response = handler.process_request(&method, &uri, &headers, None).await;
1344        let elapsed = start.elapsed();
1345
1346        // Should have latency injected
1347        assert!(elapsed.as_millis() >= 20);
1348    }
1349
1350    #[tokio::test]
1351    async fn test_failure_injection_path() {
1352        let temp_dir = TempDir::new().unwrap();
1353        let fixtures_dir = temp_dir.path().to_path_buf();
1354        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1355
1356        // Create a failure injector that injects failures
1357        let mut failure_config = crate::failure_injection::FailureConfig::default();
1358        failure_config.global_error_rate = 1.0; // 100% error rate
1359        failure_config.default_status_codes = vec![500]; // Use 500 status code
1360
1361        let failure_injector = FailureInjector::new(Some(failure_config), true);
1362
1363        let openapi_spec = crate::openapi::spec::OpenApiSpec::from_string(
1364            r#"openapi: 3.0.0
1365info:
1366  title: Test API
1367  version: 1.0.0
1368paths:
1369  /api/test:
1370    get:
1371      tags: [test]
1372      responses:
1373        '200':
1374          description: OK
1375"#,
1376            Some("yaml"),
1377        )
1378        .unwrap();
1379
1380        let handler = PriorityHttpHandler::new_with_openapi(
1381            record_replay,
1382            Some(failure_injector),
1383            None,
1384            None,
1385            Some(openapi_spec),
1386        );
1387
1388        let method = Method::GET;
1389        let uri = Uri::from_static("/api/test");
1390        let headers = HeaderMap::new();
1391
1392        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1393
1394        assert_eq!(response.status_code, 500);
1395        assert_eq!(response.source.source_type, "failure_injection");
1396        let body_str = String::from_utf8_lossy(&response.body);
1397        assert!(body_str.contains("Injected failure")); // Default message
1398    }
1399
1400    #[tokio::test]
1401    async fn test_record_handler_path() {
1402        let temp_dir = TempDir::new().unwrap();
1403        let fixtures_dir = temp_dir.path().to_path_buf();
1404        // Create record_replay with recording enabled
1405        // Parameters: fixtures_dir, enable_replay, enable_record, auto_record
1406        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, true, true);
1407
1408        // Need a mock generator as fallback since record is last in chain
1409        let mock_generator =
1410            Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
1411        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator));
1412
1413        let method = Method::POST; // POST should be recorded
1414        let uri = Uri::from_static("/api/test");
1415        let headers = HeaderMap::new();
1416
1417        // This will hit mock generator, not record handler, since record is checked after mock
1418        // Let's test the record path by checking if recording happens
1419        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1420
1421        assert_eq!(response.status_code, 200);
1422        // Response will be from mock, but recording should have happened
1423        assert_eq!(response.source.source_type, "mock");
1424    }
1425
1426    #[tokio::test]
1427    async fn test_behavioral_economics_engine_path() {
1428        let temp_dir = TempDir::new().unwrap();
1429        let fixtures_dir = temp_dir.path().to_path_buf();
1430        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1431        let mock_generator =
1432            Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
1433
1434        let be_config = crate::behavioral_economics::config::BehavioralEconomicsConfig::default();
1435        let be_engine = Arc::new(RwLock::new(BehavioralEconomicsEngine::new(be_config).unwrap()));
1436
1437        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1438            .with_behavioral_economics_engine(be_engine);
1439
1440        let method = Method::GET;
1441        let uri = Uri::from_static("/api/test");
1442        let headers = HeaderMap::new();
1443
1444        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1445
1446        // Should go through behavioral economics engine processing
1447        assert_eq!(response.status_code, 200);
1448    }
1449
1450    #[tokio::test]
1451    async fn test_replay_handler_with_recorded_fixture() {
1452        let temp_dir = TempDir::new().unwrap();
1453        let fixtures_dir = temp_dir.path().to_path_buf();
1454        // Enable both replay and recording
1455        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1456
1457        let method = Method::GET;
1458        let uri = Uri::from_static("/api/test");
1459        let mut headers = HeaderMap::new();
1460        headers.insert("content-type", "application/json".parse().unwrap());
1461
1462        // First, record a request
1463        let fingerprint = RequestFingerprint::new(method.clone(), &uri, &headers, None);
1464        record_replay
1465            .record_handler()
1466            .record_request(
1467                &fingerprint,
1468                200,
1469                &headers,
1470                r#"{"message": "recorded response"}"#,
1471                None,
1472            )
1473            .await
1474            .unwrap();
1475
1476        // Create handler after recording
1477        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1478
1479        // Now replay it - should hit the replay path (lines 266-282)
1480        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1481
1482        assert_eq!(response.status_code, 200);
1483        assert_eq!(response.source.source_type, "replay");
1484        let body_str = String::from_utf8_lossy(&response.body);
1485        assert!(body_str.contains("recorded response"));
1486    }
1487
1488    #[tokio::test]
1489    async fn test_behavioral_scenario_replay_with_cookies() {
1490        let temp_dir = TempDir::new().unwrap();
1491        let fixtures_dir = temp_dir.path().to_path_buf();
1492        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1493
1494        // Create a scenario replay that extracts session ID from headers
1495        // Note: The current implementation checks x-session-id or session-id headers (lines 288-292)
1496        // Cookie parsing would need to be added separately
1497        struct CookieScenarioReplay;
1498        #[async_trait]
1499        impl BehavioralScenarioReplay for CookieScenarioReplay {
1500            async fn try_replay(
1501                &self,
1502                _method: &Method,
1503                _uri: &Uri,
1504                _headers: &HeaderMap,
1505                _body: Option<&[u8]>,
1506                session_id: Option<&str>,
1507            ) -> Result<Option<BehavioralReplayResponse>> {
1508                // Test that session_id is extracted from headers
1509                // The code checks x-session-id or session-id headers, not cookies
1510                if session_id == Some("header-session-123") {
1511                    Ok(Some(BehavioralReplayResponse {
1512                        status_code: 200,
1513                        headers: HashMap::new(),
1514                        body: b"header scenario response".to_vec(),
1515                        timing_ms: None,
1516                        content_type: "application/json".to_string(),
1517                    }))
1518                } else {
1519                    Ok(None)
1520                }
1521            }
1522        }
1523
1524        let scenario_replay = Arc::new(CookieScenarioReplay);
1525        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1526            .with_behavioral_scenario_replay(scenario_replay);
1527
1528        let method = Method::GET;
1529        let uri = Uri::from_static("/api/test");
1530        let mut headers = HeaderMap::new();
1531        // Set session-id header (lines 288-292 test header extraction)
1532        headers.insert("session-id", "header-session-123".parse().unwrap());
1533
1534        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1535
1536        assert_eq!(response.status_code, 200);
1537        assert_eq!(response.source.source_type, "behavioral_scenario");
1538        let body_str = String::from_utf8_lossy(&response.body);
1539        assert!(body_str.contains("header scenario"));
1540    }
1541
1542    #[tokio::test]
1543    async fn test_route_chaos_latency_error_handling() {
1544        let temp_dir = TempDir::new().unwrap();
1545        let fixtures_dir = temp_dir.path().to_path_buf();
1546        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1547
1548        // Create a route chaos injector that returns an error from inject_latency (line 337)
1549        struct ErrorLatencyInjector;
1550        #[async_trait]
1551        impl RouteChaosInjectorTrait for ErrorLatencyInjector {
1552            async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
1553                Err(Error::generic("Latency injection failed".to_string()))
1554            }
1555            fn get_fault_response(
1556                &self,
1557                _method: &Method,
1558                _uri: &Uri,
1559            ) -> Option<RouteFaultResponse> {
1560                None
1561            }
1562        }
1563
1564        let route_chaos = Arc::new(ErrorLatencyInjector);
1565        let mock_generator =
1566            Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
1567        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1568            .with_route_chaos_injector(route_chaos);
1569
1570        let method = Method::GET;
1571        let uri = Uri::from_static("/api/test");
1572        let headers = HeaderMap::new();
1573
1574        // Should handle the error gracefully and continue (line 337)
1575        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1576        assert_eq!(response.status_code, 200);
1577    }
1578
1579    #[tokio::test]
1580    async fn test_behavioral_scenario_replay_with_timing_delay() {
1581        let temp_dir = TempDir::new().unwrap();
1582        let fixtures_dir = temp_dir.path().to_path_buf();
1583        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1584
1585        // Create a scenario replay with timing delay (line 299-301)
1586        struct TimingScenarioReplay;
1587        #[async_trait]
1588        impl BehavioralScenarioReplay for TimingScenarioReplay {
1589            async fn try_replay(
1590                &self,
1591                _method: &Method,
1592                _uri: &Uri,
1593                _headers: &HeaderMap,
1594                _body: Option<&[u8]>,
1595                _session_id: Option<&str>,
1596            ) -> Result<Option<BehavioralReplayResponse>> {
1597                Ok(Some(BehavioralReplayResponse {
1598                    status_code: 200,
1599                    headers: HashMap::new(),
1600                    body: b"delayed response".to_vec(),
1601                    timing_ms: Some(15), // Timing delay
1602                    content_type: "application/json".to_string(),
1603                }))
1604            }
1605        }
1606
1607        let scenario_replay = Arc::new(TimingScenarioReplay);
1608        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1609            .with_behavioral_scenario_replay(scenario_replay);
1610
1611        let method = Method::GET;
1612        let uri = Uri::from_static("/api/test");
1613        let headers = HeaderMap::new();
1614
1615        let start = std::time::Instant::now();
1616        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1617        let elapsed = start.elapsed();
1618
1619        assert_eq!(response.status_code, 200);
1620        assert!(elapsed.as_millis() >= 15); // Should have timing delay (line 300)
1621    }
1622
1623    #[tokio::test]
1624    async fn test_stateful_handler_with_response() {
1625        let temp_dir = TempDir::new().unwrap();
1626        let fixtures_dir = temp_dir.path().to_path_buf();
1627        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1628
1629        // Create a stateful handler that actually returns a response (lines 318-329)
1630        // Note: This requires setting up stateful rules, which is complex
1631        // For now, we'll test that the path is checked even if no response is returned
1632        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1633        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1634            .with_stateful_handler(stateful_handler);
1635
1636        let method = Method::GET;
1637        let uri = Uri::from_static("/api/test");
1638        let headers = HeaderMap::new();
1639
1640        // Stateful handler path is checked (lines 317-330)
1641        // May not return a response if no rules match, but path is executed
1642        let _result = handler.process_request(&method, &uri, &headers, None).await;
1643        // Result may be error if no handler matches, which is expected
1644    }
1645
1646    #[tokio::test]
1647    async fn test_replay_handler_content_type_extraction() {
1648        let temp_dir = TempDir::new().unwrap();
1649        let fixtures_dir = temp_dir.path().to_path_buf();
1650        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1651
1652        let method = Method::GET;
1653        let uri = Uri::from_static("/api/test");
1654        let mut headers = HeaderMap::new();
1655        headers.insert("content-type", "application/xml".parse().unwrap());
1656
1657        // Record with custom content type
1658        let fingerprint = RequestFingerprint::new(method.clone(), &uri, &headers, None);
1659        record_replay
1660            .record_handler()
1661            .record_request(&fingerprint, 200, &headers, r#"<xml>test</xml>"#, None)
1662            .await
1663            .unwrap();
1664
1665        // Create handler after recording
1666        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1667
1668        // Replay should extract content type from recorded headers (lines 269-273)
1669        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1670        assert_eq!(response.content_type, "application/xml");
1671    }
1672
1673    #[tokio::test]
1674    async fn test_proxy_migration_mode_mock() {
1675        let temp_dir = TempDir::new().unwrap();
1676        let fixtures_dir = temp_dir.path().to_path_buf();
1677        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1678
1679        // Create proxy config with Mock migration mode (lines 402-410)
1680        let mut proxy_config =
1681            crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
1682        proxy_config.migration_enabled = true;
1683        proxy_config.rules.push(crate::proxy::config::ProxyRule {
1684            path_pattern: "/api/*".to_string(),
1685            target_url: "http://localhost:8080".to_string(),
1686            enabled: true,
1687            pattern: "/api/*".to_string(),
1688            upstream_url: "http://localhost:8080".to_string(),
1689            migration_mode: crate::proxy::config::MigrationMode::Mock, // Force mock mode
1690            migration_group: None,
1691            condition: None,
1692        });
1693
1694        let proxy_handler = ProxyHandler::new(proxy_config);
1695        let mock_generator =
1696            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1697
1698        let handler = PriorityHttpHandler::new(
1699            record_replay,
1700            None,
1701            Some(proxy_handler),
1702            Some(mock_generator),
1703        );
1704
1705        let method = Method::GET;
1706        let uri = Uri::from_static("/api/test");
1707        let headers = HeaderMap::new();
1708
1709        // Migration mode Mock should skip proxy and use mock (lines 409-410)
1710        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1711        assert_eq!(response.status_code, 200);
1712        assert_eq!(response.source.source_type, "mock");
1713    }
1714
1715    #[tokio::test]
1716    async fn test_proxy_migration_mode_disabled() {
1717        let temp_dir = TempDir::new().unwrap();
1718        let fixtures_dir = temp_dir.path().to_path_buf();
1719        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1720
1721        // Create proxy config with migration disabled (lines 402-406)
1722        let mut proxy_config =
1723            crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
1724        proxy_config.migration_enabled = false; // Migration disabled
1725        proxy_config.enabled = false; // Also disable proxy to avoid network calls
1726
1727        let proxy_handler = ProxyHandler::new(proxy_config);
1728        let mock_generator =
1729            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1730
1731        let handler = PriorityHttpHandler::new(
1732            record_replay,
1733            None,
1734            Some(proxy_handler),
1735            Some(mock_generator),
1736        );
1737
1738        let method = Method::GET;
1739        let uri = Uri::from_static("/api/test");
1740        let headers = HeaderMap::new();
1741
1742        // With migration disabled, should fall through to mock (line 405)
1743        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1744        assert_eq!(response.status_code, 200);
1745        assert_eq!(response.source.source_type, "mock");
1746    }
1747
1748    #[tokio::test]
1749    async fn test_continuum_engine_enabled_check() {
1750        let temp_dir = TempDir::new().unwrap();
1751        let fixtures_dir = temp_dir.path().to_path_buf();
1752        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1753
1754        // Create continuum engine (lines 393-397)
1755        let continuum_config = crate::reality_continuum::config::ContinuumConfig::new();
1756        let continuum_engine = Arc::new(RealityContinuumEngine::new(continuum_config));
1757        let mock_generator =
1758            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1759
1760        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1761            .with_continuum_engine(continuum_engine);
1762
1763        let method = Method::GET;
1764        let uri = Uri::from_static("/api/test");
1765        let headers = HeaderMap::new();
1766
1767        // Should check if continuum is enabled (line 394)
1768        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1769        assert_eq!(response.status_code, 200);
1770    }
1771
1772    #[tokio::test]
1773    async fn test_behavioral_scenario_replay_error_handling() {
1774        let temp_dir = TempDir::new().unwrap();
1775        let fixtures_dir = temp_dir.path().to_path_buf();
1776        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1777
1778        // Create a scenario replay that returns an error (lines 294-296)
1779        struct ErrorScenarioReplay;
1780        #[async_trait]
1781        impl BehavioralScenarioReplay for ErrorScenarioReplay {
1782            async fn try_replay(
1783                &self,
1784                _method: &Method,
1785                _uri: &Uri,
1786                _headers: &HeaderMap,
1787                _body: Option<&[u8]>,
1788                _session_id: Option<&str>,
1789            ) -> Result<Option<BehavioralReplayResponse>> {
1790                Err(Error::generic("Scenario replay error".to_string()))
1791            }
1792        }
1793
1794        let scenario_replay = Arc::new(ErrorScenarioReplay);
1795        let mock_generator =
1796            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1797        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1798            .with_behavioral_scenario_replay(scenario_replay);
1799
1800        let method = Method::GET;
1801        let uri = Uri::from_static("/api/test");
1802        let headers = HeaderMap::new();
1803
1804        // Error should be handled gracefully and fall through to mock
1805        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1806        assert_eq!(response.status_code, 200);
1807        assert_eq!(response.source.source_type, "mock");
1808    }
1809
1810    #[tokio::test]
1811    async fn test_behavioral_scenario_replay_with_session_id_header() {
1812        let temp_dir = TempDir::new().unwrap();
1813        let fixtures_dir = temp_dir.path().to_path_buf();
1814        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1815
1816        // Test session ID extraction from x-session-id header (lines 288-292)
1817        struct SessionScenarioReplay;
1818        #[async_trait]
1819        impl BehavioralScenarioReplay for SessionScenarioReplay {
1820            async fn try_replay(
1821                &self,
1822                _method: &Method,
1823                _uri: &Uri,
1824                _headers: &HeaderMap,
1825                _body: Option<&[u8]>,
1826                session_id: Option<&str>,
1827            ) -> Result<Option<BehavioralReplayResponse>> {
1828                if session_id == Some("header-session-456") {
1829                    Ok(Some(BehavioralReplayResponse {
1830                        status_code: 200,
1831                        headers: HashMap::new(),
1832                        body: b"header session response".to_vec(),
1833                        timing_ms: None,
1834                        content_type: "application/json".to_string(),
1835                    }))
1836                } else {
1837                    Ok(None)
1838                }
1839            }
1840        }
1841
1842        let scenario_replay = Arc::new(SessionScenarioReplay);
1843        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1844            .with_behavioral_scenario_replay(scenario_replay);
1845
1846        let method = Method::GET;
1847        let uri = Uri::from_static("/api/test");
1848        let mut headers = HeaderMap::new();
1849        headers.insert("x-session-id", "header-session-456".parse().unwrap());
1850
1851        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1852        assert_eq!(response.status_code, 200);
1853        assert_eq!(response.source.source_type, "behavioral_scenario");
1854    }
1855
1856    #[tokio::test]
1857    async fn test_stateful_handler_returns_response() {
1858        let temp_dir = TempDir::new().unwrap();
1859        let fixtures_dir = temp_dir.path().to_path_buf();
1860        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1861
1862        // Create a stateful handler with a config that matches our request (lines 318-329)
1863        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1864
1865        // Add a stateful config for /api/orders/{order_id}
1866        let mut state_responses = HashMap::new();
1867        state_responses.insert(
1868            "initial".to_string(),
1869            crate::stateful_handler::StateResponse {
1870                status_code: 200,
1871                headers: HashMap::new(),
1872                body_template: r#"{"status": "initial", "order_id": "123"}"#.to_string(),
1873                content_type: "application/json".to_string(),
1874            },
1875        );
1876
1877        let config = crate::stateful_handler::StatefulConfig {
1878            resource_id_extract: crate::stateful_handler::ResourceIdExtract::PathParam {
1879                param: "order_id".to_string(),
1880            },
1881            resource_type: "order".to_string(),
1882            state_responses,
1883            transitions: vec![],
1884        };
1885
1886        stateful_handler.add_config("/api/orders/{order_id}".to_string(), config).await;
1887
1888        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1889            .with_stateful_handler(stateful_handler);
1890
1891        let method = Method::GET;
1892        let uri = Uri::from_static("/api/orders/123");
1893        let headers = HeaderMap::new();
1894
1895        // Should hit stateful handler path (lines 318-329)
1896        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1897        assert_eq!(response.status_code, 200);
1898        assert_eq!(response.source.source_type, "stateful");
1899        assert_eq!(response.source.metadata.get("state"), Some(&"initial".to_string()));
1900        assert_eq!(response.source.metadata.get("resource_id"), Some(&"123".to_string()));
1901    }
1902
1903    #[tokio::test]
1904    async fn test_record_handler_path_with_no_other_handlers() {
1905        let temp_dir = TempDir::new().unwrap();
1906        let fixtures_dir = temp_dir.path().to_path_buf();
1907        // Create record_replay with recording enabled (lines 714-739)
1908        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, true, false);
1909
1910        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1911
1912        let method = Method::GET; // GET should be recorded when record_get_only is false
1913        let uri = Uri::from_static("/api/test");
1914        let headers = HeaderMap::new();
1915
1916        // Should hit record handler path (lines 714-739)
1917        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1918        assert_eq!(response.status_code, 200);
1919        assert_eq!(response.source.source_type, "record");
1920        let body_str = String::from_utf8_lossy(&response.body);
1921        assert!(body_str.contains("Request recorded"));
1922    }
1923
1924    #[tokio::test]
1925    async fn test_mock_generator_with_migration_mode() {
1926        let temp_dir = TempDir::new().unwrap();
1927        let fixtures_dir = temp_dir.path().to_path_buf();
1928        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1929
1930        // Create proxy config with Mock migration mode
1931        let mut proxy_config =
1932            crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
1933        proxy_config.migration_enabled = true;
1934        proxy_config.rules.push(crate::proxy::config::ProxyRule {
1935            path_pattern: "/api/*".to_string(),
1936            target_url: "http://localhost:8080".to_string(),
1937            enabled: true,
1938            pattern: "/api/*".to_string(),
1939            upstream_url: "http://localhost:8080".to_string(),
1940            migration_mode: crate::proxy::config::MigrationMode::Mock,
1941            migration_group: None,
1942            condition: None,
1943        });
1944        proxy_config.enabled = false; // Disable proxy to avoid network calls
1945
1946        let proxy_handler = ProxyHandler::new(proxy_config);
1947        let mock_generator = Box::new(SimpleMockGenerator::new(
1948            200,
1949            r#"{"message": "mock with migration"}"#.to_string(),
1950        ));
1951
1952        let handler = PriorityHttpHandler::new(
1953            record_replay,
1954            None,
1955            Some(proxy_handler),
1956            Some(mock_generator),
1957        );
1958
1959        let method = Method::GET;
1960        let uri = Uri::from_static("/api/test");
1961        let headers = HeaderMap::new();
1962
1963        // Migration mode Mock should skip proxy and use mock (lines 682-710)
1964        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1965        assert_eq!(response.status_code, 200);
1966        assert_eq!(response.source.source_type, "mock");
1967        let body_str = String::from_utf8_lossy(&response.body);
1968        assert!(body_str.contains("mock with migration"));
1969    }
1970
1971    #[tokio::test]
1972    async fn test_no_handler_can_process_request() {
1973        let temp_dir = TempDir::new().unwrap();
1974        let fixtures_dir = temp_dir.path().to_path_buf();
1975        // Create handler with no enabled handlers
1976        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, false, false);
1977        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1978
1979        let method = Method::GET;
1980        let uri = Uri::from_static("/api/test");
1981        let headers = HeaderMap::new();
1982
1983        // Should return error when no handler can process (line 742)
1984        let result = handler.process_request(&method, &uri, &headers, None).await;
1985        assert!(result.is_err());
1986        assert!(result.unwrap_err().to_string().contains("No handler could process"));
1987    }
1988
1989    #[tokio::test]
1990    async fn test_route_chaos_fault_injection() {
1991        let temp_dir = TempDir::new().unwrap();
1992        let fixtures_dir = temp_dir.path().to_path_buf();
1993        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1994
1995        // Create a route chaos injector that returns a fault response (lines 341-355)
1996        struct FaultInjector;
1997        #[async_trait]
1998        impl RouteChaosInjectorTrait for FaultInjector {
1999            async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
2000                Ok(())
2001            }
2002            fn get_fault_response(&self, method: &Method, uri: &Uri) -> Option<RouteFaultResponse> {
2003                if method == Method::GET && uri.path() == "/api/faulty" {
2004                    Some(RouteFaultResponse {
2005                        status_code: 503,
2006                        error_message: "Service unavailable".to_string(),
2007                        fault_type: "injected_fault".to_string(),
2008                    })
2009                } else {
2010                    None
2011                }
2012            }
2013        }
2014
2015        let route_chaos = Arc::new(FaultInjector);
2016        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
2017            .with_route_chaos_injector(route_chaos);
2018
2019        let method = Method::GET;
2020        let uri = Uri::from_static("/api/faulty");
2021        let headers = HeaderMap::new();
2022
2023        // Should return fault response (lines 341-355)
2024        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
2025        assert_eq!(response.status_code, 503);
2026        let body_str = String::from_utf8_lossy(&response.body);
2027        assert!(body_str.contains("Service unavailable"));
2028        assert!(body_str.contains("injected_failure"));
2029    }
2030}