use crate::helpers::client::E2eClient;
use crate::helpers::constants::{category_enabled, Suite};
use crate::helpers::docker;
use crate::helpers::report::TestReport;
use crate::run_test;
use std::time::Duration;
pub fn run(client: &E2eClient, suite: Suite, report: &mut TestReport) {
if !category_enabled("resilience") {
return;
}
if suite.is_all() {
eprintln!();
eprintln!("\x1b[1mFailure Injection Tests\x1b[0m");
eprintln!("--------------------------------------------");
run_test!(report, "resilience", "Failover: both healthy", {
let (_, pool) = client.pool();
let healthy = pool["stats"]["healthy_upstreams"].as_u64().unwrap_or(0);
assert!(
healthy >= 2,
"Expected >= 2 healthy upstreams, got {healthy}"
);
});
docker::stop_container("conproxy-cmeili-2");
std::thread::sleep(Duration::from_secs(3));
for i in 0..5 {
let _ = client.query(&format!("failover probe {i}"));
}
std::thread::sleep(Duration::from_secs(2));
run_test!(report, "resilience", "Failover: query via primary", {
let (status, body) = client.query("failover test query");
assert_eq!(status, 200);
assert!(
body["results"].is_array(),
"Missing results during failover"
);
});
run_test!(report, "resilience", "Failover: failures detected", {
let (_, pool) = client.pool();
let failures = pool["stats"]["total_failures"].as_u64().unwrap_or(0);
assert!(failures > 0, "Expected failures > 0, got {failures}");
});
docker::start_container("conproxy-cmeili-2");
std::thread::sleep(Duration::from_secs(12));
run_test!(report, "resilience", "Failover: es-secondary back", {
let check = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(5))
.build()
.unwrap();
let resp = check
.get("http://localhost:7701/health")
.header("Authorization", "Bearer conproxy_test_key")
.send();
assert!(
resp.is_ok() && resp.unwrap().status().is_success(),
"Meili secondary not back"
);
});
run_test!(report, "resilience", "Degradation: starts Full", {
let (_, h) = client.health();
let level = h["degradation_level"].as_str().unwrap_or("");
assert!(
level.eq_ignore_ascii_case("full") || h["degradation_level"] == 0,
"Expected Full degradation level, got: {}",
h["degradation_level"]
);
});
run_test!(report, "resilience", "Circuit: starts closed", {
let (_, cb) = client.circuit();
assert_eq!(cb["state"].as_str(), Some("closed"));
});
docker::stop_container("conproxy-cmeili-1");
docker::stop_container("conproxy-cmeili-2");
std::thread::sleep(Duration::from_secs(2));
for i in 0..8 {
let _ = client.query(&format!("circuit test {i}"));
}
std::thread::sleep(Duration::from_secs(1));
run_test!(report, "resilience", "Circuit: tripped", {
let (_, cb) = client.circuit();
let tripped = cb["times_tripped"].as_u64().unwrap_or(0);
let opened = cb["times_opened"].as_u64().unwrap_or(0);
let failures = cb["failure_count"].as_u64().unwrap_or(0);
let state = cb["state"].as_str().unwrap_or("closed");
let (_, pool) = client.pool();
let pool_failures = pool["stats"]["total_failures"].as_u64().unwrap_or(0);
assert!(
tripped > 0
|| opened > 0
|| failures > 0
|| state != "closed"
|| pool_failures > 0,
"Expected circuit/pool failure activity, state={state}, tripped={tripped}, opened={opened}, failures={failures}, pool_failures={pool_failures}"
);
});
docker::start_container("conproxy-cmeili-1");
docker::start_container("conproxy-cmeili-2");
std::thread::sleep(Duration::from_secs(35));
run_test!(report, "resilience", "Circuit: recovering", {
let (_, cb) = client.circuit();
let state = cb["state"].as_str().unwrap_or("unknown");
assert!(
state == "closed" || state == "half_open",
"Expected closed or half_open, got {state}"
);
});
eprintln!("--------------------------------------------");
}
if suite.has_text_upstreams() {
eprintln!();
eprintln!("\x1b[1mRequest Coalescing Test\x1b[0m");
eprintln!("--------------------------------------------");
client.cache_clear();
let handles: Vec<_> = (0..5)
.map(|_| {
let url = format!("{}/query", crate::helpers::constants::proxy_url());
std::thread::spawn(move || {
let c = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.build()
.unwrap();
let _ = c
.post(&url)
.json(&serde_json::json!({"query": "coalescing test identical query"}))
.send();
})
})
.collect();
for h in handles {
let _ = h.join();
}
run_test!(report, "resilience", "Coalescing: metric tracked", {
let (_, prom) = client.prometheus();
assert!(
prom.contains("conproxy_coalesced_requests_total"),
"Missing coalescing metric"
);
});
eprintln!("--------------------------------------------");
}
}