Files
frxd/tests/cli.rs
T

270 lines
7.7 KiB
Rust

mod common;
use std::fs;
use std::io::{BufRead, BufReader, Read};
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
fn frxd() -> Command {
Command::new(env!("CARGO_BIN_EXE_frxd"))
}
fn frx() -> Command {
Command::new(env!("CARGO_BIN_EXE_frx"))
}
fn init_args(
config: &Path,
name: &str,
listen_port: u16,
relay_port: u16,
data: &Path,
) -> Vec<String> {
vec![
"--config".to_string(),
config.display().to_string(),
"init".to_string(),
"--name".to_string(),
name.to_string(),
"--listen".to_string(),
format!("127.0.0.1:{listen_port}"),
"--relay".to_string(),
format!("http://127.0.0.1:{relay_port}"),
"--data-dir".to_string(),
data.display().to_string(),
]
}
fn add_args(config: &Path, docs: &Path, name: &str, shared: bool) -> Vec<String> {
let mut args = vec![
"--config".to_string(),
config.display().to_string(),
"add".to_string(),
docs.display().to_string(),
"--name".to_string(),
name.to_string(),
"--exposure".to_string(),
"full".to_string(),
];
if shared {
args.push("--shared".to_string());
}
args
}
fn run_ok(args: &[String]) -> String {
let output = frxd().args(args).output().unwrap();
assert!(
output.status.success(),
"command failed: {}\n{}",
args.join(" "),
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8_lossy(&output.stdout).to_string()
}
struct Service {
child: Child,
}
impl Drop for Service {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
fn spawn_service(args: &[String], needle: &str) -> Service {
let mut child = frxd()
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.unwrap();
let reader = BufReader::new(child.stdout.take().unwrap());
let mut reader = reader;
let deadline = Instant::now() + Duration::from_secs(10);
loop {
let mut line = String::new();
let read = reader.read_line(&mut line).unwrap();
if read == 0 {
panic!("service exited before printing '{needle}'");
}
if line.contains(needle) {
break;
}
assert!(Instant::now() < deadline, "timeout waiting for '{needle}'");
}
std::thread::spawn(move || {
let mut sink = Vec::new();
let _ = reader.read_to_end(&mut sink);
});
Service { child }
}
#[test]
fn cli_init_add_search_status() {
let root = tempfile::tempdir().unwrap();
let config = root.path().join("frxd.toml");
let data = root.path().join("data");
let docs = root.path().join("docs");
fs::create_dir_all(&docs).unwrap();
fs::write(docs.join("note.txt"), "cli smoke rust document").unwrap();
let config_arg = config.display().to_string();
let stdout = run_ok(&init_args(&config, "alice", 0, 1, &data));
assert!(stdout.contains("pubkey"));
assert!(config.exists());
let key = data.join("key.hex");
assert!(key.exists());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&key).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o600, "key file must not be world readable");
}
let stdout = run_ok(&[
"--config".to_string(),
config_arg.clone(),
"member".to_string(),
"add".to_string(),
"carol".to_string(),
"ab".repeat(32),
"--class".to_string(),
"enrichment".to_string(),
]);
assert!(stdout.contains("carol"));
let stdout = run_ok(&[
"--config".to_string(),
config_arg.clone(),
"member".to_string(),
"list".to_string(),
]);
assert!(stdout.contains("carol [enrichment]"));
let stdout = run_ok(&add_args(&config, &docs, "docs", true));
assert!(stdout.contains("indexed 1 file(s)"));
let stdout = run_ok(&[
"--config".to_string(),
config_arg.clone(),
"search".to_string(),
"rust".to_string(),
]);
assert!(stdout.contains("cli smoke rust document"));
let output = frx()
.args(["--config", &config_arg, "search", "rust"])
.output()
.unwrap();
assert!(output.status.success());
assert!(String::from_utf8_lossy(&output.stdout).contains("cli smoke rust document"));
let stdout = run_ok(&["--config".to_string(), config_arg, "status".to_string()]);
assert!(stdout.contains("node not running"));
}
#[test]
fn cli_init_refuses_overwrite_without_force() {
let root = tempfile::tempdir().unwrap();
let config = root.path().join("frxd.toml");
let data = root.path().join("data");
let args = init_args(&config, "alice", 0, 1, &data);
run_ok(&args);
let output = frxd().args(&args).output().unwrap();
assert!(!output.status.success());
assert!(String::from_utf8_lossy(&output.stderr).contains("already exists"));
let mut forced = args.clone();
forced.push("--force".to_string());
run_ok(&forced);
}
#[test]
fn cli_full_network_pipeline() {
let root = tempfile::tempdir().unwrap();
let relay_port = common::free_port();
let relay_args = vec![
"relay".to_string(),
"--listen".to_string(),
format!("127.0.0.1:{relay_port}"),
];
let _relay = spawn_service(&relay_args, "relay listening");
let bob_port = common::free_port();
let alice_port = common::free_port();
let bob_config = root.path().join("bob.toml");
let alice_config = root.path().join("alice.toml");
let bob_docs = root.path().join("bob-docs");
let alice_docs = root.path().join("alice-docs");
fs::create_dir_all(&bob_docs).unwrap();
fs::create_dir_all(&alice_docs).unwrap();
fs::write(
bob_docs.join("tantivy.md"),
"# Tantivy BM25\nTantivy indexes rust documents.",
)
.unwrap();
fs::write(alice_docs.join("private.txt"), "alice private rust note").unwrap();
run_ok(&init_args(
&bob_config,
"bob",
bob_port,
relay_port,
&root.path().join("bob-data"),
));
run_ok(&init_args(
&alice_config,
"alice",
alice_port,
relay_port,
&root.path().join("alice-data"),
));
run_ok(&add_args(&bob_config, &bob_docs, "shared", true));
run_ok(&add_args(&alice_config, &alice_docs, "mine", false));
let bob_serve = vec![
"--config".to_string(),
bob_config.display().to_string(),
"serve".to_string(),
];
let alice_serve = vec![
"--config".to_string(),
alice_config.display().to_string(),
"serve".to_string(),
];
let _bob = spawn_service(&bob_serve, "frxd listening");
let _alice = spawn_service(&alice_serve, "frxd listening");
std::thread::sleep(Duration::from_millis(400));
let alice_config_arg = alice_config.display().to_string();
let stdout = run_ok(&[
"--config".to_string(),
alice_config_arg.clone(),
"query".to_string(),
"tantivy".to_string(),
]);
assert!(stdout.contains("Tantivy BM25"), "{stdout}");
assert!(stdout.contains("remote: 1 response(s)"), "{stdout}");
let stdout = run_ok(&[
"--config".to_string(),
alice_config_arg.clone(),
"query".to_string(),
"rust".to_string(),
"--local-only".to_string(),
]);
assert!(stdout.contains("alice private rust note"), "{stdout}");
assert!(stdout.contains("remote: 0 response(s)"), "{stdout}");
let output = frx()
.args(["--config", &alice_config_arg, "query", "tantivy"])
.output()
.unwrap();
assert!(output.status.success());
assert!(String::from_utf8_lossy(&output.stdout).contains("Tantivy BM25"));
}