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