use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
use alopex_server::auth::AuthMode;
use alopex_server::config::ServerConfig;
use alopex_server::Server;
use tempfile::tempdir;
const LOCK_MESSAGE: &str = "already open by another process";
fn cli_put(data_dir: &Path, key: &str, value: &str) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_alopex"))
.args([
"--data-dir",
&data_dir.display().to_string(),
"--batch",
"kv",
"put",
key,
value,
])
.stdin(Stdio::null())
.output()
.expect("run alopex")
}
fn wait_until_open(child: &mut Child, data_dir: &Path) {
let wal = data_dir.join("lsm.wal");
let deadline = Instant::now() + Duration::from_secs(60);
while Instant::now() < deadline {
if wal.exists() {
return;
}
if let Some(status) = child.try_wait().expect("poll the first alopex") {
panic!("the first alopex exited early with {status} before opening {data_dir:?}");
}
std::thread::sleep(Duration::from_millis(50));
}
panic!("the first alopex never opened {data_dir:?}");
}
#[test]
fn the_cli_refuses_a_data_dir_a_running_server_owns() {
let temp = tempdir().expect("tempdir");
let config = ServerConfig {
data_dir: temp.path().to_path_buf(),
auth_mode: AuthMode::None,
query_timeout: Duration::from_secs(5),
audit_log_enabled: false,
..ServerConfig::default()
};
let server = Server::new(config).expect("server starts");
let output = cli_put(temp.path(), "k", "v");
assert!(
!output.status.success(),
"the CLI must not succeed against a data_dir the server holds"
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
combined.contains(LOCK_MESSAGE),
"expected the stable lock message, got:\n{combined}"
);
drop(server);
let output = cli_put(temp.path(), "k", "v");
assert!(
output.status.success(),
"the CLI must work once the server is gone:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn two_cli_processes_cannot_share_one_data_dir() {
let temp = tempdir().expect("tempdir");
let data_dir = temp.path().join("clidb");
let mut first = Command::new(env!("CARGO_BIN_EXE_alopex"))
.args([
"--data-dir",
&data_dir.display().to_string(),
"--batch",
"--output",
"json",
"sql",
])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn the first alopex");
wait_until_open(&mut first, &data_dir);
let output = cli_put(&data_dir, "k", "v");
assert!(
!output.status.success(),
"the second CLI must not share the first one's data_dir"
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
combined.contains(LOCK_MESSAGE),
"expected the stable lock message, got:\n{combined}"
);
drop(first.stdin.take());
first.wait_with_output().expect("first CLI exits");
let output = cli_put(&data_dir, "k", "v");
assert!(
output.status.success(),
"the data_dir must be reusable after the first CLI exits:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}