use reqwest::Client;
use serde_json::Value;
use std::env;
use std::net::TcpListener;
use std::process::{Child, Command, Stdio};
use std::thread::sleep;
use std::time::{Duration, Instant};
fn get_free_port() -> u16 {
TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap()
.port()
}
fn start_server() -> (Child, u16, u16) {
let http_port = get_free_port();
let grpc_port = get_free_port();
let _ = std::fs::remove_dir_all("coord-test-data");
let _ = std::fs::create_dir_all("coord-test-data");
std::fs::write(
"config.toml",
"node_id = 'coord-test'\nrole = 'coordinator'\n",
)
.expect("Failed to write config.toml");
let mut cmd = Command::new(
env::var("CARGO_BIN_EXE_minikv-coord")
.expect("CARGO_BIN_EXE_minikv-coord not set by cargo test"),
);
cmd.args([
"serve",
"--id",
"coord-test",
"--bind",
&format!("127.0.0.1:{}", http_port),
"--grpc",
&format!("127.0.0.1:{}", grpc_port),
"--db",
"./coord-test-data",
]);
let log = std::fs::File::create("coord-test.log").expect("Failed to create log file");
let log_err = log.try_clone().expect("Failed to clone log file");
cmd.stdout(Stdio::from(log));
cmd.stderr(Stdio::from(log_err));
let child = cmd.spawn().expect("Failed to launch minikv-coord server");
(child, http_port, grpc_port)
}
async fn wait_for_server(child: &mut Child, http_port: u16) {
let client = Client::new();
let url = format!("http://localhost:{}/admin/status", http_port);
let start = Instant::now();
loop {
if let Some(status) = child.try_wait().expect("Error waiting for server") {
if let Some(mut stderr) = child.stderr.take() {
use std::io::Read;
let mut buf = String::new();
let _ = stderr.read_to_string(&mut buf);
panic!("minikv-coord server exited prematurely (exit code {status}):\n{buf}");
} else {
panic!("minikv-coord server exited prematurely (exit code {status})");
}
}
if start.elapsed() > Duration::from_secs(15) {
panic!("Timeout: server not ready at {url}");
}
if let Ok(resp) = client.get(&url).send().await {
if resp.status().is_success() {
break;
}
}
sleep(Duration::from_millis(100));
}
}
#[tokio::test]
async fn test_admin_status() {
if std::env::var("CARGO_BIN_EXE_minikv-coord").is_err() {
eprintln!("Skipping test_admin_status: CARGO_BIN_EXE_minikv-coord not set");
return;
}
let (mut server, http_port, _grpc_port) = start_server();
wait_for_server(&mut server, http_port).await;
let client = Client::new();
let url = format!("http://localhost:{}/admin/status", http_port);
let resp = client
.get(&url)
.send()
.await
.expect("Status request failed");
assert!(resp.status().is_success(), "status endpoint failed");
let text = resp.text().await.expect("Failed to read response body");
let json: Value = serde_json::from_str(&text).expect("Response is not valid JSON");
assert!(json.get("role").is_some());
assert!(json.get("is_leader").is_some());
assert!(json.get("nb_peers").is_some());
assert!(json.get("nb_volumes").is_some());
assert!(json.get("nb_s3_objects").is_some());
let _ = server.kill();
let _ = server.wait();
}