Menu
AkurAI-Build
publicLatest change 75b5a12a56d5427ca0f851159b9eb5a6825f8054 - tests: subprocess coverage for CLI wiring and exit-code contract by Olafur Bui
//! End-to-end CLI wiring checks against the built binary.
//!
//! Architecture review 8 (P2): the clap argument plumbing for keygen,
//! migrate, vector put/search, backup, restore acknowledgement, and the
//! doctor exit-code contract had no subprocess coverage; only the
//! underlying library functions were tested.
#![cfg(unix)]
use std::{fs, process::Command};
use anyhow::{Context, Result, ensure};
fn bunfork() -> Command {
Command::new(env!("CARGO_BIN_EXE_bunfork"))
}
fn run(command: &mut Command) -> Result<Vec<u8>> {
let output = command.output().context("spawn bunfork")?;
ensure!(
output.status.success(),
"command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
Ok(output.stdout)
}
#[test]
fn cli_wires_keygen_migrate_vector_backup_restore_and_doctor() -> Result<()> {
let directory = tempfile::tempdir()?;
let database = directory.path().join("data/bunfork.db");
let key_file = directory.path().join("db.key");
let content = directory.path().join("note.txt");
fs::write(&content, "hello vectors")?;
run(bunfork().arg("keygen").arg("--out").arg(&key_file))?;
run(bunfork()
.arg("--database")
.arg(&database)
.arg("--key-file")
.arg(&key_file)
.arg("migrate"))?;
run(bunfork()
.arg("--database")
.arg(&database)
.arg("--key-file")
.arg(&key_file)
.args(["vector", "put", "note-1", "--content-file"])
.arg(&content)
.args(["--embedding", "1,0"]))?;
let stdout = run(bunfork()
.arg("--database")
.arg(&database)
.arg("--key-file")
.arg(&key_file)
.args(["vector", "search", "--embedding", "1,0", "--limit", "1"]))?;
let matches: serde_json::Value = serde_json::from_slice(&stdout)?;
ensure!(matches[0]["id"] == "note-1", "unexpected search result");
let backup = directory.path().join("backup.db");
let stdout = run(bunfork()
.arg("--database")
.arg(&database)
.arg("--key-file")
.arg(&key_file)
.args(["backup", "--json", "--out"])
.arg(&backup))?;
let report: serde_json::Value = serde_json::from_slice(&stdout)?;
ensure!(report["ok"] == true, "backup report not ok");
ensure!(backup.is_file(), "backup file missing");
// Omitting the required --yes acknowledgement is clap misuse: exit 2.
let output = bunfork()
.arg("--database")
.arg(&database)
.arg("--key-file")
.arg(&key_file)
.arg("restore")
.arg(&backup)
.output()?;
ensure!(
output.status.code() == Some(2),
"restore without --yes must be a usage error (exit 2)"
);
// Verification failure has its own exit code (3) and names the file.
let output = bunfork()
.args([
"doctor",
"--static",
"tests/fixtures/static/mpa/site",
"--manifest",
"tests/fixtures/static/spa/manifest.json",
"--json",
])
.output()?;
ensure!(
output.status.code() == Some(3),
"doctor mismatch must exit 3"
);
let report: serde_json::Value = serde_json::from_slice(&output.stdout)?;
ensure!(report["ok"] == false, "doctor report must fail");
ensure!(
report["checks"][0]["message"]
.as_str()
.is_some_and(|message| message.contains("missing file")),
"doctor report must name the divergence"
);
Ok(())
}