use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
pub mod openrouter_mock;
#[allow(unused_imports)]
pub use openrouter_mock::{
global_stub, write_sandbox_config, write_sandbox_config_without_key, OpenRouterStub,
};
#[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");
let env = IsolatedEnv {
mock_llm: mock_llm_path(),
db,
root,
};
write_sandbox_config(&env.config(), None);
env
}
#[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())
.arg("--embedding-model")
.arg(openrouter_mock::STUB_MODEL);
c
}
pub fn embeddings_url(&self) -> &str {
global_stub().embeddings_url()
}
pub fn chat_url(&self) -> &str {
global_stub().chat_url()
}
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) {
write_sandbox_config(config_dir, Some(db));
}
#[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)
.arg("--embedding-model")
.arg(openrouter_mock::STUB_MODEL);
}
#[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)
.arg("--embedding-model")
.arg(openrouter_mock::STUB_MODEL);
}
#[allow(dead_code)]
pub fn sgr_cmd() -> assert_cmd::Command {
let mock_dir = mock_llm_path();
let mut c = assert_cmd::Command::cargo_bin("sqlite-graphrag")
.expect("sqlite-graphrag binary not found");
c.env("PATH", prepend_path(&mock_dir));
c
}
#[allow(dead_code)]
pub fn cmd(tmp: &TempDir) -> assert_cmd::Command {
let mut c = sgr_cmd();
wire_assert_cmd(tmp, &mut c, "test.sqlite");
c
}
#[allow(dead_code)]
pub fn init_db(tmp: &TempDir) {
cmd(tmp).arg("init").assert().success();
}
#[allow(dead_code)]
pub fn isolated_cmd_in(dir: &std::path::Path) -> assert_cmd::Command {
let mut c = sgr_cmd();
c.current_dir(dir);
c.env("HOME", dir.join("home"));
c.env("XDG_CACHE_HOME", dir.join("cache"));
c.env("XDG_CONFIG_HOME", dir.join("config_home"));
c.env("XDG_DATA_HOME", dir.join("data"));
plant_db_path(&dir.join("config"), &dir.join("graphrag.sqlite"));
c.arg("--config-dir").arg(dir.join("config"));
c.arg("--cache-dir").arg(dir.join("cache"));
c.arg("--embedding-model").arg(openrouter_mock::STUB_MODEL);
c
}
#[allow(dead_code)]
pub fn home_isolated_cmd(cwd: &std::path::Path) -> assert_cmd::Command {
let mock_dir = mock_llm_path();
let mut c = assert_cmd::Command::cargo_bin("sqlite-graphrag").expect("bin");
c.env_clear();
c.env("PATH", prepend_path(&mock_dir));
if let Ok(home_var) = std::env::var("HOME") {
c.env("HOME", home_var);
}
c.current_dir(cwd);
c.env("XDG_CACHE_HOME", cwd.join("cache"));
c
}
#[allow(dead_code)]
pub fn seed_memory_with_entities(
tmp: &TempDir,
memory_name: &str,
entities_json: &str,
) -> std::path::PathBuf {
let entities_path = tmp.path().join(format!("entities-{memory_name}.json"));
std::fs::write(&entities_path, entities_json).unwrap();
cmd(tmp)
.args([
"remember",
"--name",
memory_name,
"--type",
"project",
"--description",
"seed memory for graph tests",
"--body",
"body",
"--entities-file",
entities_path.to_str().unwrap(),
])
.assert()
.success();
entities_path
}