use crate::helpers::client::E2eClient;
use crate::helpers::config::ConfigManager;
use crate::helpers::constants::{category_enabled, proxy_url, Suite};
use crate::helpers::metrics::snapshot_and_reset;
use crate::helpers::proxy::ProxyProcess;
use crate::helpers::report::TestReport;
use crate::{assert_section, run_test};
use std::path::PathBuf;
use std::time::Duration;
pub fn run(suite: Suite, report: &mut TestReport) {
if !category_enabled("advanced") || !suite.is_all() {
return;
}
eprintln!();
eprintln!("\x1b[1mAdvanced Feature Tests (proxy restart)\x1b[0m");
eprintln!("--------------------------------------------");
let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut config = ConfigManager::new(&project_root);
config.write_advanced_config();
eprintln!("\x1b[32m[INFO]\x1b[0m Starting proxy with rate limit + auth + short TTL...");
let mut proxy = ProxyProcess::start("127.0.0.1:8080");
if let Err(e) = proxy.wait_healthy(Duration::from_secs(5)) {
eprintln!("\x1b[31m[ERROR]\x1b[0m Advanced proxy failed to start: {e}");
proxy.stop();
config.restore();
return;
}
let client_no_auth = E2eClient::new(proxy_url());
let client_auth = E2eClient::new(proxy_url()).with_api_key("e2e-test-key");
run_auth(&client_no_auth, &client_auth, report);
let s = snapshot_and_reset(&client_auth, "auth", report);
assert_section!(report, s, "auth", max_errors: 0);
run_rate_limit(&client_auth, report);
let s = snapshot_and_reset(&client_auth, "rate-limit", report);
assert_section!(report, s, "rate-limit", max_errors: 0);
std::thread::sleep(Duration::from_secs(1));
run_ttl(&client_auth, report);
let s = snapshot_and_reset(&client_auth, "ttl", report);
assert_section!(report, s, "ttl", min_hits: 1, max_errors: 0);
eprintln!("\x1b[32m[INFO]\x1b[0m Stopping advanced proxy...");
proxy.stop();
config.restore();
eprintln!("--------------------------------------------");
}
fn run_auth(client_no_auth: &E2eClient, client_auth: &E2eClient, report: &mut TestReport) {
eprintln!();
eprintln!("\x1b[32m[INFO]\x1b[0m Auth tests...");
run_test!(report, "auth", "Auth: health without key", {
let (status, body) = client_no_auth.health();
assert_eq!(status, 200);
assert!(body["status"].is_string());
});
run_test!(report, "auth", "Auth: ready without key", {
let (status, body) = client_no_auth.ready();
assert_eq!(status, 200);
assert!(!body["ready"].is_null());
});
run_test!(report, "auth", "Auth: query without key (401)", {
let (status, _) = client_no_auth.query_no_auth("auth test");
assert_eq!(status, 401, "Expected 401 without key, got {status}");
});
run_test!(report, "auth", "Auth: wrong key (401)", {
let (status, _) = client_no_auth.query_with_key("auth test", "wrong-key");
assert_eq!(status, 401, "Expected 401 with wrong key, got {status}");
});
run_test!(report, "auth", "Auth: valid X-API-Key (200)", {
let (status, body) = client_auth.query("auth test");
assert_eq!(status, 200);
assert!(body["results"].is_array(), "Missing results with valid key");
});
run_test!(report, "auth", "Auth: valid Bearer (200)", {
let (status, body) = client_no_auth.query_with_bearer("auth bearer test", "e2e-test-key");
assert_eq!(status, 200);
assert!(body["results"].is_array());
});
}
fn run_rate_limit(client_auth: &E2eClient, report: &mut TestReport) {
eprintln!();
eprintln!("\x1b[32m[INFO]\x1b[0m Rate limiting tests (burst_size=5, refill=10/s)...");
let mut rate_limit_hit = false;
for i in 0..8 {
let (status, _) = client_auth.query(&format!("rate limit burst {i}"));
if status == 429 {
rate_limit_hit = true;
break;
}
}
run_test!(report, "rate-limit", "Rate limit: 429 on burst", {
assert!(rate_limit_hit, "Expected 429 rate limit hit during burst");
});
std::thread::sleep(Duration::from_secs(1));
run_test!(report, "rate-limit", "Rate limit: works after refill", {
let (status, body) = client_auth.query("rate limit recovered");
assert_eq!(status, 200);
assert!(body["results"].is_array());
});
}
fn run_ttl(client_auth: &E2eClient, report: &mut TestReport) {
eprintln!();
eprintln!("\x1b[32m[INFO]\x1b[0m TTL expiration tests (fresh=5s, stale=10s)...");
client_auth.cache_clear();
run_test!(report, "ttl", "TTL: initial miss", {
let (status, body) = client_auth.query("ttl expiration test");
assert_eq!(status, 200);
assert_eq!(body["cache_status"].as_str(), Some("miss"));
});
run_test!(report, "ttl", "TTL: fresh hit", {
let (status, body) = client_auth.query("ttl expiration test");
assert_eq!(status, 200);
assert_eq!(body["cache_status"].as_str(), Some("hit"));
});
eprintln!("\x1b[32m[INFO]\x1b[0m Waiting 7s for TTL fresh→stale transition...");
std::thread::sleep(Duration::from_secs(7));
run_test!(report, "ttl", "TTL: stale after fresh expires", {
let (status, body) = client_auth.query("ttl expiration test");
assert_eq!(status, 200);
let cs = body["cache_status"].as_str().unwrap_or("");
assert!(
cs == "stale" || cs == "miss",
"Expected stale or miss, got {cs}"
);
});
eprintln!("\x1b[32m[INFO]\x1b[0m Waiting 12s for TTL stale→expired transition...");
std::thread::sleep(Duration::from_secs(12));
run_test!(report, "ttl", "TTL: miss after full expiry", {
let (status, body) = client_auth.query("ttl expiration test");
assert_eq!(status, 200);
assert_eq!(body["cache_status"].as_str(), Some("miss"));
});
}