use std::io::{BufRead, BufReader};
use std::net::TcpListener as StdTcpListener;
use std::process::{Child, Command, Stdio};
use std::sync::{Arc, Mutex};
use std::time::Duration;
struct ChildGuard(Child);
impl Drop for ChildGuard {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
fn drain_stderr(child: &mut Child) -> Arc<Mutex<String>> {
let buffer = Arc::new(Mutex::new(String::new()));
let stderr = child
.stderr
.take()
.expect("child spawned with Stdio::piped() stderr");
let buffer_writer = Arc::clone(&buffer);
std::thread::spawn(move || {
let reader = BufReader::new(stderr);
for line in reader.lines() {
let Ok(line) = line else { break };
if let Ok(mut buffer) = buffer_writer.lock() {
buffer.push_str(&line);
buffer.push('\n');
}
}
});
buffer
}
fn pick_free_port() -> u16 {
let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind ephemeral port to pick one");
listener.local_addr().expect("read bound local addr").port()
}
fn binary_path() -> &'static str {
env!("CARGO_BIN_EXE_velesdb-memory")
}
fn wait_for_plain_http_health(port: u16, timeout: Duration) -> Result<(), String> {
let deadline = std::time::Instant::now() + timeout;
let mut last_err = String::from("never attempted");
while std::time::Instant::now() < deadline {
match reqwest::blocking::get(format!("http://127.0.0.1:{port}/health")) {
Ok(response) if response.status().is_success() => return Ok(()),
Ok(response) => last_err = format!("non-success status: {}", response.status()),
Err(err) => last_err = err.to_string(),
}
std::thread::sleep(Duration::from_millis(100));
}
Err(format!("daemon never answered /health in time: {last_err}"))
}
#[test]
fn http_insecure_flag_serves_plain_http() {
let port = pick_free_port();
let store_dir = tempfile::tempdir().expect("create scratch store dir");
let mut child = Command::new(binary_path())
.arg("--http")
.arg("--http-insecure")
.arg("--http-port")
.arg(port.to_string())
.env("VELESDB_MEMORY_PATH", store_dir.path())
.env("VELESDB_MEMORY_QUIET", "1")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.expect("spawn velesdb-memory --http --http-insecure");
let stderr_output = drain_stderr(&mut child);
let mut guard = ChildGuard(child);
let ready = wait_for_plain_http_health(port, Duration::from_secs(10));
let stderr_output = stderr_output.lock().expect("stderr buffer lock").clone();
assert!(
ready.is_ok(),
"expected --http-insecure to serve plain HTTP reachable at /health: {ready:?}\nstderr:\n{stderr_output}"
);
guard.0.kill().expect("kill the insecure daemon");
guard
.0
.wait()
.expect("reap the insecure daemon after kill — no orphan process");
}