use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
#[allow(dead_code)]
pub fn mock_llm_path() -> PathBuf {
let dir = TempDir::new()
.expect("mock_llm_path: TempDir must be creatable")
.keep();
for name in &["claude", "codex"] {
let src = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("mock-llm")
.join(name);
let dst = dir.join(name);
fs::copy(&src, &dst)
.unwrap_or_else(|e| panic!("mock_llm_path: copy {src:?} -> {dst:?} failed: {e}"));
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&dst)
.expect("mock_llm_path: stat dst")
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&dst, perms).expect("mock_llm_path: chmod 755");
}
}
dir
}
#[allow(dead_code)]
pub fn prepend_path(mock_dir: &std::path::Path) -> std::ffi::OsString {
let current = std::env::var_os("PATH").unwrap_or_default();
let mut entries = vec![mock_dir.to_path_buf()];
entries.extend(std::env::split_paths(¤t));
std::env::join_paths(entries).expect("prepend_path: PATH entries must not contain the separator")
}
#[must_use = "dropping the guard deletes the sandbox mid-test"]
pub struct IsolatedEnv {
root: TempDir,
mock_llm: PathBuf,
db: PathBuf,
}
#[allow(dead_code)]
pub fn isolated_env() -> IsolatedEnv {
let root = TempDir::new().expect("isolated_env: TempDir must be creatable");
for sub in &["home", "cache", "data", "config", "runtime", "db"] {
fs::create_dir_all(root.path().join(sub))
.unwrap_or_else(|e| panic!("isolated_env: mkdir {sub} failed: {e}"));
}
let db = root.path().join("db").join("test.sqlite");
IsolatedEnv {
mock_llm: mock_llm_path(),
db,
root,
}
}
#[allow(dead_code)]
impl IsolatedEnv {
pub fn db(&self) -> &std::path::Path {
&self.db
}
pub fn root(&self) -> &std::path::Path {
self.root.path()
}
pub fn cache(&self) -> PathBuf {
self.root.path().join("cache").join("sqlite-graphrag")
}
pub fn config(&self) -> PathBuf {
self.root.path().join("config").join("sqlite-graphrag")
}
pub fn cmd(&self) -> assert_cmd::Command {
let mut c = assert_cmd::Command::cargo_bin("sqlite-graphrag")
.expect("sqlite-graphrag binary not found");
c.env("PATH", prepend_path(&self.mock_llm))
.env("HOME", self.root.path().join("home"))
.env("XDG_CACHE_HOME", self.root.path().join("cache"))
.env("XDG_DATA_HOME", self.root.path().join("data"))
.env("XDG_CONFIG_HOME", self.root.path().join("config"))
.env("XDG_RUNTIME_DIR", self.root.path().join("runtime"))
.arg("--config-dir")
.arg(self.config())
.arg("--cache-dir")
.arg(self.cache());
c
}
pub fn sgr(&self, subcommand: &str) -> assert_cmd::Command {
let mut c = self.cmd();
c.arg(subcommand).arg("--db").arg(&self.db);
c
}
}
#[allow(dead_code)]
pub fn plant_db_path(config_dir: &std::path::Path, db: &std::path::Path) {
fs::create_dir_all(config_dir).expect("plant_db_path: mkdir config");
let db_str = db
.display()
.to_string()
.replace('\\', "\\\\")
.replace('"', "\\\"");
let cfg = format!("schema_version = 1\n\n[settings]\n\"db.path\" = \"{db_str}\"\n");
fs::write(config_dir.join("config.toml"), cfg).expect("plant_db_path: write config.toml");
}
#[allow(dead_code)]
pub fn wire_assert_cmd(tmp: &TempDir, c: &mut assert_cmd::Command, db_name: &str) {
let root = tmp.path();
let config_dir = root.join("config");
let cache_dir = root.join("cache");
plant_db_path(&config_dir, &root.join(db_name));
c.env("HOME", root.join("home"))
.env("XDG_CACHE_HOME", root.join("xdg_cache"))
.env("XDG_CONFIG_HOME", root.join("xdg_config"))
.env("XDG_DATA_HOME", root.join("xdg_data"))
.env("XDG_RUNTIME_DIR", root.join("xdg_runtime"))
.arg("--config-dir")
.arg(&config_dir)
.arg("--cache-dir")
.arg(&cache_dir);
}
#[allow(dead_code)]
pub fn wire_std_cmd(root: &std::path::Path, c: &mut std::process::Command, db: &std::path::Path) {
let config_dir = root.join("config");
let cache_dir = root.join("cache");
plant_db_path(&config_dir, db);
c.env("HOME", root.join("home"))
.env("XDG_CACHE_HOME", root.join("xdg_cache"))
.env("XDG_CONFIG_HOME", root.join("xdg_config"))
.env("XDG_DATA_HOME", root.join("xdg_data"))
.env("XDG_RUNTIME_DIR", root.join("xdg_runtime"))
.arg("--config-dir")
.arg(&config_dir)
.arg("--cache-dir")
.arg(&cache_dir);
}