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    #[allow(dead_code, clippy::type_complexity)]
97    request_metrics: Arc<RwLock<HashMap<String, (u64, u64, std::time::Instant)>>>,
98}
99
100/// Trait for mock response generation
101pub trait MockGenerator {
102    /// Generate a mock response for the given request
103    fn generate_mock_response(
104        &self,
105        fingerprint: &RequestFingerprint,
106        headers: &HeaderMap,
107        body: Option<&[u8]>,
108    ) -> Result<Option<MockResponse>>;
109}
110
111/// Mock response
112#[derive(Debug, Clone)]
113pub struct MockResponse {
114    /// Response status code
115    pub status_code: u16,
116    /// Response headers
117    pub headers: HashMap<String, String>,
118    /// Response body
119    pub body: String,
120    /// Content type
121    pub content_type: String,
122}
123
124impl PriorityHttpHandler {
125    /// Create a new priority HTTP handler
126    pub fn new(
127        record_replay: RecordReplayHandler,
128        failure_injector: Option<FailureInjector>,
129        proxy_handler: Option<ProxyHandler>,
130        mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
131    ) -> Self {
132        Self {
133            custom_fixture_loader: None,
134            record_replay,
135            behavioral_scenario_replay: None,
136            stateful_handler: None,
137            route_chaos_injector: None,
138            failure_injector,
139            proxy_handler,
140            mock_generator,
141            openapi_spec: None,
142            continuum_engine: None,
143            behavioral_economics_engine: None,
144            request_metrics: Arc::new(RwLock::new(HashMap::new())),
145        }
146    }
147
148    /// Create a new priority HTTP handler with OpenAPI spec
149    pub fn new_with_openapi(
150        record_replay: RecordReplayHandler,
151        failure_injector: Option<FailureInjector>,
152        proxy_handler: Option<ProxyHandler>,
153        mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
154        openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
155    ) -> Self {
156        Self {
157            custom_fixture_loader: None,
158            record_replay,
159            behavioral_scenario_replay: None,
160            stateful_handler: None,
161            route_chaos_injector: None,
162            failure_injector,
163            proxy_handler,
164            mock_generator,
165            openapi_spec,
166            continuum_engine: None,
167            behavioral_economics_engine: None,
168            request_metrics: Arc::new(RwLock::new(HashMap::new())),
169        }
170    }
171
172    /// Set custom fixture loader
173    pub fn with_custom_fixture_loader(mut self, loader: Arc<CustomFixtureLoader>) -> Self {
174        self.custom_fixture_loader = Some(loader);
175        self
176    }
177
178    /// Set stateful response handler
179    pub fn with_stateful_handler(mut self, handler: Arc<StatefulResponseHandler>) -> Self {
180        self.stateful_handler = Some(handler);
181        self
182    }
183
184    /// Set per-route chaos injector
185    pub fn with_route_chaos_injector(mut self, injector: Arc<dyn RouteChaosInjectorTrait>) -> Self {
186        self.route_chaos_injector = Some(injector);
187        self
188    }
189
190    /// Set Reality Continuum engine
191    pub fn with_continuum_engine(mut self, engine: Arc<RealityContinuumEngine>) -> Self {
192        self.continuum_engine = Some(engine);
193        self
194    }
195
196    /// Set Behavioral Economics Engine
197    pub fn with_behavioral_economics_engine(
198        mut self,
199        engine: Arc<RwLock<BehavioralEconomicsEngine>>,
200    ) -> Self {
201        self.behavioral_economics_engine = Some(engine);
202        self
203    }
204
205    /// Set behavioral scenario replay engine
206    pub fn with_behavioral_scenario_replay(
207        mut self,
208        replay_engine: Arc<dyn BehavioralScenarioReplay + Send + Sync>,
209    ) -> Self {
210        self.behavioral_scenario_replay = Some(replay_engine);
211        self
212    }
213
214    /// Process a request through the priority chain
215    pub async fn process_request(
216        &self,
217        method: &Method,
218        uri: &Uri,
219        headers: &HeaderMap,
220        body: Option<&[u8]>,
221    ) -> Result<PriorityResponse> {
222        // Normalize the URI path before creating fingerprint to match fixture normalization
223        // This ensures fixtures are matched correctly
224        let normalized_path = CustomFixtureLoader::normalize_path(uri.path());
225        let normalized_uri_str = if let Some(query) = uri.query() {
226            format!("{}?{}", normalized_path, query)
227        } else {
228            normalized_path
229        };
230        let normalized_uri = normalized_uri_str.parse::<Uri>().unwrap_or_else(|_| uri.clone());
231
232        let fingerprint = RequestFingerprint::new(method.clone(), &normalized_uri, headers, body);
233
234        // 0. CUSTOM FIXTURES: Check if we have a custom fixture (highest priority)
235        if let Some(ref custom_loader) = self.custom_fixture_loader {
236            if let Some(custom_fixture) = custom_loader.load_fixture(&fingerprint) {
237                // Apply delay if specified
238                if custom_fixture.delay_ms > 0 {
239                    tokio::time::sleep(tokio::time::Duration::from_millis(custom_fixture.delay_ms))
240                        .await;
241                }
242
243                // Convert response to JSON string if it's not already a string
244                let response_body = match custom_fixture.response.as_str() {
245                    Some(s) => s.to_string(),
246                    None => 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    #[allow(dead_code)]
760    async fn apply_behavioral_economics(
761        &self,
762        response: PriorityResponse,
763        _method: &Method,
764        uri: &Uri,
765        latency_ms: Option<u64>,
766    ) -> Result<PriorityResponse> {
767        if let Some(ref engine) = self.behavioral_economics_engine {
768            let engine = engine.read().await;
769            let evaluator = engine.condition_evaluator();
770
771            // Update condition evaluator with current metrics
772            {
773                let mut eval = evaluator.write().await;
774                if let Some(latency) = latency_ms {
775                    eval.update_latency(uri.path(), latency);
776                }
777
778                // Update load and error rates
779                let endpoint = uri.path().to_string();
780                let mut metrics = self.request_metrics.write().await;
781                let now = std::time::Instant::now();
782
783                // Get or create metrics entry for this endpoint
784                let (request_count, error_count, last_request_time) =
785                    metrics.entry(endpoint.clone()).or_insert_with(|| (0, 0, now));
786
787                // Increment request count
788                *request_count += 1;
789
790                // Check if this is an error response (status >= 400)
791                if response.status_code >= 400 {
792                    *error_count += 1;
793                }
794
795                // Calculate error rate
796                let error_rate = if *request_count > 0 {
797                    *error_count as f64 / *request_count as f64
798                } else {
799                    0.0
800                };
801                eval.update_error_rate(&endpoint, error_rate);
802
803                // Calculate load (requests per second) based on time window
804                let time_elapsed = now.duration_since(*last_request_time).as_secs_f64();
805                if time_elapsed > 0.0 {
806                    let rps = *request_count as f64 / time_elapsed.max(1.0);
807                    eval.update_load(rps);
808                }
809
810                // Reset metrics periodically (every 60 seconds) to avoid unbounded growth
811                if time_elapsed > 60.0 {
812                    *request_count = 1;
813                    *error_count = if response.status_code >= 400 { 1 } else { 0 };
814                    *last_request_time = now;
815                } else {
816                    *last_request_time = now;
817                }
818            }
819
820            // Evaluate rules and get executed actions
821            let executed_actions = engine.evaluate().await?;
822
823            // Apply actions to response if any were executed
824            if !executed_actions.is_empty() {
825                tracing::debug!(
826                    "Behavioral economics engine executed {} actions",
827                    executed_actions.len()
828                );
829                // Actions are executed by the engine, but we may need to modify
830                // the response based on action results. For now, the engine
831                // handles action execution internally.
832            }
833        }
834
835        Ok(response)
836    }
837}
838
839/// Priority response
840#[derive(Debug, Clone)]
841pub struct PriorityResponse {
842    /// Response source information
843    pub source: ResponseSource,
844    /// HTTP status code
845    pub status_code: u16,
846    /// Response headers
847    pub headers: HashMap<String, String>,
848    /// Response body
849    pub body: Vec<u8>,
850    /// Content type
851    pub content_type: String,
852}
853
854impl PriorityResponse {
855    /// Convert to Axum response
856    pub fn to_axum_response(self) -> axum::response::Response {
857        let mut response = axum::response::Response::new(axum::body::Body::from(self.body));
858        *response.status_mut() = StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::OK);
859
860        // Add headers
861        for (key, value) in self.headers {
862            if let (Ok(header_name), Ok(header_value)) =
863                (key.parse::<axum::http::HeaderName>(), value.parse::<axum::http::HeaderValue>())
864            {
865                response.headers_mut().insert(header_name, header_value);
866            }
867        }
868
869        // Set content type if not already set
870        if !response.headers().contains_key("content-type") {
871            if let Ok(header_value) = self.content_type.parse::<axum::http::HeaderValue>() {
872                response.headers_mut().insert("content-type", header_value);
873            }
874        }
875
876        response
877    }
878}
879
880/// Simple mock generator for testing
881pub struct SimpleMockGenerator {
882    /// Default status code
883    pub default_status: u16,
884    /// Default response body
885    pub default_body: String,
886}
887
888impl SimpleMockGenerator {
889    /// Create a new simple mock generator
890    pub fn new(default_status: u16, default_body: String) -> Self {
891        Self {
892            default_status,
893            default_body,
894        }
895    }
896}
897
898impl MockGenerator for SimpleMockGenerator {
899    fn generate_mock_response(
900        &self,
901        _fingerprint: &RequestFingerprint,
902        _headers: &HeaderMap,
903        _body: Option<&[u8]>,
904    ) -> Result<Option<MockResponse>> {
905        Ok(Some(MockResponse {
906            status_code: self.default_status,
907            headers: HashMap::new(),
908            body: self.default_body.clone(),
909            content_type: "application/json".to_string(),
910        }))
911    }
912}
913
914#[cfg(test)]
915mod tests {
916    use super::*;
917    use tempfile::TempDir;
918
919    // Mock implementations for testing
920    struct MockRouteChaosInjector;
921
922    #[async_trait]
923    impl RouteChaosInjectorTrait for MockRouteChaosInjector {
924        async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
925            Ok(())
926        }
927
928        fn get_fault_response(&self, _method: &Method, _uri: &Uri) -> Option<RouteFaultResponse> {
929            Some(RouteFaultResponse {
930                status_code: 503,
931                error_message: "Service unavailable".to_string(),
932                fault_type: "test_fault".to_string(),
933            })
934        }
935    }
936
937    struct MockBehavioralScenarioReplay;
938
939    #[async_trait]
940    impl BehavioralScenarioReplay for MockBehavioralScenarioReplay {
941        async fn try_replay(
942            &self,
943            _method: &Method,
944            _uri: &Uri,
945            _headers: &HeaderMap,
946            _body: Option<&[u8]>,
947            _session_id: Option<&str>,
948        ) -> Result<Option<BehavioralReplayResponse>> {
949            Ok(Some(BehavioralReplayResponse {
950                status_code: 200,
951                headers: HashMap::new(),
952                body: b"scenario response".to_vec(),
953                timing_ms: Some(100),
954                content_type: "application/json".to_string(),
955            }))
956        }
957    }
958
959    #[tokio::test]
960    async fn test_priority_chain() {
961        let temp_dir = TempDir::new().unwrap();
962        let fixtures_dir = temp_dir.path().to_path_buf();
963
964        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
965        let mock_generator =
966            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock response"}"#.to_string()));
967
968        let handler = PriorityHttpHandler::new_with_openapi(
969            record_replay,
970            None, // No failure injection
971            None, // No proxy
972            Some(mock_generator),
973            None, // No OpenAPI spec
974        );
975
976        let method = Method::GET;
977        let uri = Uri::from_static("/api/test");
978        let headers = HeaderMap::new();
979
980        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
981
982        assert_eq!(response.status_code, 200);
983        assert_eq!(response.source.source_type, "mock");
984    }
985
986    #[tokio::test]
987    async fn test_builder_methods() {
988        let temp_dir = TempDir::new().unwrap();
989        let fixtures_dir = temp_dir.path().to_path_buf();
990        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
991        let mock_generator = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
992
993        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator));
994
995        // Test with_custom_fixture_loader
996        let custom_loader = Arc::new(CustomFixtureLoader::new(temp_dir.path().to_path_buf(), true));
997        let handler = handler.with_custom_fixture_loader(custom_loader);
998        assert!(handler.custom_fixture_loader.is_some());
999
1000        // Test with_stateful_handler
1001        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1002        let handler = handler.with_stateful_handler(stateful_handler);
1003        assert!(handler.stateful_handler.is_some());
1004
1005        // Test with_route_chaos_injector
1006        let route_chaos = Arc::new(MockRouteChaosInjector);
1007        let handler = handler.with_route_chaos_injector(route_chaos);
1008        assert!(handler.route_chaos_injector.is_some());
1009
1010        // Test with_continuum_engine
1011        let continuum_engine = Arc::new(RealityContinuumEngine::new(
1012            crate::reality_continuum::config::ContinuumConfig::default(),
1013        ));
1014        let handler = handler.with_continuum_engine(continuum_engine);
1015        assert!(handler.continuum_engine.is_some());
1016
1017        // Test with_behavioral_economics_engine
1018        let behavioral_engine = Arc::new(RwLock::new(
1019            BehavioralEconomicsEngine::new(
1020                crate::behavioral_economics::config::BehavioralEconomicsConfig::default(),
1021            )
1022            .unwrap(),
1023        ));
1024        let handler = handler.with_behavioral_economics_engine(behavioral_engine);
1025        assert!(handler.behavioral_economics_engine.is_some());
1026
1027        // Test with_behavioral_scenario_replay
1028        let scenario_replay = Arc::new(MockBehavioralScenarioReplay);
1029        let handler = handler.with_behavioral_scenario_replay(scenario_replay);
1030        assert!(handler.behavioral_scenario_replay.is_some());
1031    }
1032
1033    #[tokio::test]
1034    async fn test_custom_fixture_priority() {
1035        let temp_dir = TempDir::new().unwrap();
1036        let fixtures_dir = temp_dir.path().to_path_buf();
1037        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1038        let custom_loader = Arc::new(CustomFixtureLoader::new(temp_dir.path().to_path_buf(), true));
1039
1040        // Create a custom fixture
1041        let fixture_path = temp_dir.path().join("custom_fixture.json");
1042        std::fs::write(
1043            &fixture_path,
1044            r#"{"status": 201, "response": {"message": "custom"}, "headers": {"x-custom": "value"}}"#,
1045        )
1046        .unwrap();
1047
1048        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1049            .with_custom_fixture_loader(custom_loader);
1050
1051        let _method = Method::GET;
1052        let _uri = Uri::from_static("/api/test");
1053        let _headers = HeaderMap::new();
1054
1055        // Custom fixture should be checked first, but won't match without proper fingerprint
1056        // This tests the custom fixture loader path
1057        let _handler = handler; // Handler is ready for custom fixture lookup
1058    }
1059
1060    #[tokio::test]
1061    async fn test_route_chaos_injection() {
1062        let temp_dir = TempDir::new().unwrap();
1063        let fixtures_dir = temp_dir.path().to_path_buf();
1064        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
1065        let route_chaos = Arc::new(MockRouteChaosInjector);
1066
1067        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1068            .with_route_chaos_injector(route_chaos);
1069
1070        let method = Method::GET;
1071        let uri = Uri::from_static("/api/test");
1072        let headers = HeaderMap::new();
1073
1074        let response = handler.process_request(&method, &uri, &headers, None).await;
1075
1076        // Should get fault response from route chaos injector
1077        if let Ok(resp) = response {
1078            assert_eq!(resp.status_code, 503);
1079            assert_eq!(resp.source.source_type, "route_fault_injection");
1080        }
1081    }
1082
1083    #[tokio::test]
1084    async fn test_behavioral_scenario_replay() {
1085        let temp_dir = TempDir::new().unwrap();
1086        let fixtures_dir = temp_dir.path().to_path_buf();
1087        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
1088        let scenario_replay = Arc::new(MockBehavioralScenarioReplay);
1089
1090        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1091            .with_behavioral_scenario_replay(scenario_replay);
1092
1093        let method = Method::GET;
1094        let uri = Uri::from_static("/api/test");
1095        let mut headers = HeaderMap::new();
1096        headers.insert("x-session-id", "test-session".parse().unwrap());
1097
1098        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1099
1100        assert_eq!(response.status_code, 200);
1101        assert_eq!(response.source.source_type, "behavioral_scenario");
1102        assert_eq!(response.body, b"scenario response");
1103    }
1104
1105    #[tokio::test]
1106    async fn test_priority_response_to_axum() {
1107        let response = PriorityResponse {
1108            source: ResponseSource::new(ResponsePriority::Mock, "test".to_string()),
1109            status_code: 201,
1110            headers: {
1111                let mut h = HashMap::new();
1112                h.insert("x-custom".to_string(), "value".to_string());
1113                h
1114            },
1115            body: b"test body".to_vec(),
1116            content_type: "application/json".to_string(),
1117        };
1118
1119        let axum_response = response.to_axum_response();
1120        assert_eq!(axum_response.status(), StatusCode::CREATED);
1121    }
1122
1123    #[tokio::test]
1124    async fn test_simple_mock_generator() {
1125        let generator = SimpleMockGenerator::new(404, r#"{"error": "not found"}"#.to_string());
1126        let fingerprint = RequestFingerprint::new(
1127            Method::GET,
1128            &Uri::from_static("/api/test"),
1129            &HeaderMap::new(),
1130            None,
1131        );
1132
1133        let response =
1134            generator.generate_mock_response(&fingerprint, &HeaderMap::new(), None).unwrap();
1135
1136        assert!(response.is_some());
1137        let mock_response = response.unwrap();
1138        assert_eq!(mock_response.status_code, 404);
1139        assert_eq!(mock_response.body, r#"{"error": "not found"}"#);
1140    }
1141
1142    #[tokio::test]
1143    async fn test_new_vs_new_with_openapi() {
1144        let temp_dir = TempDir::new().unwrap();
1145        let fixtures_dir = temp_dir.path().to_path_buf();
1146        let _record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1147        let _mock_generator = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
1148
1149        // Test new()
1150        let record_replay1 = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1151        let mock_generator1 = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
1152        let handler1 = PriorityHttpHandler::new(record_replay1, None, None, Some(mock_generator1));
1153        assert!(handler1.openapi_spec.is_none());
1154
1155        // Test new_with_openapi()
1156        let record_replay2 = RecordReplayHandler::new(fixtures_dir, true, true, false);
1157        let mock_generator2 = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
1158        let openapi_spec = crate::openapi::spec::OpenApiSpec::from_string(
1159            r#"openapi: 3.0.0
1160info:
1161  title: Test API
1162  version: 1.0.0
1163paths:
1164  /test:
1165    get:
1166      responses:
1167        '200':
1168          description: OK
1169"#,
1170            Some("yaml"),
1171        )
1172        .unwrap();
1173        let handler2 = PriorityHttpHandler::new_with_openapi(
1174            record_replay2,
1175            None,
1176            None,
1177            Some(mock_generator2),
1178            Some(openapi_spec),
1179        );
1180        assert!(handler2.openapi_spec.is_some());
1181    }
1182
1183    #[tokio::test]
1184    async fn test_custom_fixture_with_delay() {
1185        let temp_dir = TempDir::new().unwrap();
1186        let fixtures_dir = temp_dir.path().to_path_buf();
1187        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1188
1189        // Create a custom fixture with delay
1190        let fixture_content = r#"{
1191  "method": "GET",
1192  "path": "/api/test",
1193  "status": 200,
1194  "response": {"message": "delayed response"},
1195  "delay_ms": 10
1196}"#;
1197        let fixture_file = fixtures_dir.join("test.json");
1198        std::fs::write(&fixture_file, fixture_content).unwrap();
1199
1200        let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
1201        custom_loader.load_fixtures().await.unwrap();
1202
1203        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1204            .with_custom_fixture_loader(Arc::new(custom_loader));
1205
1206        let method = Method::GET;
1207        let uri = Uri::from_static("/api/test");
1208        let headers = HeaderMap::new();
1209
1210        let start = std::time::Instant::now();
1211        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1212        let elapsed = start.elapsed();
1213
1214        assert_eq!(response.status_code, 200);
1215        assert_eq!(response.source.source_type, "custom_fixture");
1216        assert!(elapsed.as_millis() >= 10); // Should have delay
1217    }
1218
1219    #[tokio::test]
1220    async fn test_custom_fixture_with_non_string_response() {
1221        let temp_dir = TempDir::new().unwrap();
1222        let fixtures_dir = temp_dir.path().to_path_buf();
1223        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1224
1225        // Create a custom fixture with object response (not string)
1226        let fixture_content = r#"{
1227  "method": "GET",
1228  "path": "/api/test",
1229  "status": 201,
1230  "response": {"id": 123, "name": "test"},
1231  "headers": {"content-type": "application/json"}
1232}"#;
1233        let fixture_file = fixtures_dir.join("test.json");
1234        std::fs::write(&fixture_file, fixture_content).unwrap();
1235
1236        let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
1237        custom_loader.load_fixtures().await.unwrap();
1238
1239        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1240            .with_custom_fixture_loader(Arc::new(custom_loader));
1241
1242        let method = Method::GET;
1243        let uri = Uri::from_static("/api/test");
1244        let headers = HeaderMap::new();
1245
1246        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1247
1248        assert_eq!(response.status_code, 201);
1249        assert_eq!(response.source.source_type, "custom_fixture");
1250        assert!(!response.body.is_empty());
1251        let body_str = String::from_utf8_lossy(&response.body);
1252        assert!(body_str.contains("id"));
1253    }
1254
1255    #[tokio::test]
1256    async fn test_custom_fixture_with_custom_content_type() {
1257        let temp_dir = TempDir::new().unwrap();
1258        let fixtures_dir = temp_dir.path().to_path_buf();
1259        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1260
1261        // Create a custom fixture with custom content-type
1262        let fixture_content = r#"{
1263  "method": "GET",
1264  "path": "/api/test",
1265  "status": 200,
1266  "response": "text response",
1267  "headers": {"content-type": "text/plain"}
1268}"#;
1269        let fixture_file = fixtures_dir.join("test.json");
1270        std::fs::write(&fixture_file, fixture_content).unwrap();
1271
1272        let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
1273        custom_loader.load_fixtures().await.unwrap();
1274
1275        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1276            .with_custom_fixture_loader(Arc::new(custom_loader));
1277
1278        let method = Method::GET;
1279        let uri = Uri::from_static("/api/test");
1280        let headers = HeaderMap::new();
1281
1282        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1283
1284        assert_eq!(response.status_code, 200);
1285        assert_eq!(response.content_type, "text/plain");
1286    }
1287
1288    #[tokio::test]
1289    async fn test_stateful_handler_path() {
1290        let temp_dir = TempDir::new().unwrap();
1291        let fixtures_dir = temp_dir.path().to_path_buf();
1292        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1293
1294        // Create a stateful handler that returns a response
1295        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1296
1297        // Add a stateful rule that matches our request
1298        // Note: This is a simplified test - in reality we'd need to set up stateful rules
1299        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1300            .with_stateful_handler(stateful_handler);
1301
1302        let method = Method::GET;
1303        let uri = Uri::from_static("/api/test");
1304        let headers = HeaderMap::new();
1305
1306        // Stateful handler might not match, so this will fall through to mock/record
1307        // But we're testing the stateful handler path is checked
1308        let _response = handler.process_request(&method, &uri, &headers, None).await;
1309        // This may error if no handler matches, which is expected
1310    }
1311
1312    #[tokio::test]
1313    async fn test_route_chaos_latency_injection() {
1314        let temp_dir = TempDir::new().unwrap();
1315        let fixtures_dir = temp_dir.path().to_path_buf();
1316        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1317
1318        // Create a route chaos injector that injects latency
1319        struct LatencyInjector;
1320        #[async_trait]
1321        impl RouteChaosInjectorTrait for LatencyInjector {
1322            async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
1323                tokio::time::sleep(tokio::time::Duration::from_millis(20)).await;
1324                Ok(())
1325            }
1326            fn get_fault_response(
1327                &self,
1328                _method: &Method,
1329                _uri: &Uri,
1330            ) -> Option<RouteFaultResponse> {
1331                None
1332            }
1333        }
1334
1335        let route_chaos = Arc::new(LatencyInjector);
1336        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1337            .with_route_chaos_injector(route_chaos);
1338
1339        let method = Method::GET;
1340        let uri = Uri::from_static("/api/test");
1341        let headers = HeaderMap::new();
1342
1343        let start = std::time::Instant::now();
1344        let _response = handler.process_request(&method, &uri, &headers, None).await;
1345        let elapsed = start.elapsed();
1346
1347        // Should have latency injected
1348        assert!(elapsed.as_millis() >= 20);
1349    }
1350
1351    #[tokio::test]
1352    async fn test_failure_injection_path() {
1353        let temp_dir = TempDir::new().unwrap();
1354        let fixtures_dir = temp_dir.path().to_path_buf();
1355        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1356
1357        // Create a failure injector that injects failures
1358        let failure_config = crate::failure_injection::FailureConfig {
1359            global_error_rate: 1.0,          // 100% error rate
1360            default_status_codes: vec![500], // Use 500 status code
1361            ..Default::default()
1362        };
1363
1364        let failure_injector = FailureInjector::new(Some(failure_config), true);
1365
1366        let openapi_spec = crate::openapi::spec::OpenApiSpec::from_string(
1367            r#"openapi: 3.0.0
1368info:
1369  title: Test API
1370  version: 1.0.0
1371paths:
1372  /api/test:
1373    get:
1374      tags: [test]
1375      responses:
1376        '200':
1377          description: OK
1378"#,
1379            Some("yaml"),
1380        )
1381        .unwrap();
1382
1383        let handler = PriorityHttpHandler::new_with_openapi(
1384            record_replay,
1385            Some(failure_injector),
1386            None,
1387            None,
1388            Some(openapi_spec),
1389        );
1390
1391        let method = Method::GET;
1392        let uri = Uri::from_static("/api/test");
1393        let headers = HeaderMap::new();
1394
1395        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1396
1397        assert_eq!(response.status_code, 500);
1398        assert_eq!(response.source.source_type, "failure_injection");
1399        let body_str = String::from_utf8_lossy(&response.body);
1400        assert!(body_str.contains("Injected failure")); // Default message
1401    }
1402
1403    #[tokio::test]
1404    async fn test_record_handler_path() {
1405        let temp_dir = TempDir::new().unwrap();
1406        let fixtures_dir = temp_dir.path().to_path_buf();
1407        // Create record_replay with recording enabled
1408        // Parameters: fixtures_dir, enable_replay, enable_record, auto_record
1409        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, true, true);
1410
1411        // Need a mock generator as fallback since record is last in chain
1412        let mock_generator =
1413            Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
1414        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator));
1415
1416        let method = Method::POST; // POST should be recorded
1417        let uri = Uri::from_static("/api/test");
1418        let headers = HeaderMap::new();
1419
1420        // This will hit mock generator, not record handler, since record is checked after mock
1421        // Let's test the record path by checking if recording happens
1422        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1423
1424        assert_eq!(response.status_code, 200);
1425        // Response will be from mock, but recording should have happened
1426        assert_eq!(response.source.source_type, "mock");
1427    }
1428
1429    #[tokio::test]
1430    async fn test_behavioral_economics_engine_path() {
1431        let temp_dir = TempDir::new().unwrap();
1432        let fixtures_dir = temp_dir.path().to_path_buf();
1433        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1434        let mock_generator =
1435            Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
1436
1437        let be_config = crate::behavioral_economics::config::BehavioralEconomicsConfig::default();
1438        let be_engine = Arc::new(RwLock::new(BehavioralEconomicsEngine::new(be_config).unwrap()));
1439
1440        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1441            .with_behavioral_economics_engine(be_engine);
1442
1443        let method = Method::GET;
1444        let uri = Uri::from_static("/api/test");
1445        let headers = HeaderMap::new();
1446
1447        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1448
1449        // Should go through behavioral economics engine processing
1450        assert_eq!(response.status_code, 200);
1451    }
1452
1453    #[tokio::test]
1454    async fn test_replay_handler_with_recorded_fixture() {
1455        let temp_dir = TempDir::new().unwrap();
1456        let fixtures_dir = temp_dir.path().to_path_buf();
1457        // Enable both replay and recording
1458        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1459
1460        let method = Method::GET;
1461        let uri = Uri::from_static("/api/test");
1462        let mut headers = HeaderMap::new();
1463        headers.insert("content-type", "application/json".parse().unwrap());
1464
1465        // First, record a request
1466        let fingerprint = RequestFingerprint::new(method.clone(), &uri, &headers, None);
1467        record_replay
1468            .record_handler()
1469            .record_request(
1470                &fingerprint,
1471                200,
1472                &headers,
1473                r#"{"message": "recorded response"}"#,
1474                None,
1475            )
1476            .await
1477            .unwrap();
1478
1479        // Create handler after recording
1480        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1481
1482        // Now replay it - should hit the replay path (lines 266-282)
1483        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1484
1485        assert_eq!(response.status_code, 200);
1486        assert_eq!(response.source.source_type, "replay");
1487        let body_str = String::from_utf8_lossy(&response.body);
1488        assert!(body_str.contains("recorded response"));
1489    }
1490
1491    #[tokio::test]
1492    async fn test_behavioral_scenario_replay_with_cookies() {
1493        let temp_dir = TempDir::new().unwrap();
1494        let fixtures_dir = temp_dir.path().to_path_buf();
1495        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1496
1497        // Create a scenario replay that extracts session ID from headers
1498        // Note: The current implementation checks x-session-id or session-id headers (lines 288-292)
1499        // Cookie parsing would need to be added separately
1500        struct CookieScenarioReplay;
1501        #[async_trait]
1502        impl BehavioralScenarioReplay for CookieScenarioReplay {
1503            async fn try_replay(
1504                &self,
1505                _method: &Method,
1506                _uri: &Uri,
1507                _headers: &HeaderMap,
1508                _body: Option<&[u8]>,
1509                session_id: Option<&str>,
1510            ) -> Result<Option<BehavioralReplayResponse>> {
1511                // Test that session_id is extracted from headers
1512                // The code checks x-session-id or session-id headers, not cookies
1513                if session_id == Some("header-session-123") {
1514                    Ok(Some(BehavioralReplayResponse {
1515                        status_code: 200,
1516                        headers: HashMap::new(),
1517                        body: b"header scenario response".to_vec(),
1518                        timing_ms: None,
1519                        content_type: "application/json".to_string(),
1520                    }))
1521                } else {
1522                    Ok(None)
1523                }
1524            }
1525        }
1526
1527        let scenario_replay = Arc::new(CookieScenarioReplay);
1528        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1529            .with_behavioral_scenario_replay(scenario_replay);
1530
1531        let method = Method::GET;
1532        let uri = Uri::from_static("/api/test");
1533        let mut headers = HeaderMap::new();
1534        // Set session-id header (lines 288-292 test header extraction)
1535        headers.insert("session-id", "header-session-123".parse().unwrap());
1536
1537        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1538
1539        assert_eq!(response.status_code, 200);
1540        assert_eq!(response.source.source_type, "behavioral_scenario");
1541        let body_str = String::from_utf8_lossy(&response.body);
1542        assert!(body_str.contains("header scenario"));
1543    }
1544
1545    #[tokio::test]
1546    async fn test_route_chaos_latency_error_handling() {
1547        let temp_dir = TempDir::new().unwrap();
1548        let fixtures_dir = temp_dir.path().to_path_buf();
1549        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1550
1551        // Create a route chaos injector that returns an error from inject_latency (line 337)
1552        struct ErrorLatencyInjector;
1553        #[async_trait]
1554        impl RouteChaosInjectorTrait for ErrorLatencyInjector {
1555            async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
1556                Err(Error::generic("Latency injection failed".to_string()))
1557            }
1558            fn get_fault_response(
1559                &self,
1560                _method: &Method,
1561                _uri: &Uri,
1562            ) -> Option<RouteFaultResponse> {
1563                None
1564            }
1565        }
1566
1567        let route_chaos = Arc::new(ErrorLatencyInjector);
1568        let mock_generator =
1569            Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
1570        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1571            .with_route_chaos_injector(route_chaos);
1572
1573        let method = Method::GET;
1574        let uri = Uri::from_static("/api/test");
1575        let headers = HeaderMap::new();
1576
1577        // Should handle the error gracefully and continue (line 337)
1578        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1579        assert_eq!(response.status_code, 200);
1580    }
1581
1582    #[tokio::test]
1583    async fn test_behavioral_scenario_replay_with_timing_delay() {
1584        let temp_dir = TempDir::new().unwrap();
1585        let fixtures_dir = temp_dir.path().to_path_buf();
1586        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1587
1588        // Create a scenario replay with timing delay (line 299-301)
1589        struct TimingScenarioReplay;
1590        #[async_trait]
1591        impl BehavioralScenarioReplay for TimingScenarioReplay {
1592            async fn try_replay(
1593                &self,
1594                _method: &Method,
1595                _uri: &Uri,
1596                _headers: &HeaderMap,
1597                _body: Option<&[u8]>,
1598                _session_id: Option<&str>,
1599            ) -> Result<Option<BehavioralReplayResponse>> {
1600                Ok(Some(BehavioralReplayResponse {
1601                    status_code: 200,
1602                    headers: HashMap::new(),
1603                    body: b"delayed response".to_vec(),
1604                    timing_ms: Some(15), // Timing delay
1605                    content_type: "application/json".to_string(),
1606                }))
1607            }
1608        }
1609
1610        let scenario_replay = Arc::new(TimingScenarioReplay);
1611        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1612            .with_behavioral_scenario_replay(scenario_replay);
1613
1614        let method = Method::GET;
1615        let uri = Uri::from_static("/api/test");
1616        let headers = HeaderMap::new();
1617
1618        let start = std::time::Instant::now();
1619        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1620        let elapsed = start.elapsed();
1621
1622        assert_eq!(response.status_code, 200);
1623        assert!(elapsed.as_millis() >= 15); // Should have timing delay (line 300)
1624    }
1625
1626    #[tokio::test]
1627    async fn test_stateful_handler_with_response() {
1628        let temp_dir = TempDir::new().unwrap();
1629        let fixtures_dir = temp_dir.path().to_path_buf();
1630        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1631
1632        // Create a stateful handler that actually returns a response (lines 318-329)
1633        // Note: This requires setting up stateful rules, which is complex
1634        // For now, we'll test that the path is checked even if no response is returned
1635        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1636        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1637            .with_stateful_handler(stateful_handler);
1638
1639        let method = Method::GET;
1640        let uri = Uri::from_static("/api/test");
1641        let headers = HeaderMap::new();
1642
1643        // Stateful handler path is checked (lines 317-330)
1644        // May not return a response if no rules match, but path is executed
1645        let _result = handler.process_request(&method, &uri, &headers, None).await;
1646        // Result may be error if no handler matches, which is expected
1647    }
1648
1649    #[tokio::test]
1650    async fn test_replay_handler_content_type_extraction() {
1651        let temp_dir = TempDir::new().unwrap();
1652        let fixtures_dir = temp_dir.path().to_path_buf();
1653        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1654
1655        let method = Method::GET;
1656        let uri = Uri::from_static("/api/test");
1657        let mut headers = HeaderMap::new();
1658        headers.insert("content-type", "application/xml".parse().unwrap());
1659
1660        // Record with custom content type
1661        let fingerprint = RequestFingerprint::new(method.clone(), &uri, &headers, None);
1662        record_replay
1663            .record_handler()
1664            .record_request(&fingerprint, 200, &headers, r#"<xml>test</xml>"#, None)
1665            .await
1666            .unwrap();
1667
1668        // Create handler after recording
1669        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1670
1671        // Replay should extract content type from recorded headers (lines 269-273)
1672        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1673        assert_eq!(response.content_type, "application/xml");
1674    }
1675
1676    #[tokio::test]
1677    async fn test_proxy_migration_mode_mock() {
1678        let temp_dir = TempDir::new().unwrap();
1679        let fixtures_dir = temp_dir.path().to_path_buf();
1680        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1681
1682        // Create proxy config with Mock migration mode (lines 402-410)
1683        let mut proxy_config =
1684            crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
1685        proxy_config.migration_enabled = true;
1686        proxy_config.rules.push(crate::proxy::config::ProxyRule {
1687            path_pattern: "/api/*".to_string(),
1688            target_url: "http://localhost:8080".to_string(),
1689            enabled: true,
1690            pattern: "/api/*".to_string(),
1691            upstream_url: "http://localhost:8080".to_string(),
1692            migration_mode: crate::proxy::config::MigrationMode::Mock, // Force mock mode
1693            migration_group: None,
1694            condition: None,
1695        });
1696
1697        let proxy_handler = ProxyHandler::new(proxy_config);
1698        let mock_generator =
1699            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1700
1701        let handler = PriorityHttpHandler::new(
1702            record_replay,
1703            None,
1704            Some(proxy_handler),
1705            Some(mock_generator),
1706        );
1707
1708        let method = Method::GET;
1709        let uri = Uri::from_static("/api/test");
1710        let headers = HeaderMap::new();
1711
1712        // Migration mode Mock should skip proxy and use mock (lines 409-410)
1713        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1714        assert_eq!(response.status_code, 200);
1715        assert_eq!(response.source.source_type, "mock");
1716    }
1717
1718    #[tokio::test]
1719    async fn test_proxy_migration_mode_disabled() {
1720        let temp_dir = TempDir::new().unwrap();
1721        let fixtures_dir = temp_dir.path().to_path_buf();
1722        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1723
1724        // Create proxy config with migration disabled (lines 402-406)
1725        let mut proxy_config =
1726            crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
1727        proxy_config.migration_enabled = false; // Migration disabled
1728        proxy_config.enabled = false; // Also disable proxy to avoid network calls
1729
1730        let proxy_handler = ProxyHandler::new(proxy_config);
1731        let mock_generator =
1732            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1733
1734        let handler = PriorityHttpHandler::new(
1735            record_replay,
1736            None,
1737            Some(proxy_handler),
1738            Some(mock_generator),
1739        );
1740
1741        let method = Method::GET;
1742        let uri = Uri::from_static("/api/test");
1743        let headers = HeaderMap::new();
1744
1745        // With migration disabled, should fall through to mock (line 405)
1746        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1747        assert_eq!(response.status_code, 200);
1748        assert_eq!(response.source.source_type, "mock");
1749    }
1750
1751    #[tokio::test]
1752    async fn test_continuum_engine_enabled_check() {
1753        let temp_dir = TempDir::new().unwrap();
1754        let fixtures_dir = temp_dir.path().to_path_buf();
1755        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1756
1757        // Create continuum engine (lines 393-397)
1758        let continuum_config = crate::reality_continuum::config::ContinuumConfig::new();
1759        let continuum_engine = Arc::new(RealityContinuumEngine::new(continuum_config));
1760        let mock_generator =
1761            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1762
1763        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1764            .with_continuum_engine(continuum_engine);
1765
1766        let method = Method::GET;
1767        let uri = Uri::from_static("/api/test");
1768        let headers = HeaderMap::new();
1769
1770        // Should check if continuum is enabled (line 394)
1771        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1772        assert_eq!(response.status_code, 200);
1773    }
1774
1775    #[tokio::test]
1776    async fn test_behavioral_scenario_replay_error_handling() {
1777        let temp_dir = TempDir::new().unwrap();
1778        let fixtures_dir = temp_dir.path().to_path_buf();
1779        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1780
1781        // Create a scenario replay that returns an error (lines 294-296)
1782        struct ErrorScenarioReplay;
1783        #[async_trait]
1784        impl BehavioralScenarioReplay for ErrorScenarioReplay {
1785            async fn try_replay(
1786                &self,
1787                _method: &Method,
1788                _uri: &Uri,
1789                _headers: &HeaderMap,
1790                _body: Option<&[u8]>,
1791                _session_id: Option<&str>,
1792            ) -> Result<Option<BehavioralReplayResponse>> {
1793                Err(Error::generic("Scenario replay error".to_string()))
1794            }
1795        }
1796
1797        let scenario_replay = Arc::new(ErrorScenarioReplay);
1798        let mock_generator =
1799            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
1800        let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
1801            .with_behavioral_scenario_replay(scenario_replay);
1802
1803        let method = Method::GET;
1804        let uri = Uri::from_static("/api/test");
1805        let headers = HeaderMap::new();
1806
1807        // Error should be handled gracefully and fall through to mock
1808        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1809        assert_eq!(response.status_code, 200);
1810        assert_eq!(response.source.source_type, "mock");
1811    }
1812
1813    #[tokio::test]
1814    async fn test_behavioral_scenario_replay_with_session_id_header() {
1815        let temp_dir = TempDir::new().unwrap();
1816        let fixtures_dir = temp_dir.path().to_path_buf();
1817        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1818
1819        // Test session ID extraction from x-session-id header (lines 288-292)
1820        struct SessionScenarioReplay;
1821        #[async_trait]
1822        impl BehavioralScenarioReplay for SessionScenarioReplay {
1823            async fn try_replay(
1824                &self,
1825                _method: &Method,
1826                _uri: &Uri,
1827                _headers: &HeaderMap,
1828                _body: Option<&[u8]>,
1829                session_id: Option<&str>,
1830            ) -> Result<Option<BehavioralReplayResponse>> {
1831                if session_id == Some("header-session-456") {
1832                    Ok(Some(BehavioralReplayResponse {
1833                        status_code: 200,
1834                        headers: HashMap::new(),
1835                        body: b"header session response".to_vec(),
1836                        timing_ms: None,
1837                        content_type: "application/json".to_string(),
1838                    }))
1839                } else {
1840                    Ok(None)
1841                }
1842            }
1843        }
1844
1845        let scenario_replay = Arc::new(SessionScenarioReplay);
1846        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1847            .with_behavioral_scenario_replay(scenario_replay);
1848
1849        let method = Method::GET;
1850        let uri = Uri::from_static("/api/test");
1851        let mut headers = HeaderMap::new();
1852        headers.insert("x-session-id", "header-session-456".parse().unwrap());
1853
1854        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1855        assert_eq!(response.status_code, 200);
1856        assert_eq!(response.source.source_type, "behavioral_scenario");
1857    }
1858
1859    #[tokio::test]
1860    async fn test_stateful_handler_returns_response() {
1861        let temp_dir = TempDir::new().unwrap();
1862        let fixtures_dir = temp_dir.path().to_path_buf();
1863        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1864
1865        // Create a stateful handler with a config that matches our request (lines 318-329)
1866        let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
1867
1868        // Add a stateful config for /api/orders/{order_id}
1869        let mut state_responses = HashMap::new();
1870        state_responses.insert(
1871            "initial".to_string(),
1872            crate::stateful_handler::StateResponse {
1873                status_code: 200,
1874                headers: HashMap::new(),
1875                body_template: r#"{"status": "initial", "order_id": "123"}"#.to_string(),
1876                content_type: "application/json".to_string(),
1877            },
1878        );
1879
1880        let config = crate::stateful_handler::StatefulConfig {
1881            resource_id_extract: crate::stateful_handler::ResourceIdExtract::PathParam {
1882                param: "order_id".to_string(),
1883            },
1884            resource_type: "order".to_string(),
1885            state_responses,
1886            transitions: vec![],
1887        };
1888
1889        stateful_handler.add_config("/api/orders/{order_id}".to_string(), config).await;
1890
1891        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
1892            .with_stateful_handler(stateful_handler);
1893
1894        let method = Method::GET;
1895        let uri = Uri::from_static("/api/orders/123");
1896        let headers = HeaderMap::new();
1897
1898        // Should hit stateful handler path (lines 318-329)
1899        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1900        assert_eq!(response.status_code, 200);
1901        assert_eq!(response.source.source_type, "stateful");
1902        assert_eq!(response.source.metadata.get("state"), Some(&"initial".to_string()));
1903        assert_eq!(response.source.metadata.get("resource_id"), Some(&"123".to_string()));
1904    }
1905
1906    #[tokio::test]
1907    async fn test_record_handler_path_with_no_other_handlers() {
1908        let temp_dir = TempDir::new().unwrap();
1909        let fixtures_dir = temp_dir.path().to_path_buf();
1910        // Create record_replay with recording enabled (lines 714-739)
1911        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, true, false);
1912
1913        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1914
1915        let method = Method::GET; // GET should be recorded when record_get_only is false
1916        let uri = Uri::from_static("/api/test");
1917        let headers = HeaderMap::new();
1918
1919        // Should hit record handler path (lines 714-739)
1920        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1921        assert_eq!(response.status_code, 200);
1922        assert_eq!(response.source.source_type, "record");
1923        let body_str = String::from_utf8_lossy(&response.body);
1924        assert!(body_str.contains("Request recorded"));
1925    }
1926
1927    #[tokio::test]
1928    async fn test_mock_generator_with_migration_mode() {
1929        let temp_dir = TempDir::new().unwrap();
1930        let fixtures_dir = temp_dir.path().to_path_buf();
1931        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1932
1933        // Create proxy config with Mock migration mode
1934        let mut proxy_config =
1935            crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
1936        proxy_config.migration_enabled = true;
1937        proxy_config.rules.push(crate::proxy::config::ProxyRule {
1938            path_pattern: "/api/*".to_string(),
1939            target_url: "http://localhost:8080".to_string(),
1940            enabled: true,
1941            pattern: "/api/*".to_string(),
1942            upstream_url: "http://localhost:8080".to_string(),
1943            migration_mode: crate::proxy::config::MigrationMode::Mock,
1944            migration_group: None,
1945            condition: None,
1946        });
1947        proxy_config.enabled = false; // Disable proxy to avoid network calls
1948
1949        let proxy_handler = ProxyHandler::new(proxy_config);
1950        let mock_generator = Box::new(SimpleMockGenerator::new(
1951            200,
1952            r#"{"message": "mock with migration"}"#.to_string(),
1953        ));
1954
1955        let handler = PriorityHttpHandler::new(
1956            record_replay,
1957            None,
1958            Some(proxy_handler),
1959            Some(mock_generator),
1960        );
1961
1962        let method = Method::GET;
1963        let uri = Uri::from_static("/api/test");
1964        let headers = HeaderMap::new();
1965
1966        // Migration mode Mock should skip proxy and use mock (lines 682-710)
1967        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
1968        assert_eq!(response.status_code, 200);
1969        assert_eq!(response.source.source_type, "mock");
1970        let body_str = String::from_utf8_lossy(&response.body);
1971        assert!(body_str.contains("mock with migration"));
1972    }
1973
1974    #[tokio::test]
1975    async fn test_no_handler_can_process_request() {
1976        let temp_dir = TempDir::new().unwrap();
1977        let fixtures_dir = temp_dir.path().to_path_buf();
1978        // Create handler with no enabled handlers
1979        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, false, false);
1980        let handler = PriorityHttpHandler::new(record_replay, None, None, None);
1981
1982        let method = Method::GET;
1983        let uri = Uri::from_static("/api/test");
1984        let headers = HeaderMap::new();
1985
1986        // Should return error when no handler can process (line 742)
1987        let result = handler.process_request(&method, &uri, &headers, None).await;
1988        assert!(result.is_err());
1989        assert!(result.unwrap_err().to_string().contains("No handler could process"));
1990    }
1991
1992    #[tokio::test]
1993    async fn test_route_chaos_fault_injection() {
1994        let temp_dir = TempDir::new().unwrap();
1995        let fixtures_dir = temp_dir.path().to_path_buf();
1996        let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
1997
1998        // Create a route chaos injector that returns a fault response (lines 341-355)
1999        struct FaultInjector;
2000        #[async_trait]
2001        impl RouteChaosInjectorTrait for FaultInjector {
2002            async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
2003                Ok(())
2004            }
2005            fn get_fault_response(&self, method: &Method, uri: &Uri) -> Option<RouteFaultResponse> {
2006                if method == Method::GET && uri.path() == "/api/faulty" {
2007                    Some(RouteFaultResponse {
2008                        status_code: 503,
2009                        error_message: "Service unavailable".to_string(),
2010                        fault_type: "injected_fault".to_string(),
2011                    })
2012                } else {
2013                    None
2014                }
2015            }
2016        }
2017
2018        let route_chaos = Arc::new(FaultInjector);
2019        let handler = PriorityHttpHandler::new(record_replay, None, None, None)
2020            .with_route_chaos_injector(route_chaos);
2021
2022        let method = Method::GET;
2023        let uri = Uri::from_static("/api/faulty");
2024        let headers = HeaderMap::new();
2025
2026        // Should return fault response (lines 341-355)
2027        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
2028        assert_eq!(response.status_code, 503);
2029        let body_str = String::from_utf8_lossy(&response.body);
2030        assert!(body_str.contains("Service unavailable"));
2031        assert!(body_str.contains("injected_failure"));
2032    }
2033}