use crate::behavioral_economics::BehavioralEconomicsEngine;
use crate::stateful_handler::StatefulResponseHandler;
use crate::{
CustomFixtureLoader, Error, FailureInjector, ProxyHandler, RealityContinuumEngine,
RecordReplayHandler, RequestFingerprint, ResponsePriority, ResponseSource, Result,
};
use async_trait::async_trait;
use axum::http::{HeaderMap, Method, StatusCode, Uri};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
pub struct RouteFaultResponse {
pub status_code: u16,
pub error_message: String,
pub fault_type: String,
}
#[async_trait]
pub trait RouteChaosInjectorTrait: Send + Sync {
async fn inject_latency(&self, method: &Method, uri: &Uri) -> Result<()>;
fn get_fault_response(&self, method: &Method, uri: &Uri) -> Option<RouteFaultResponse>;
}
#[async_trait]
pub trait BehavioralScenarioReplay: Send + Sync {
async fn try_replay(
&self,
method: &Method,
uri: &Uri,
headers: &HeaderMap,
body: Option<&[u8]>,
session_id: Option<&str>,
) -> Result<Option<BehavioralReplayResponse>>;
}
#[derive(Debug, Clone)]
pub struct BehavioralReplayResponse {
pub status_code: u16,
pub headers: HashMap<String, String>,
pub body: Vec<u8>,
pub timing_ms: Option<u64>,
pub content_type: String,
}
pub struct PriorityRequest<'a> {
pub method: &'a Method,
pub uri: &'a Uri,
pub headers: &'a HeaderMap,
pub body: Option<&'a [u8]>,
pub fingerprint: &'a RequestFingerprint,
}
#[async_trait]
pub trait PriorityStep: Send + Sync {
fn name(&self) -> &str;
fn priority(&self) -> u16;
async fn try_handle(&self, req: &PriorityRequest<'_>) -> Result<Option<PriorityResponse>>;
}
pub struct PriorityHttpHandler {
custom_fixture_loader: Option<Arc<CustomFixtureLoader>>,
record_replay: RecordReplayHandler,
behavioral_scenario_replay: Option<Arc<dyn BehavioralScenarioReplay + Send + Sync>>,
stateful_handler: Option<Arc<StatefulResponseHandler>>,
route_chaos_injector: Option<Arc<dyn RouteChaosInjectorTrait>>,
failure_injector: Option<FailureInjector>,
proxy_handler: Option<ProxyHandler>,
mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
continuum_engine: Option<Arc<RealityContinuumEngine>>,
behavioral_economics_engine: Option<Arc<RwLock<BehavioralEconomicsEngine>>>,
#[allow(dead_code, clippy::type_complexity)]
request_metrics: Arc<RwLock<HashMap<String, (u64, u64, std::time::Instant)>>>,
}
#[derive(Debug, Clone)]
pub enum GenerationResult {
Generated(MockResponse),
NoMatchingSchema {
path: String,
method: String,
},
GeneratorDisabled,
AmbiguousOperation {
candidates: Vec<String>,
},
}
impl GenerationResult {
pub fn into_response(self) -> Option<MockResponse> {
match self {
Self::Generated(r) => Some(r),
_ => None,
}
}
pub fn is_generated(&self) -> bool {
matches!(self, Self::Generated(_))
}
}
pub trait MockGenerator {
fn generate_mock_response(
&self,
fingerprint: &RequestFingerprint,
headers: &HeaderMap,
body: Option<&[u8]>,
) -> Result<GenerationResult>;
}
#[derive(Debug, Clone)]
pub struct MockResponse {
pub status_code: u16,
pub headers: HashMap<String, String>,
pub body: String,
pub content_type: String,
}
impl PriorityHttpHandler {
pub fn new(
record_replay: RecordReplayHandler,
failure_injector: Option<FailureInjector>,
proxy_handler: Option<ProxyHandler>,
mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
) -> Self {
Self {
custom_fixture_loader: None,
record_replay,
behavioral_scenario_replay: None,
stateful_handler: None,
route_chaos_injector: None,
failure_injector,
proxy_handler,
mock_generator,
openapi_spec: None,
continuum_engine: None,
behavioral_economics_engine: None,
request_metrics: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn new_with_openapi(
record_replay: RecordReplayHandler,
failure_injector: Option<FailureInjector>,
proxy_handler: Option<ProxyHandler>,
mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
) -> Self {
Self {
custom_fixture_loader: None,
record_replay,
behavioral_scenario_replay: None,
stateful_handler: None,
route_chaos_injector: None,
failure_injector,
proxy_handler,
mock_generator,
openapi_spec,
continuum_engine: None,
behavioral_economics_engine: None,
request_metrics: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn with_custom_fixture_loader(mut self, loader: Arc<CustomFixtureLoader>) -> Self {
self.custom_fixture_loader = Some(loader);
self
}
pub fn with_stateful_handler(mut self, handler: Arc<StatefulResponseHandler>) -> Self {
self.stateful_handler = Some(handler);
self
}
pub fn with_route_chaos_injector(mut self, injector: Arc<dyn RouteChaosInjectorTrait>) -> Self {
self.route_chaos_injector = Some(injector);
self
}
pub fn with_continuum_engine(mut self, engine: Arc<RealityContinuumEngine>) -> Self {
self.continuum_engine = Some(engine);
self
}
pub fn with_behavioral_economics_engine(
mut self,
engine: Arc<RwLock<BehavioralEconomicsEngine>>,
) -> Self {
self.behavioral_economics_engine = Some(engine);
self
}
pub fn with_behavioral_scenario_replay(
mut self,
replay_engine: Arc<dyn BehavioralScenarioReplay + Send + Sync>,
) -> Self {
self.behavioral_scenario_replay = Some(replay_engine);
self
}
pub async fn process_request(
&self,
method: &Method,
uri: &Uri,
headers: &HeaderMap,
body: Option<&[u8]>,
) -> Result<PriorityResponse> {
let normalized_path = CustomFixtureLoader::normalize_path(uri.path());
let normalized_uri_str = if let Some(query) = uri.query() {
format!("{}?{}", normalized_path, query)
} else {
normalized_path
};
let normalized_uri = normalized_uri_str.parse::<Uri>().unwrap_or_else(|_| uri.clone());
let fingerprint = RequestFingerprint::new(method.clone(), &normalized_uri, headers, body);
if let Some(ref custom_loader) = self.custom_fixture_loader {
if let Some(custom_fixture) = custom_loader.load_fixture(&fingerprint) {
if custom_fixture.delay_ms > 0 {
tokio::time::sleep(tokio::time::Duration::from_millis(custom_fixture.delay_ms))
.await;
}
let response_body = match custom_fixture.response.as_str() {
Some(s) => s.to_string(),
None => serde_json::to_string(&custom_fixture.response).map_err(|e| {
Error::internal(format!(
"Failed to serialize custom fixture response: {}",
e
))
})?,
};
let content_type = custom_fixture
.headers
.get("content-type")
.cloned()
.unwrap_or_else(|| "application/json".to_string());
return Ok(PriorityResponse {
source: ResponseSource::new(
ResponsePriority::Replay,
"custom_fixture".to_string(),
)
.with_metadata("fixture_path".to_string(), custom_fixture.path.clone()),
status_code: custom_fixture.status,
headers: custom_fixture.headers.clone(),
body: response_body.into_bytes(),
content_type,
});
}
}
if let Some(recorded_request) =
self.record_replay.replay_handler().load_fixture(&fingerprint).await?
{
let content_type = recorded_request
.response_headers
.get("content-type")
.unwrap_or(&"application/json".to_string())
.clone();
return Ok(PriorityResponse {
source: ResponseSource::new(ResponsePriority::Replay, "replay".to_string())
.with_metadata("fixture_path".to_string(), "recorded".to_string()),
status_code: recorded_request.status_code,
headers: recorded_request.response_headers,
body: recorded_request.response_body.into_bytes(),
content_type,
});
}
if let Some(ref scenario_replay) = self.behavioral_scenario_replay {
let session_id = headers
.get("x-session-id")
.or_else(|| headers.get("session-id"))
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
if let Ok(Some(replay_response)) = scenario_replay
.try_replay(method, uri, headers, body, session_id.as_deref())
.await
{
if let Some(timing_ms) = replay_response.timing_ms {
tokio::time::sleep(tokio::time::Duration::from_millis(timing_ms)).await;
}
return Ok(PriorityResponse {
source: ResponseSource::new(
ResponsePriority::Replay,
"behavioral_scenario".to_string(),
)
.with_metadata("replay_type".to_string(), "scenario".to_string()),
status_code: replay_response.status_code,
headers: replay_response.headers,
body: replay_response.body,
content_type: replay_response.content_type,
});
}
}
if let Some(ref stateful_handler) = self.stateful_handler {
if let Some(stateful_response) =
stateful_handler.process_request(method, uri, headers, body).await?
{
return Ok(PriorityResponse {
source: ResponseSource::new(ResponsePriority::Stateful, "stateful".to_string())
.with_metadata("state".to_string(), stateful_response.state)
.with_metadata("resource_id".to_string(), stateful_response.resource_id),
status_code: stateful_response.status_code,
headers: stateful_response.headers,
body: stateful_response.body.into_bytes(),
content_type: stateful_response.content_type,
});
}
}
if let Some(ref route_chaos) = self.route_chaos_injector {
if let Err(e) = route_chaos.inject_latency(method, uri).await {
tracing::warn!("Failed to inject per-route latency: {}", e);
}
if let Some(fault_response) = route_chaos.get_fault_response(method, uri) {
let error_response = serde_json::json!({
"error": fault_response.error_message,
"injected_failure": true,
"fault_type": fault_response.fault_type,
"timestamp": chrono::Utc::now().to_rfc3339()
});
return Ok(PriorityResponse {
source: ResponseSource::new(
ResponsePriority::Fail,
"route_fault_injection".to_string(),
)
.with_metadata("fault_type".to_string(), fault_response.fault_type)
.with_metadata("error_message".to_string(), fault_response.error_message),
status_code: fault_response.status_code,
headers: HashMap::new(),
body: serde_json::to_string(&error_response)?.into_bytes(),
content_type: "application/json".to_string(),
});
}
}
if let Some(ref failure_injector) = self.failure_injector {
let tags = if let Some(ref spec) = self.openapi_spec {
fingerprint.openapi_tags(spec).unwrap_or_else(|| fingerprint.tags())
} else {
fingerprint.tags()
};
if let Some((status_code, error_message)) = failure_injector.process_request(&tags) {
let error_response = serde_json::json!({
"error": error_message,
"injected_failure": true,
"timestamp": chrono::Utc::now().to_rfc3339()
});
return Ok(PriorityResponse {
source: ResponseSource::new(
ResponsePriority::Fail,
"failure_injection".to_string(),
)
.with_metadata("error_message".to_string(), error_message),
status_code,
headers: HashMap::new(),
body: serde_json::to_string(&error_response)?.into_bytes(),
content_type: "application/json".to_string(),
});
}
}
let should_blend = if let Some(ref continuum_engine) = self.continuum_engine {
continuum_engine.is_enabled().await
} else {
false
};
if let Some(ref proxy_handler) = self.proxy_handler {
let migration_mode = if proxy_handler.config.migration_enabled {
proxy_handler.config.get_effective_migration_mode(uri.path())
} else {
None
};
if let Some(crate::proxy::config::MigrationMode::Mock) = migration_mode {
} else if proxy_handler.config.should_proxy_with_condition(method, uri, headers, body) {
let is_shadow = proxy_handler.config.should_shadow(uri.path());
if should_blend {
let proxy_future = proxy_handler.proxy_request(method, uri, headers, body);
let mock_result = if let Some(ref mock_generator) = self.mock_generator {
mock_generator
.generate_mock_response(&fingerprint, headers, body)
.map(|r| r.into_response())
} else {
Ok(None)
};
let proxy_result = proxy_future.await;
match (proxy_result, mock_result) {
(Ok(proxy_response), Ok(Some(mock_response))) => {
if let Some(ref continuum_engine) = self.continuum_engine {
let blend_ratio =
continuum_engine.get_blend_ratio(uri.path()).await;
let blender = continuum_engine.blender();
let mock_body_str = &mock_response.body;
let real_body_bytes =
proxy_response.body.clone().unwrap_or_default();
let real_body_str = String::from_utf8_lossy(&real_body_bytes);
let mock_json: serde_json::Value =
serde_json::from_str(mock_body_str)
.unwrap_or_else(|_| serde_json::json!({}));
let real_json: serde_json::Value =
serde_json::from_str(&real_body_str)
.unwrap_or_else(|_| serde_json::json!({}));
let blended_json =
blender.blend_responses(&mock_json, &real_json, blend_ratio);
let blended_body = serde_json::to_string(&blended_json)
.unwrap_or_else(|_| real_body_str.to_string());
let blended_status = blender.blend_status_code(
mock_response.status_code,
proxy_response.status_code,
blend_ratio,
);
let mut proxy_headers = HashMap::new();
for (key, value) in proxy_response.headers.iter() {
if let Ok(value_str) = value.to_str() {
proxy_headers.insert(
key.as_str().to_string(),
value_str.to_string(),
);
}
}
let blended_headers = blender.blend_headers(
&mock_response.headers,
&proxy_headers,
blend_ratio,
);
let content_type = blended_headers
.get("content-type")
.cloned()
.or_else(|| {
proxy_response
.headers
.get("content-type")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
})
.unwrap_or_else(|| "application/json".to_string());
tracing::info!(
path = %uri.path(),
blend_ratio = blend_ratio,
"Reality Continuum: blended mock and real responses"
);
let mut source = ResponseSource::new(
ResponsePriority::Proxy,
"continuum".to_string(),
)
.with_metadata("blend_ratio".to_string(), blend_ratio.to_string())
.with_metadata(
"upstream_url".to_string(),
proxy_handler.config.get_upstream_url(uri.path()),
);
if let Some(mode) = migration_mode {
source = source.with_metadata(
"migration_mode".to_string(),
format!("{:?}", mode),
);
}
return Ok(PriorityResponse {
source,
status_code: blended_status,
headers: blended_headers,
body: blended_body.into_bytes(),
content_type,
});
}
}
(Ok(_proxy_response), Ok(None)) => {
tracing::debug!(
path = %uri.path(),
"Continuum: mock generation failed, using real response"
);
}
(Ok(_proxy_response), Err(_)) => {
tracing::debug!(
path = %uri.path(),
"Continuum: mock generation failed, using real response"
);
}
(Err(e), Ok(Some(mock_response))) => {
tracing::debug!(
path = %uri.path(),
error = %e,
"Continuum: proxy failed, using mock response"
);
let mut source = ResponseSource::new(
ResponsePriority::Mock,
"continuum_fallback".to_string(),
)
.with_metadata("generated_from".to_string(), "openapi_spec".to_string())
.with_metadata(
"fallback_reason".to_string(),
"proxy_failed".to_string(),
);
if let Some(mode) = migration_mode {
source = source.with_metadata(
"migration_mode".to_string(),
format!("{:?}", mode),
);
}
return Ok(PriorityResponse {
source,
status_code: mock_response.status_code,
headers: mock_response.headers,
body: mock_response.body.into_bytes(),
content_type: mock_response.content_type,
});
}
(Err(e), _) => {
tracing::warn!(
path = %uri.path(),
error = %e,
"Continuum: both proxy and mock failed"
);
if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode
{
return Err(Error::internal(format!(
"Proxy request failed in real mode: {}",
e
)));
}
}
}
}
match proxy_handler.proxy_request(method, uri, headers, body).await {
Ok(proxy_response) => {
let mut response_headers = HashMap::new();
for (key, value) in proxy_response.headers.iter() {
let key_str = key.as_str();
if let Ok(value_str) = value.to_str() {
response_headers.insert(key_str.to_string(), value_str.to_string());
}
}
let content_type = response_headers
.get("content-type")
.unwrap_or(&"application/json".to_string())
.clone();
if is_shadow {
if let Some(ref mock_generator) = self.mock_generator {
if let Ok(GenerationResult::Generated(mock_response)) =
mock_generator.generate_mock_response(
&fingerprint,
headers,
body,
)
{
tracing::info!(
path = %uri.path(),
real_status = proxy_response.status_code,
mock_status = mock_response.status_code,
"Shadow mode: comparing real and mock responses"
);
let real_body_bytes =
proxy_response.body.clone().unwrap_or_default();
let real_body = String::from_utf8_lossy(&real_body_bytes);
let mock_body = &mock_response.body;
if real_body != *mock_body {
tracing::warn!(
path = %uri.path(),
"Shadow mode: real and mock responses differ"
);
}
}
}
}
let mut source = ResponseSource::new(
ResponsePriority::Proxy,
if is_shadow {
"shadow".to_string()
} else {
"proxy".to_string()
},
)
.with_metadata(
"upstream_url".to_string(),
proxy_handler.config.get_upstream_url(uri.path()),
);
if let Some(mode) = migration_mode {
source = source
.with_metadata("migration_mode".to_string(), format!("{:?}", mode));
}
return Ok(PriorityResponse {
source,
status_code: proxy_response.status_code,
headers: response_headers,
body: proxy_response.body.unwrap_or_default(),
content_type,
});
}
Err(e) => {
tracing::warn!("Proxy request failed: {}", e);
if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode {
return Err(Error::internal(format!(
"Proxy request failed in real mode: {}",
e
)));
}
}
}
}
}
if let Some(ref mock_generator) = self.mock_generator {
let migration_mode = if let Some(ref proxy_handler) = self.proxy_handler {
if proxy_handler.config.migration_enabled {
proxy_handler.config.get_effective_migration_mode(uri.path())
} else {
None
}
} else {
None
};
if let GenerationResult::Generated(mock_response) =
mock_generator.generate_mock_response(&fingerprint, headers, body)?
{
let mut source = ResponseSource::new(ResponsePriority::Mock, "mock".to_string())
.with_metadata("generated_from".to_string(), "openapi_spec".to_string());
if let Some(mode) = migration_mode {
source =
source.with_metadata("migration_mode".to_string(), format!("{:?}", mode));
}
return Ok(PriorityResponse {
source,
status_code: mock_response.status_code,
headers: mock_response.headers,
body: mock_response.body.into_bytes(),
content_type: mock_response.content_type,
});
}
}
if self.record_replay.record_handler().should_record(method) {
let default_response = serde_json::json!({
"message": "Request recorded for future replay",
"timestamp": chrono::Utc::now().to_rfc3339(),
"fingerprint": fingerprint.to_hash()
});
let response_body = serde_json::to_string(&default_response)?;
let status_code = 200;
self.record_replay
.record_handler()
.record_request(&fingerprint, status_code, headers, &response_body, None)
.await?;
return Ok(PriorityResponse {
source: ResponseSource::new(ResponsePriority::Record, "record".to_string())
.with_metadata("recorded".to_string(), "true".to_string()),
status_code,
headers: HashMap::new(),
body: response_body.into_bytes(),
content_type: "application/json".to_string(),
});
}
Err(Error::internal("No handler could process the request".to_string()))
}
#[allow(dead_code)]
async fn apply_behavioral_economics(
&self,
response: PriorityResponse,
_method: &Method,
uri: &Uri,
latency_ms: Option<u64>,
) -> Result<PriorityResponse> {
if let Some(ref engine) = self.behavioral_economics_engine {
let engine = engine.read().await;
let evaluator = engine.condition_evaluator();
{
let mut eval = evaluator.write().await;
if let Some(latency) = latency_ms {
eval.update_latency(uri.path(), latency);
}
let endpoint = uri.path().to_string();
let mut metrics = self.request_metrics.write().await;
let now = std::time::Instant::now();
let (request_count, error_count, last_request_time) =
metrics.entry(endpoint.clone()).or_insert_with(|| (0, 0, now));
*request_count += 1;
if response.status_code >= 400 {
*error_count += 1;
}
let error_rate = if *request_count > 0 {
*error_count as f64 / *request_count as f64
} else {
0.0
};
eval.update_error_rate(&endpoint, error_rate);
let time_elapsed = now.duration_since(*last_request_time).as_secs_f64();
if time_elapsed > 0.0 {
let rps = *request_count as f64 / time_elapsed.max(1.0);
eval.update_load(rps);
}
if time_elapsed > 60.0 {
*request_count = 1;
*error_count = if response.status_code >= 400 { 1 } else { 0 };
*last_request_time = now;
} else {
*last_request_time = now;
}
}
let executed_actions = engine.evaluate().await?;
if !executed_actions.is_empty() {
tracing::debug!(
"Behavioral economics engine executed {} actions",
executed_actions.len()
);
}
}
Ok(response)
}
}
#[derive(Debug, Clone)]
pub struct PriorityResponse {
pub source: ResponseSource,
pub status_code: u16,
pub headers: HashMap<String, String>,
pub body: Vec<u8>,
pub content_type: String,
}
impl PriorityResponse {
pub fn to_axum_response(self) -> axum::response::Response {
let mut response = axum::response::Response::new(axum::body::Body::from(self.body));
*response.status_mut() = StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::OK);
for (key, value) in self.headers {
if let (Ok(header_name), Ok(header_value)) =
(key.parse::<axum::http::HeaderName>(), value.parse::<axum::http::HeaderValue>())
{
response.headers_mut().insert(header_name, header_value);
}
}
if !response.headers().contains_key("content-type") {
if let Ok(header_value) = self.content_type.parse::<axum::http::HeaderValue>() {
response.headers_mut().insert("content-type", header_value);
}
}
response
}
}
pub struct CustomFixtureStep {
loader: Arc<CustomFixtureLoader>,
}
impl CustomFixtureStep {
pub fn new(loader: Arc<CustomFixtureLoader>) -> Self {
Self { loader }
}
}
#[async_trait]
impl PriorityStep for CustomFixtureStep {
fn name(&self) -> &str {
"custom_fixture"
}
fn priority(&self) -> u16 {
0
}
async fn try_handle(&self, req: &PriorityRequest<'_>) -> Result<Option<PriorityResponse>> {
if let Some(custom_fixture) = self.loader.load_fixture(req.fingerprint) {
if custom_fixture.delay_ms > 0 {
tokio::time::sleep(tokio::time::Duration::from_millis(custom_fixture.delay_ms))
.await;
}
let response_body = match custom_fixture.response.as_str() {
Some(s) => s.to_string(),
None => serde_json::to_string(&custom_fixture.response)
.map_err(|e| Error::internal(format!("Failed to serialize fixture: {}", e)))?,
};
let content_type = custom_fixture
.headers
.get("content-type")
.cloned()
.unwrap_or_else(|| "application/json".to_string());
return Ok(Some(PriorityResponse {
source: ResponseSource::new(ResponsePriority::Replay, "custom_fixture".to_string())
.with_metadata("fixture_path".to_string(), custom_fixture.path.clone()),
status_code: custom_fixture.status,
headers: custom_fixture.headers.clone(),
body: response_body.into_bytes(),
content_type,
}));
}
Ok(None)
}
}
pub struct FailureInjectionStep {
injector: FailureInjector,
spec: Option<crate::openapi::spec::OpenApiSpec>,
}
impl FailureInjectionStep {
pub fn new(injector: FailureInjector, spec: Option<crate::openapi::spec::OpenApiSpec>) -> Self {
Self { injector, spec }
}
}
#[async_trait]
impl PriorityStep for FailureInjectionStep {
fn name(&self) -> &str {
"failure_injection"
}
fn priority(&self) -> u16 {
300
}
async fn try_handle(&self, req: &PriorityRequest<'_>) -> Result<Option<PriorityResponse>> {
let tags = if let Some(ref spec) = self.spec {
req.fingerprint.openapi_tags(spec).unwrap_or_else(|| req.fingerprint.tags())
} else {
req.fingerprint.tags()
};
if let Some((status_code, error_message)) = self.injector.process_request(&tags) {
let error_response = serde_json::json!({
"error": error_message,
"injected_failure": true,
"timestamp": chrono::Utc::now().to_rfc3339()
});
return Ok(Some(PriorityResponse {
source: ResponseSource::new(
ResponsePriority::Fail,
"failure_injection".to_string(),
)
.with_metadata("error_message".to_string(), error_message),
status_code,
headers: HashMap::new(),
body: serde_json::to_string(&error_response)?.into_bytes(),
content_type: "application/json".to_string(),
}));
}
Ok(None)
}
}
pub struct SimpleMockGenerator {
pub default_status: u16,
pub default_body: String,
}
impl SimpleMockGenerator {
pub fn new(default_status: u16, default_body: String) -> Self {
Self {
default_status,
default_body,
}
}
}
impl MockGenerator for SimpleMockGenerator {
fn generate_mock_response(
&self,
_fingerprint: &RequestFingerprint,
_headers: &HeaderMap,
_body: Option<&[u8]>,
) -> Result<GenerationResult> {
Ok(GenerationResult::Generated(MockResponse {
status_code: self.default_status,
headers: HashMap::new(),
body: self.default_body.clone(),
content_type: "application/json".to_string(),
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
struct MockRouteChaosInjector;
#[async_trait]
impl RouteChaosInjectorTrait for MockRouteChaosInjector {
async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
Ok(())
}
fn get_fault_response(&self, _method: &Method, _uri: &Uri) -> Option<RouteFaultResponse> {
Some(RouteFaultResponse {
status_code: 503,
error_message: "Service unavailable".to_string(),
fault_type: "test_fault".to_string(),
})
}
}
struct MockBehavioralScenarioReplay;
#[async_trait]
impl BehavioralScenarioReplay for MockBehavioralScenarioReplay {
async fn try_replay(
&self,
_method: &Method,
_uri: &Uri,
_headers: &HeaderMap,
_body: Option<&[u8]>,
_session_id: Option<&str>,
) -> Result<Option<BehavioralReplayResponse>> {
Ok(Some(BehavioralReplayResponse {
status_code: 200,
headers: HashMap::new(),
body: b"scenario response".to_vec(),
timing_ms: Some(100),
content_type: "application/json".to_string(),
}))
}
}
#[tokio::test]
async fn test_priority_chain() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock response"}"#.to_string()));
let handler = PriorityHttpHandler::new_with_openapi(
record_replay,
None, None, Some(mock_generator),
None, );
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "mock");
}
#[tokio::test]
async fn test_builder_methods() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
let mock_generator = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator));
let custom_loader = Arc::new(CustomFixtureLoader::new(temp_dir.path().to_path_buf(), true));
let handler = handler.with_custom_fixture_loader(custom_loader);
assert!(handler.custom_fixture_loader.is_some());
let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
let handler = handler.with_stateful_handler(stateful_handler);
assert!(handler.stateful_handler.is_some());
let route_chaos = Arc::new(MockRouteChaosInjector);
let handler = handler.with_route_chaos_injector(route_chaos);
assert!(handler.route_chaos_injector.is_some());
let continuum_engine = Arc::new(RealityContinuumEngine::new(
crate::reality_continuum::config::ContinuumConfig::default(),
));
let handler = handler.with_continuum_engine(continuum_engine);
assert!(handler.continuum_engine.is_some());
let behavioral_engine = Arc::new(RwLock::new(
BehavioralEconomicsEngine::new(
crate::behavioral_economics::config::BehavioralEconomicsConfig::default(),
)
.unwrap(),
));
let handler = handler.with_behavioral_economics_engine(behavioral_engine);
assert!(handler.behavioral_economics_engine.is_some());
let scenario_replay = Arc::new(MockBehavioralScenarioReplay);
let handler = handler.with_behavioral_scenario_replay(scenario_replay);
assert!(handler.behavioral_scenario_replay.is_some());
}
#[tokio::test]
async fn test_custom_fixture_priority() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let custom_loader = Arc::new(CustomFixtureLoader::new(temp_dir.path().to_path_buf(), true));
let fixture_path = temp_dir.path().join("custom_fixture.json");
std::fs::write(
&fixture_path,
r#"{"status": 201, "response": {"message": "custom"}, "headers": {"x-custom": "value"}}"#,
)
.unwrap();
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_custom_fixture_loader(custom_loader);
let _method = Method::GET;
let _uri = Uri::from_static("/api/test");
let _headers = HeaderMap::new();
let _handler = handler; }
#[tokio::test]
async fn test_route_chaos_injection() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
let route_chaos = Arc::new(MockRouteChaosInjector);
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_route_chaos_injector(route_chaos);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await;
if let Ok(resp) = response {
assert_eq!(resp.status_code, 503);
assert_eq!(resp.source.source_type, "route_fault_injection");
}
}
#[tokio::test]
async fn test_behavioral_scenario_replay() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
let scenario_replay = Arc::new(MockBehavioralScenarioReplay);
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_behavioral_scenario_replay(scenario_replay);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let mut headers = HeaderMap::new();
headers.insert("x-session-id", "test-session".parse().unwrap());
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "behavioral_scenario");
assert_eq!(response.body, b"scenario response");
}
#[tokio::test]
async fn test_priority_response_to_axum() {
let response = PriorityResponse {
source: ResponseSource::new(ResponsePriority::Mock, "test".to_string()),
status_code: 201,
headers: {
let mut h = HashMap::new();
h.insert("x-custom".to_string(), "value".to_string());
h
},
body: b"test body".to_vec(),
content_type: "application/json".to_string(),
};
let axum_response = response.to_axum_response();
assert_eq!(axum_response.status(), StatusCode::CREATED);
}
#[tokio::test]
async fn test_simple_mock_generator() {
let generator = SimpleMockGenerator::new(404, r#"{"error": "not found"}"#.to_string());
let fingerprint = RequestFingerprint::new(
Method::GET,
&Uri::from_static("/api/test"),
&HeaderMap::new(),
None,
);
let result =
generator.generate_mock_response(&fingerprint, &HeaderMap::new(), None).unwrap();
assert!(result.is_generated());
let mock_response = result.into_response().unwrap();
assert_eq!(mock_response.status_code, 404);
assert_eq!(mock_response.body, r#"{"error": "not found"}"#);
}
#[tokio::test]
async fn test_new_vs_new_with_openapi() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let _record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let _mock_generator = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
let record_replay1 = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let mock_generator1 = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
let handler1 = PriorityHttpHandler::new(record_replay1, None, None, Some(mock_generator1));
assert!(handler1.openapi_spec.is_none());
let record_replay2 = RecordReplayHandler::new(fixtures_dir, true, true, false);
let mock_generator2 = Box::new(SimpleMockGenerator::new(200, "{}".to_string()));
let openapi_spec = crate::openapi::spec::OpenApiSpec::from_string(
r#"openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths:
/test:
get:
responses:
'200':
description: OK
"#,
Some("yaml"),
)
.unwrap();
let handler2 = PriorityHttpHandler::new_with_openapi(
record_replay2,
None,
None,
Some(mock_generator2),
Some(openapi_spec),
);
assert!(handler2.openapi_spec.is_some());
}
#[tokio::test]
async fn test_custom_fixture_with_delay() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let fixture_content = r#"{
"method": "GET",
"path": "/api/test",
"status": 200,
"response": {"message": "delayed response"},
"delay_ms": 10
}"#;
let fixture_file = fixtures_dir.join("test.json");
std::fs::write(&fixture_file, fixture_content).unwrap();
let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
custom_loader.load_fixtures().await.unwrap();
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_custom_fixture_loader(Arc::new(custom_loader));
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let start = std::time::Instant::now();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
let elapsed = start.elapsed();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "custom_fixture");
assert!(elapsed.as_millis() >= 10); }
#[tokio::test]
async fn test_custom_fixture_with_non_string_response() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let fixture_content = r#"{
"method": "GET",
"path": "/api/test",
"status": 201,
"response": {"id": 123, "name": "test"},
"headers": {"content-type": "application/json"}
}"#;
let fixture_file = fixtures_dir.join("test.json");
std::fs::write(&fixture_file, fixture_content).unwrap();
let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
custom_loader.load_fixtures().await.unwrap();
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_custom_fixture_loader(Arc::new(custom_loader));
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 201);
assert_eq!(response.source.source_type, "custom_fixture");
assert!(!response.body.is_empty());
let body_str = String::from_utf8_lossy(&response.body);
assert!(body_str.contains("id"));
}
#[tokio::test]
async fn test_custom_fixture_with_custom_content_type() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let fixture_content = r#"{
"method": "GET",
"path": "/api/test",
"status": 200,
"response": "text response",
"headers": {"content-type": "text/plain"}
}"#;
let fixture_file = fixtures_dir.join("test.json");
std::fs::write(&fixture_file, fixture_content).unwrap();
let mut custom_loader = CustomFixtureLoader::new(fixtures_dir.clone(), true);
custom_loader.load_fixtures().await.unwrap();
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_custom_fixture_loader(Arc::new(custom_loader));
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.content_type, "text/plain");
}
#[tokio::test]
async fn test_stateful_handler_path() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_stateful_handler(stateful_handler);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let _response = handler.process_request(&method, &uri, &headers, None).await;
}
#[tokio::test]
async fn test_route_chaos_latency_injection() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
struct LatencyInjector;
#[async_trait]
impl RouteChaosInjectorTrait for LatencyInjector {
async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
tokio::time::sleep(tokio::time::Duration::from_millis(20)).await;
Ok(())
}
fn get_fault_response(
&self,
_method: &Method,
_uri: &Uri,
) -> Option<RouteFaultResponse> {
None
}
}
let route_chaos = Arc::new(LatencyInjector);
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_route_chaos_injector(route_chaos);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let start = std::time::Instant::now();
let _response = handler.process_request(&method, &uri, &headers, None).await;
let elapsed = start.elapsed();
assert!(elapsed.as_millis() >= 20);
}
#[tokio::test]
async fn test_failure_injection_path() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let failure_config = crate::failure_injection::FailureConfig {
global_error_rate: 1.0, default_status_codes: vec![500], ..Default::default()
};
let failure_injector = FailureInjector::new(Some(failure_config), true);
let openapi_spec = crate::openapi::spec::OpenApiSpec::from_string(
r#"openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths:
/api/test:
get:
tags: [test]
responses:
'200':
description: OK
"#,
Some("yaml"),
)
.unwrap();
let handler = PriorityHttpHandler::new_with_openapi(
record_replay,
Some(failure_injector),
None,
None,
Some(openapi_spec),
);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 500);
assert_eq!(response.source.source_type, "failure_injection");
let body_str = String::from_utf8_lossy(&response.body);
assert!(body_str.contains("Injected failure")); }
#[tokio::test]
async fn test_record_handler_path() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, true, true);
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator));
let method = Method::POST; let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "mock");
}
#[tokio::test]
async fn test_behavioral_economics_engine_path() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
let be_config = crate::behavioral_economics::config::BehavioralEconomicsConfig::default();
let be_engine = Arc::new(RwLock::new(BehavioralEconomicsEngine::new(be_config).unwrap()));
let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
.with_behavioral_economics_engine(be_engine);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
}
#[tokio::test]
async fn test_replay_handler_with_recorded_fixture() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let mut headers = HeaderMap::new();
headers.insert("content-type", "application/json".parse().unwrap());
let fingerprint = RequestFingerprint::new(method.clone(), &uri, &headers, None);
record_replay
.record_handler()
.record_request(
&fingerprint,
200,
&headers,
r#"{"message": "recorded response"}"#,
None,
)
.await
.unwrap();
let handler = PriorityHttpHandler::new(record_replay, None, None, None);
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "replay");
let body_str = String::from_utf8_lossy(&response.body);
assert!(body_str.contains("recorded response"));
}
#[tokio::test]
async fn test_behavioral_scenario_replay_with_cookies() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
struct CookieScenarioReplay;
#[async_trait]
impl BehavioralScenarioReplay for CookieScenarioReplay {
async fn try_replay(
&self,
_method: &Method,
_uri: &Uri,
_headers: &HeaderMap,
_body: Option<&[u8]>,
session_id: Option<&str>,
) -> Result<Option<BehavioralReplayResponse>> {
if session_id == Some("header-session-123") {
Ok(Some(BehavioralReplayResponse {
status_code: 200,
headers: HashMap::new(),
body: b"header scenario response".to_vec(),
timing_ms: None,
content_type: "application/json".to_string(),
}))
} else {
Ok(None)
}
}
}
let scenario_replay = Arc::new(CookieScenarioReplay);
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_behavioral_scenario_replay(scenario_replay);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let mut headers = HeaderMap::new();
headers.insert("session-id", "header-session-123".parse().unwrap());
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "behavioral_scenario");
let body_str = String::from_utf8_lossy(&response.body);
assert!(body_str.contains("header scenario"));
}
#[tokio::test]
async fn test_route_chaos_latency_error_handling() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
struct ErrorLatencyInjector;
#[async_trait]
impl RouteChaosInjectorTrait for ErrorLatencyInjector {
async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
Err(Error::internal("Latency injection failed".to_string()))
}
fn get_fault_response(
&self,
_method: &Method,
_uri: &Uri,
) -> Option<RouteFaultResponse> {
None
}
}
let route_chaos = Arc::new(ErrorLatencyInjector);
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "test"}"#.to_string()));
let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
.with_route_chaos_injector(route_chaos);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
}
#[tokio::test]
async fn test_behavioral_scenario_replay_with_timing_delay() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
struct TimingScenarioReplay;
#[async_trait]
impl BehavioralScenarioReplay for TimingScenarioReplay {
async fn try_replay(
&self,
_method: &Method,
_uri: &Uri,
_headers: &HeaderMap,
_body: Option<&[u8]>,
_session_id: Option<&str>,
) -> Result<Option<BehavioralReplayResponse>> {
Ok(Some(BehavioralReplayResponse {
status_code: 200,
headers: HashMap::new(),
body: b"delayed response".to_vec(),
timing_ms: Some(15), content_type: "application/json".to_string(),
}))
}
}
let scenario_replay = Arc::new(TimingScenarioReplay);
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_behavioral_scenario_replay(scenario_replay);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let start = std::time::Instant::now();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
let elapsed = start.elapsed();
assert_eq!(response.status_code, 200);
assert!(elapsed.as_millis() >= 15); }
#[tokio::test]
async fn test_stateful_handler_with_response() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_stateful_handler(stateful_handler);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let _result = handler.process_request(&method, &uri, &headers, None).await;
}
#[tokio::test]
async fn test_replay_handler_content_type_extraction() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let mut headers = HeaderMap::new();
headers.insert("content-type", "application/xml".parse().unwrap());
let fingerprint = RequestFingerprint::new(method.clone(), &uri, &headers, None);
record_replay
.record_handler()
.record_request(&fingerprint, 200, &headers, r#"<xml>test</xml>"#, None)
.await
.unwrap();
let handler = PriorityHttpHandler::new(record_replay, None, None, None);
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.content_type, "application/xml");
}
#[tokio::test]
async fn test_proxy_migration_mode_mock() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let mut proxy_config =
crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
proxy_config.migration_enabled = true;
proxy_config.rules.push(crate::proxy::config::ProxyRule {
path_pattern: "/api/*".to_string(),
target_url: "http://localhost:8080".to_string(),
enabled: true,
pattern: "/api/*".to_string(),
upstream_url: "http://localhost:8080".to_string(),
migration_mode: crate::proxy::config::MigrationMode::Mock, migration_group: None,
condition: None,
});
let proxy_handler = ProxyHandler::new(proxy_config);
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
let handler = PriorityHttpHandler::new(
record_replay,
None,
Some(proxy_handler),
Some(mock_generator),
);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "mock");
}
#[tokio::test]
async fn test_proxy_migration_mode_disabled() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let mut proxy_config =
crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
proxy_config.migration_enabled = false; proxy_config.enabled = false;
let proxy_handler = ProxyHandler::new(proxy_config);
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
let handler = PriorityHttpHandler::new(
record_replay,
None,
Some(proxy_handler),
Some(mock_generator),
);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "mock");
}
#[tokio::test]
async fn test_continuum_engine_enabled_check() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let continuum_config = crate::reality_continuum::config::ContinuumConfig::new();
let continuum_engine = Arc::new(RealityContinuumEngine::new(continuum_config));
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
.with_continuum_engine(continuum_engine);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
}
#[tokio::test]
async fn test_behavioral_scenario_replay_error_handling() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
struct ErrorScenarioReplay;
#[async_trait]
impl BehavioralScenarioReplay for ErrorScenarioReplay {
async fn try_replay(
&self,
_method: &Method,
_uri: &Uri,
_headers: &HeaderMap,
_body: Option<&[u8]>,
_session_id: Option<&str>,
) -> Result<Option<BehavioralReplayResponse>> {
Err(Error::internal("Scenario replay error".to_string()))
}
}
let scenario_replay = Arc::new(ErrorScenarioReplay);
let mock_generator =
Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock"}"#.to_string()));
let handler = PriorityHttpHandler::new(record_replay, None, None, Some(mock_generator))
.with_behavioral_scenario_replay(scenario_replay);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "mock");
}
#[tokio::test]
async fn test_behavioral_scenario_replay_with_session_id_header() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
struct SessionScenarioReplay;
#[async_trait]
impl BehavioralScenarioReplay for SessionScenarioReplay {
async fn try_replay(
&self,
_method: &Method,
_uri: &Uri,
_headers: &HeaderMap,
_body: Option<&[u8]>,
session_id: Option<&str>,
) -> Result<Option<BehavioralReplayResponse>> {
if session_id == Some("header-session-456") {
Ok(Some(BehavioralReplayResponse {
status_code: 200,
headers: HashMap::new(),
body: b"header session response".to_vec(),
timing_ms: None,
content_type: "application/json".to_string(),
}))
} else {
Ok(None)
}
}
}
let scenario_replay = Arc::new(SessionScenarioReplay);
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_behavioral_scenario_replay(scenario_replay);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let mut headers = HeaderMap::new();
headers.insert("x-session-id", "header-session-456".parse().unwrap());
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "behavioral_scenario");
}
#[tokio::test]
async fn test_stateful_handler_returns_response() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let stateful_handler = Arc::new(StatefulResponseHandler::new().unwrap());
let mut state_responses = HashMap::new();
state_responses.insert(
"initial".to_string(),
crate::stateful_handler::StateResponse {
status_code: 200,
headers: HashMap::new(),
body_template: r#"{"status": "initial", "order_id": "123"}"#.to_string(),
content_type: "application/json".to_string(),
},
);
let config = crate::stateful_handler::StatefulConfig {
resource_id_extract: crate::stateful_handler::ResourceIdExtract::PathParam {
param: "order_id".to_string(),
},
resource_type: "order".to_string(),
state_responses,
transitions: vec![],
};
stateful_handler.add_config("/api/orders/{order_id}".to_string(), config).await;
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_stateful_handler(stateful_handler);
let method = Method::GET;
let uri = Uri::from_static("/api/orders/123");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "stateful");
assert_eq!(response.source.metadata.get("state"), Some(&"initial".to_string()));
assert_eq!(response.source.metadata.get("resource_id"), Some(&"123".to_string()));
}
#[tokio::test]
async fn test_record_handler_path_with_no_other_handlers() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, true, false);
let handler = PriorityHttpHandler::new(record_replay, None, None, None);
let method = Method::GET; let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "record");
let body_str = String::from_utf8_lossy(&response.body);
assert!(body_str.contains("Request recorded"));
}
#[tokio::test]
async fn test_mock_generator_with_migration_mode() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
let mut proxy_config =
crate::proxy::config::ProxyConfig::new("http://localhost:8080".to_string());
proxy_config.migration_enabled = true;
proxy_config.rules.push(crate::proxy::config::ProxyRule {
path_pattern: "/api/*".to_string(),
target_url: "http://localhost:8080".to_string(),
enabled: true,
pattern: "/api/*".to_string(),
upstream_url: "http://localhost:8080".to_string(),
migration_mode: crate::proxy::config::MigrationMode::Mock,
migration_group: None,
condition: None,
});
proxy_config.enabled = false;
let proxy_handler = ProxyHandler::new(proxy_config);
let mock_generator = Box::new(SimpleMockGenerator::new(
200,
r#"{"message": "mock with migration"}"#.to_string(),
));
let handler = PriorityHttpHandler::new(
record_replay,
None,
Some(proxy_handler),
Some(mock_generator),
);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.source.source_type, "mock");
let body_str = String::from_utf8_lossy(&response.body);
assert!(body_str.contains("mock with migration"));
}
#[tokio::test]
async fn test_no_handler_can_process_request() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), false, false, false);
let handler = PriorityHttpHandler::new(record_replay, None, None, None);
let method = Method::GET;
let uri = Uri::from_static("/api/test");
let headers = HeaderMap::new();
let result = handler.process_request(&method, &uri, &headers, None).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No handler could process"));
}
#[tokio::test]
async fn test_route_chaos_fault_injection() {
let temp_dir = TempDir::new().unwrap();
let fixtures_dir = temp_dir.path().to_path_buf();
let record_replay = RecordReplayHandler::new(fixtures_dir.clone(), true, true, false);
struct FaultInjector;
#[async_trait]
impl RouteChaosInjectorTrait for FaultInjector {
async fn inject_latency(&self, _method: &Method, _uri: &Uri) -> Result<()> {
Ok(())
}
fn get_fault_response(&self, method: &Method, uri: &Uri) -> Option<RouteFaultResponse> {
if method == Method::GET && uri.path() == "/api/faulty" {
Some(RouteFaultResponse {
status_code: 503,
error_message: "Service unavailable".to_string(),
fault_type: "injected_fault".to_string(),
})
} else {
None
}
}
}
let route_chaos = Arc::new(FaultInjector);
let handler = PriorityHttpHandler::new(record_replay, None, None, None)
.with_route_chaos_injector(route_chaos);
let method = Method::GET;
let uri = Uri::from_static("/api/faulty");
let headers = HeaderMap::new();
let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
assert_eq!(response.status_code, 503);
let body_str = String::from_utf8_lossy(&response.body);
assert!(body_str.contains("Service unavailable"));
assert!(body_str.contains("injected_failure"));
}
}