1use crate::behavioral_economics::BehavioralEconomicsEngine;
5use crate::stateful_handler::StatefulResponseHandler;
6use crate::{
7 CustomFixtureLoader, Error, FailureInjector, ProxyHandler, RealityContinuumEngine,
8 RecordReplayHandler, RequestFingerprint, ResponsePriority, ResponseSource, Result,
9};
10use 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#[derive(Debug, Clone)]
20pub struct RouteFaultResponse {
21 pub status_code: u16,
23 pub error_message: String,
25 pub fault_type: String,
27}
28
29#[async_trait]
33pub trait RouteChaosInjectorTrait: Send + Sync {
34 async fn inject_latency(&self, method: &Method, uri: &Uri) -> Result<()>;
36
37 fn get_fault_response(&self, method: &Method, uri: &Uri) -> Option<RouteFaultResponse>;
39}
40
41#[async_trait]
43pub trait BehavioralScenarioReplay: Send + Sync {
44 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#[derive(Debug, Clone)]
57pub struct BehavioralReplayResponse {
58 pub status_code: u16,
60 pub headers: HashMap<String, String>,
62 pub body: Vec<u8>,
64 pub timing_ms: Option<u64>,
66 pub content_type: String,
68}
69
70pub struct PriorityHttpHandler {
72 custom_fixture_loader: Option<Arc<CustomFixtureLoader>>,
74 record_replay: RecordReplayHandler,
76 behavioral_scenario_replay: Option<Arc<dyn BehavioralScenarioReplay + Send + Sync>>,
78 stateful_handler: Option<Arc<StatefulResponseHandler>>,
80 route_chaos_injector: Option<Arc<dyn RouteChaosInjectorTrait>>,
83 failure_injector: Option<FailureInjector>,
85 proxy_handler: Option<ProxyHandler>,
87 mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
89 openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
91 continuum_engine: Option<Arc<RealityContinuumEngine>>,
93 behavioral_economics_engine: Option<Arc<RwLock<BehavioralEconomicsEngine>>>,
95 request_metrics: Arc<RwLock<HashMap<String, (u64, u64, std::time::Instant)>>>,
97}
98
99pub trait MockGenerator {
101 fn generate_mock_response(
103 &self,
104 fingerprint: &RequestFingerprint,
105 headers: &HeaderMap,
106 body: Option<&[u8]>,
107 ) -> Result<Option<MockResponse>>;
108}
109
110#[derive(Debug, Clone)]
112pub struct MockResponse {
113 pub status_code: u16,
115 pub headers: HashMap<String, String>,
117 pub body: String,
119 pub content_type: String,
121}
122
123impl PriorityHttpHandler {
124 pub fn new(
126 record_replay: RecordReplayHandler,
127 failure_injector: Option<FailureInjector>,
128 proxy_handler: Option<ProxyHandler>,
129 mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
130 ) -> Self {
131 Self {
132 custom_fixture_loader: None,
133 record_replay,
134 behavioral_scenario_replay: None,
135 stateful_handler: None,
136 route_chaos_injector: None,
137 failure_injector,
138 proxy_handler,
139 mock_generator,
140 openapi_spec: None,
141 continuum_engine: None,
142 behavioral_economics_engine: None,
143 request_metrics: Arc::new(RwLock::new(HashMap::new())),
144 }
145 }
146
147 pub fn new_with_openapi(
149 record_replay: RecordReplayHandler,
150 failure_injector: Option<FailureInjector>,
151 proxy_handler: Option<ProxyHandler>,
152 mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
153 openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
154 ) -> Self {
155 Self {
156 custom_fixture_loader: None,
157 record_replay,
158 behavioral_scenario_replay: None,
159 stateful_handler: None,
160 route_chaos_injector: None,
161 failure_injector,
162 proxy_handler,
163 mock_generator,
164 openapi_spec,
165 continuum_engine: None,
166 behavioral_economics_engine: None,
167 request_metrics: Arc::new(RwLock::new(HashMap::new())),
168 }
169 }
170
171 pub fn with_custom_fixture_loader(mut self, loader: Arc<CustomFixtureLoader>) -> Self {
173 self.custom_fixture_loader = Some(loader);
174 self
175 }
176
177 pub fn with_stateful_handler(mut self, handler: Arc<StatefulResponseHandler>) -> Self {
179 self.stateful_handler = Some(handler);
180 self
181 }
182
183 pub fn with_route_chaos_injector(mut self, injector: Arc<dyn RouteChaosInjectorTrait>) -> Self {
185 self.route_chaos_injector = Some(injector);
186 self
187 }
188
189 pub fn with_continuum_engine(mut self, engine: Arc<RealityContinuumEngine>) -> Self {
191 self.continuum_engine = Some(engine);
192 self
193 }
194
195 pub fn with_behavioral_economics_engine(
197 mut self,
198 engine: Arc<RwLock<BehavioralEconomicsEngine>>,
199 ) -> Self {
200 self.behavioral_economics_engine = Some(engine);
201 self
202 }
203
204 pub fn with_behavioral_scenario_replay(
206 mut self,
207 replay_engine: Arc<dyn BehavioralScenarioReplay + Send + Sync>,
208 ) -> Self {
209 self.behavioral_scenario_replay = Some(replay_engine);
210 self
211 }
212
213 pub async fn process_request(
215 &self,
216 method: &Method,
217 uri: &Uri,
218 headers: &HeaderMap,
219 body: Option<&[u8]>,
220 ) -> Result<PriorityResponse> {
221 let fingerprint = RequestFingerprint::new(method.clone(), uri, headers, body);
222
223 if let Some(ref custom_loader) = self.custom_fixture_loader {
225 if let Some(custom_fixture) = custom_loader.load_fixture(&fingerprint) {
226 if custom_fixture.delay_ms > 0 {
228 tokio::time::sleep(tokio::time::Duration::from_millis(custom_fixture.delay_ms))
229 .await;
230 }
231
232 let response_body = if custom_fixture.response.is_string() {
234 custom_fixture.response.as_str().unwrap().to_string()
235 } else {
236 serde_json::to_string(&custom_fixture.response).map_err(|e| {
237 Error::generic(format!(
238 "Failed to serialize custom fixture response: {}",
239 e
240 ))
241 })?
242 };
243
244 let content_type = custom_fixture
246 .headers
247 .get("content-type")
248 .cloned()
249 .unwrap_or_else(|| "application/json".to_string());
250
251 return Ok(PriorityResponse {
252 source: ResponseSource::new(
253 ResponsePriority::Replay,
254 "custom_fixture".to_string(),
255 )
256 .with_metadata("fixture_path".to_string(), custom_fixture.path.clone()),
257 status_code: custom_fixture.status,
258 headers: custom_fixture.headers.clone(),
259 body: response_body.into_bytes(),
260 content_type,
261 });
262 }
263 }
264
265 if let Some(recorded_request) =
267 self.record_replay.replay_handler().load_fixture(&fingerprint).await?
268 {
269 let content_type = recorded_request
270 .response_headers
271 .get("content-type")
272 .unwrap_or(&"application/json".to_string())
273 .clone();
274
275 return Ok(PriorityResponse {
276 source: ResponseSource::new(ResponsePriority::Replay, "replay".to_string())
277 .with_metadata("fixture_path".to_string(), "recorded".to_string()),
278 status_code: recorded_request.status_code,
279 headers: recorded_request.response_headers,
280 body: recorded_request.response_body.into_bytes(),
281 content_type,
282 });
283 }
284
285 if let Some(ref scenario_replay) = self.behavioral_scenario_replay {
287 let session_id = headers
289 .get("x-session-id")
290 .or_else(|| headers.get("session-id"))
291 .and_then(|v| v.to_str().ok())
292 .map(|s| s.to_string());
293
294 if let Ok(Some(replay_response)) = scenario_replay
295 .try_replay(method, uri, headers, body, session_id.as_deref())
296 .await
297 {
298 if let Some(timing_ms) = replay_response.timing_ms {
300 tokio::time::sleep(tokio::time::Duration::from_millis(timing_ms)).await;
301 }
302 return Ok(PriorityResponse {
303 source: ResponseSource::new(
304 ResponsePriority::Replay,
305 "behavioral_scenario".to_string(),
306 )
307 .with_metadata("replay_type".to_string(), "scenario".to_string()),
308 status_code: replay_response.status_code,
309 headers: replay_response.headers,
310 body: replay_response.body,
311 content_type: replay_response.content_type,
312 });
313 }
314 }
315
316 if let Some(ref stateful_handler) = self.stateful_handler {
318 if let Some(stateful_response) =
319 stateful_handler.process_request(method, uri, headers, body).await?
320 {
321 return Ok(PriorityResponse {
322 source: ResponseSource::new(ResponsePriority::Stateful, "stateful".to_string())
323 .with_metadata("state".to_string(), stateful_response.state)
324 .with_metadata("resource_id".to_string(), stateful_response.resource_id),
325 status_code: stateful_response.status_code,
326 headers: stateful_response.headers,
327 body: stateful_response.body.into_bytes(),
328 content_type: stateful_response.content_type,
329 });
330 }
331 }
332
333 if let Some(ref route_chaos) = self.route_chaos_injector {
335 if let Err(e) = route_chaos.inject_latency(method, uri).await {
337 tracing::warn!("Failed to inject per-route latency: {}", e);
338 }
339
340 if let Some(fault_response) = route_chaos.get_fault_response(method, uri) {
342 let error_response = serde_json::json!({
343 "error": fault_response.error_message,
344 "injected_failure": true,
345 "fault_type": fault_response.fault_type,
346 "timestamp": chrono::Utc::now().to_rfc3339()
347 });
348
349 return Ok(PriorityResponse {
350 source: ResponseSource::new(
351 ResponsePriority::Fail,
352 "route_fault_injection".to_string(),
353 )
354 .with_metadata("fault_type".to_string(), fault_response.fault_type)
355 .with_metadata("error_message".to_string(), fault_response.error_message),
356 status_code: fault_response.status_code,
357 headers: HashMap::new(),
358 body: serde_json::to_string(&error_response)?.into_bytes(),
359 content_type: "application/json".to_string(),
360 });
361 }
362 }
363
364 if let Some(ref failure_injector) = self.failure_injector {
366 let tags = if let Some(ref spec) = self.openapi_spec {
367 fingerprint.openapi_tags(spec).unwrap_or_else(|| fingerprint.tags())
368 } else {
369 fingerprint.tags()
370 };
371 if let Some((status_code, error_message)) = failure_injector.process_request(&tags) {
372 let error_response = serde_json::json!({
373 "error": error_message,
374 "injected_failure": true,
375 "timestamp": chrono::Utc::now().to_rfc3339()
376 });
377
378 return Ok(PriorityResponse {
379 source: ResponseSource::new(
380 ResponsePriority::Fail,
381 "failure_injection".to_string(),
382 )
383 .with_metadata("error_message".to_string(), error_message),
384 status_code,
385 headers: HashMap::new(),
386 body: serde_json::to_string(&error_response)?.into_bytes(),
387 content_type: "application/json".to_string(),
388 });
389 }
390 }
391
392 let should_blend = if let Some(ref continuum_engine) = self.continuum_engine {
394 continuum_engine.is_enabled().await
395 } else {
396 false
397 };
398
399 if let Some(ref proxy_handler) = self.proxy_handler {
401 let migration_mode = if proxy_handler.config.migration_enabled {
403 proxy_handler.config.get_effective_migration_mode(uri.path())
404 } else {
405 None
406 };
407
408 if let Some(crate::proxy::config::MigrationMode::Mock) = migration_mode {
410 } else if proxy_handler.config.should_proxy_with_condition(method, uri, headers, body) {
412 let is_shadow = proxy_handler.config.should_shadow(uri.path());
414
415 if should_blend {
417 let proxy_future = proxy_handler.proxy_request(method, uri, headers, body);
419 let mock_result = if let Some(ref mock_generator) = self.mock_generator {
420 mock_generator.generate_mock_response(&fingerprint, headers, body)
421 } else {
422 Ok(None)
423 };
424
425 let proxy_result = proxy_future.await;
427
428 match (proxy_result, mock_result) {
430 (Ok(proxy_response), Ok(Some(mock_response))) => {
431 if let Some(ref continuum_engine) = self.continuum_engine {
433 let blend_ratio =
434 continuum_engine.get_blend_ratio(uri.path()).await;
435 let blender = continuum_engine.blender();
436
437 let mock_body_str = &mock_response.body;
439 let real_body_bytes =
440 proxy_response.body.clone().unwrap_or_default();
441 let real_body_str = String::from_utf8_lossy(&real_body_bytes);
442
443 let mock_json: serde_json::Value =
444 serde_json::from_str(mock_body_str)
445 .unwrap_or_else(|_| serde_json::json!({}));
446 let real_json: serde_json::Value =
447 serde_json::from_str(&real_body_str)
448 .unwrap_or_else(|_| serde_json::json!({}));
449
450 let blended_json =
452 blender.blend_responses(&mock_json, &real_json, blend_ratio);
453 let blended_body = serde_json::to_string(&blended_json)
454 .unwrap_or_else(|_| real_body_str.to_string());
455
456 let blended_status = blender.blend_status_code(
458 mock_response.status_code,
459 proxy_response.status_code,
460 blend_ratio,
461 );
462
463 let mut proxy_headers = HashMap::new();
465 for (key, value) in proxy_response.headers.iter() {
466 if let Ok(value_str) = value.to_str() {
467 proxy_headers.insert(
468 key.as_str().to_string(),
469 value_str.to_string(),
470 );
471 }
472 }
473 let blended_headers = blender.blend_headers(
474 &mock_response.headers,
475 &proxy_headers,
476 blend_ratio,
477 );
478
479 let content_type = blended_headers
480 .get("content-type")
481 .cloned()
482 .or_else(|| {
483 proxy_response
484 .headers
485 .get("content-type")
486 .and_then(|v| v.to_str().ok())
487 .map(|s| s.to_string())
488 })
489 .unwrap_or_else(|| "application/json".to_string());
490
491 tracing::info!(
492 path = %uri.path(),
493 blend_ratio = blend_ratio,
494 "Reality Continuum: blended mock and real responses"
495 );
496
497 let mut source = ResponseSource::new(
498 ResponsePriority::Proxy,
499 "continuum".to_string(),
500 )
501 .with_metadata("blend_ratio".to_string(), blend_ratio.to_string())
502 .with_metadata(
503 "upstream_url".to_string(),
504 proxy_handler.config.get_upstream_url(uri.path()),
505 );
506
507 if let Some(mode) = migration_mode {
508 source = source.with_metadata(
509 "migration_mode".to_string(),
510 format!("{:?}", mode),
511 );
512 }
513
514 return Ok(PriorityResponse {
515 source,
516 status_code: blended_status,
517 headers: blended_headers,
518 body: blended_body.into_bytes(),
519 content_type,
520 });
521 }
522 }
523 (Ok(proxy_response), Ok(None)) => {
524 tracing::debug!(
526 path = %uri.path(),
527 "Continuum: mock generation failed, using real response"
528 );
529 }
531 (Ok(proxy_response), Err(_)) => {
532 tracing::debug!(
534 path = %uri.path(),
535 "Continuum: mock generation failed, using real response"
536 );
537 }
539 (Err(e), Ok(Some(mock_response))) => {
540 tracing::debug!(
542 path = %uri.path(),
543 error = %e,
544 "Continuum: proxy failed, using mock response"
545 );
546 let mut source = ResponseSource::new(
548 ResponsePriority::Mock,
549 "continuum_fallback".to_string(),
550 )
551 .with_metadata("generated_from".to_string(), "openapi_spec".to_string())
552 .with_metadata(
553 "fallback_reason".to_string(),
554 "proxy_failed".to_string(),
555 );
556
557 if let Some(mode) = migration_mode {
558 source = source.with_metadata(
559 "migration_mode".to_string(),
560 format!("{:?}", mode),
561 );
562 }
563
564 return Ok(PriorityResponse {
565 source,
566 status_code: mock_response.status_code,
567 headers: mock_response.headers,
568 body: mock_response.body.into_bytes(),
569 content_type: mock_response.content_type,
570 });
571 }
572 (Err(e), _) => {
573 tracing::warn!(
575 path = %uri.path(),
576 error = %e,
577 "Continuum: both proxy and mock failed"
578 );
579 if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode
581 {
582 return Err(Error::generic(format!(
583 "Proxy request failed in real mode: {}",
584 e
585 )));
586 }
587 }
589 }
590 }
591
592 match proxy_handler.proxy_request(method, uri, headers, body).await {
594 Ok(proxy_response) => {
595 let mut response_headers = HashMap::new();
596 for (key, value) in proxy_response.headers.iter() {
597 let key_str = key.as_str();
598 if let Ok(value_str) = value.to_str() {
599 response_headers.insert(key_str.to_string(), value_str.to_string());
600 }
601 }
602
603 let content_type = response_headers
604 .get("content-type")
605 .unwrap_or(&"application/json".to_string())
606 .clone();
607
608 if is_shadow {
610 if let Some(ref mock_generator) = self.mock_generator {
611 if let Ok(Some(mock_response)) = mock_generator
612 .generate_mock_response(&fingerprint, headers, body)
613 {
614 tracing::info!(
616 path = %uri.path(),
617 real_status = proxy_response.status_code,
618 mock_status = mock_response.status_code,
619 "Shadow mode: comparing real and mock responses"
620 );
621
622 let real_body_bytes =
624 proxy_response.body.clone().unwrap_or_default();
625 let real_body = String::from_utf8_lossy(&real_body_bytes);
626 let mock_body = &mock_response.body;
627
628 if real_body != *mock_body {
629 tracing::warn!(
630 path = %uri.path(),
631 "Shadow mode: real and mock responses differ"
632 );
633 }
634 }
635 }
636 }
637
638 let mut source = ResponseSource::new(
639 ResponsePriority::Proxy,
640 if is_shadow {
641 "shadow".to_string()
642 } else {
643 "proxy".to_string()
644 },
645 )
646 .with_metadata(
647 "upstream_url".to_string(),
648 proxy_handler.config.get_upstream_url(uri.path()),
649 );
650
651 if let Some(mode) = migration_mode {
652 source = source
653 .with_metadata("migration_mode".to_string(), format!("{:?}", mode));
654 }
655
656 return Ok(PriorityResponse {
657 source,
658 status_code: proxy_response.status_code,
659 headers: response_headers,
660 body: proxy_response.body.unwrap_or_default(),
661 content_type,
662 });
663 }
664 Err(e) => {
665 tracing::warn!("Proxy request failed: {}", e);
666 if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode {
668 return Err(Error::generic(format!(
669 "Proxy request failed in real mode: {}",
670 e
671 )));
672 }
673 }
675 }
676 }
677 }
678
679 if let Some(ref mock_generator) = self.mock_generator {
681 let migration_mode = if let Some(ref proxy_handler) = self.proxy_handler {
683 if proxy_handler.config.migration_enabled {
684 proxy_handler.config.get_effective_migration_mode(uri.path())
685 } else {
686 None
687 }
688 } else {
689 None
690 };
691
692 if let Some(mock_response) =
693 mock_generator.generate_mock_response(&fingerprint, headers, body)?
694 {
695 let mut source = ResponseSource::new(ResponsePriority::Mock, "mock".to_string())
696 .with_metadata("generated_from".to_string(), "openapi_spec".to_string());
697
698 if let Some(mode) = migration_mode {
699 source =
700 source.with_metadata("migration_mode".to_string(), format!("{:?}", mode));
701 }
702
703 return Ok(PriorityResponse {
704 source,
705 status_code: mock_response.status_code,
706 headers: mock_response.headers,
707 body: mock_response.body.into_bytes(),
708 content_type: mock_response.content_type,
709 });
710 }
711 }
712
713 if self.record_replay.record_handler().should_record(method) {
715 let default_response = serde_json::json!({
717 "message": "Request recorded for future replay",
718 "timestamp": chrono::Utc::now().to_rfc3339(),
719 "fingerprint": fingerprint.to_hash()
720 });
721
722 let response_body = serde_json::to_string(&default_response)?;
723 let status_code = 200;
724
725 self.record_replay
727 .record_handler()
728 .record_request(&fingerprint, status_code, headers, &response_body, None)
729 .await?;
730
731 return Ok(PriorityResponse {
732 source: ResponseSource::new(ResponsePriority::Record, "record".to_string())
733 .with_metadata("recorded".to_string(), "true".to_string()),
734 status_code,
735 headers: HashMap::new(),
736 body: response_body.into_bytes(),
737 content_type: "application/json".to_string(),
738 });
739 }
740
741 Err(Error::generic("No handler could process the request".to_string()))
743 }
744
745 async fn apply_behavioral_economics(
750 &self,
751 response: PriorityResponse,
752 method: &Method,
753 uri: &Uri,
754 latency_ms: Option<u64>,
755 ) -> Result<PriorityResponse> {
756 if let Some(ref engine) = self.behavioral_economics_engine {
757 let engine = engine.read().await;
758 let evaluator = engine.condition_evaluator();
759
760 {
762 let mut eval = evaluator.write().await;
763 if let Some(latency) = latency_ms {
764 eval.update_latency(uri.path(), latency);
765 }
766
767 let endpoint = uri.path().to_string();
769 let mut metrics = self.request_metrics.write().await;
770 let now = std::time::Instant::now();
771
772 let (request_count, error_count, last_request_time) = metrics
774 .entry(endpoint.clone())
775 .or_insert_with(|| (0, 0, now));
776
777 *request_count += 1;
779
780 if response.status_code >= 400 {
782 *error_count += 1;
783 }
784
785 let error_rate = if *request_count > 0 {
787 *error_count as f64 / *request_count as f64
788 } else {
789 0.0
790 };
791 eval.update_error_rate(&endpoint, error_rate);
792
793 let time_elapsed = now.duration_since(*last_request_time).as_secs_f64();
795 if time_elapsed > 0.0 {
796 let rps = *request_count as f64 / time_elapsed.max(1.0);
797 eval.update_load(rps);
798 }
799
800 if time_elapsed > 60.0 {
802 *request_count = 1;
803 *error_count = if response.status_code >= 400 { 1 } else { 0 };
804 *last_request_time = now;
805 } else {
806 *last_request_time = now;
807 }
808 }
809
810 let executed_actions = engine.evaluate().await?;
812
813 if !executed_actions.is_empty() {
815 tracing::debug!(
816 "Behavioral economics engine executed {} actions",
817 executed_actions.len()
818 );
819 }
823 }
824
825 Ok(response)
826 }
827}
828
829#[derive(Debug, Clone)]
831pub struct PriorityResponse {
832 pub source: ResponseSource,
834 pub status_code: u16,
836 pub headers: HashMap<String, String>,
838 pub body: Vec<u8>,
840 pub content_type: String,
842}
843
844impl PriorityResponse {
845 pub fn to_axum_response(self) -> axum::response::Response {
847 let mut response = axum::response::Response::new(axum::body::Body::from(self.body));
848 *response.status_mut() = StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::OK);
849
850 for (key, value) in self.headers {
852 if let (Ok(header_name), Ok(header_value)) =
853 (key.parse::<axum::http::HeaderName>(), value.parse::<axum::http::HeaderValue>())
854 {
855 response.headers_mut().insert(header_name, header_value);
856 }
857 }
858
859 if !response.headers().contains_key("content-type") {
861 if let Ok(header_value) = self.content_type.parse::<axum::http::HeaderValue>() {
862 response.headers_mut().insert("content-type", header_value);
863 }
864 }
865
866 response
867 }
868}
869
870pub struct SimpleMockGenerator {
872 pub default_status: u16,
874 pub default_body: String,
876}
877
878impl SimpleMockGenerator {
879 pub fn new(default_status: u16, default_body: String) -> Self {
881 Self {
882 default_status,
883 default_body,
884 }
885 }
886}
887
888impl MockGenerator for SimpleMockGenerator {
889 fn generate_mock_response(
890 &self,
891 _fingerprint: &RequestFingerprint,
892 _headers: &HeaderMap,
893 _body: Option<&[u8]>,
894 ) -> Result<Option<MockResponse>> {
895 Ok(Some(MockResponse {
896 status_code: self.default_status,
897 headers: HashMap::new(),
898 body: self.default_body.clone(),
899 content_type: "application/json".to_string(),
900 }))
901 }
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907 use tempfile::TempDir;
908
909 #[tokio::test]
910 async fn test_priority_chain() {
911 let temp_dir = TempDir::new().unwrap();
912 let fixtures_dir = temp_dir.path().to_path_buf();
913
914 let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
915 let mock_generator =
916 Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock response"}"#.to_string()));
917
918 let handler = PriorityHttpHandler::new_with_openapi(
919 record_replay,
920 None, None, Some(mock_generator),
923 None, );
925
926 let method = Method::GET;
927 let uri = Uri::from_static("/api/test");
928 let headers = HeaderMap::new();
929
930 let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
931
932 assert_eq!(response.status_code, 200);
933 assert_eq!(response.source.source_type, "mock");
934 }
935}