use std::path::{Path, PathBuf};
use std::process::Command;
const BIN: &str = env!("CARGO_BIN_EXE_roteiro");
fn git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.args([
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"-c",
"commit.gpgsign=false",
"-c",
"init.defaultBranch=main",
])
.args(args)
.current_dir(dir)
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed");
}
fn roteiro(dir: &Path, args: &[&str]) -> std::process::Output {
Command::new(BIN)
.args(args)
.current_dir(dir)
.output()
.expect("run roteiro")
}
fn write(dir: &Path, rel: &str, content: &str) {
let path = dir.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).expect("mkdir");
std::fs::write(path, content).expect("write");
}
fn fresh_dir(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("roteiro-artifact-{}-{name}", std::process::id()));
std::fs::remove_dir_all(&dir).ok();
std::fs::create_dir_all(&dir).expect("mkdir");
dir
}
fn init_repo(name: &str) -> PathBuf {
let dir = fresh_dir(name);
git(&dir, &["init", "-q"]);
write(
&dir,
"src/main.rs",
"mod util;\nfn main() {\n util::helper();\n}\n",
);
write(&dir, "src/util.rs", "pub fn helper() {}\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
dir
}
#[test]
fn export_then_load_reproduces_graph_without_extraction() {
let src = init_repo("src");
let artifact = src.join("graph.json");
let out = roteiro(&src, &["export", "--out", artifact.to_str().unwrap()]);
assert!(out.status.success(), "export failed: {out:?}");
assert!(artifact.exists(), "artifact should be written");
let json = std::fs::read_to_string(&artifact).expect("read artifact");
assert!(json.contains("roteiro.graph/v1"), "schema tag present");
assert!(
json.contains("sym:rust:src/util.rs#helper"),
"symbols exported"
);
let again = src.join("graph2.json");
roteiro(&src, &["export", "--out", again.to_str().unwrap()]);
assert_eq!(
std::fs::read_to_string(&artifact).unwrap(),
std::fs::read_to_string(&again).unwrap(),
"export must be deterministic",
);
let dst = fresh_dir("dst");
git(&dst, &["init", "-q"]);
write(&dst, "unrelated.rs", "fn only_here() {}\n");
git(&dst, &["add", "."]);
git(&dst, &["commit", "-q", "-m", "different"]);
let loaded = roteiro(&dst, &["load", "--force", artifact.to_str().unwrap()]);
assert!(loaded.status.success(), "load failed: {loaded:?}");
let msg = String::from_utf8_lossy(&loaded.stdout);
assert!(msg.contains("nodes"), "load reports counts: {msg}");
let db = dst.join(".git/roteiro/graph.db");
let store = rto_graph::Store::open(&db).expect("open loaded store");
assert!(
store
.get_node("sym:rust:src/util.rs#helper")
.expect("get")
.is_some(),
"loaded store should contain the source symbol",
);
assert!(
store
.get_node("sym:rust:unrelated.rs#only_here")
.expect("get")
.is_none(),
"loaded store must NOT contain the destination's own symbol (no extraction)",
);
assert!(store.sync_state().expect("state").is_some());
std::fs::remove_dir_all(&src).ok();
std::fs::remove_dir_all(&dst).ok();
}
#[test]
fn load_rejects_unknown_schema() {
let dir = init_repo("badschema");
let bad = dir.join("bad.json");
std::fs::write(
&bad,
r#"{"schema":"roteiro.graph/v999","tree":null,"facts":{"nodes":[],"edges":[]}}"#,
)
.expect("write");
let out = roteiro(&dir, &["load", bad.to_str().unwrap()]);
assert!(!out.status.success(), "loading an unknown schema must fail");
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn load_refuses_a_mismatched_tree() {
let dir = fresh_dir("verify");
git(&dir, &["init", "-q"]);
write(&dir, "src/lib.rs", "pub fn a() {}\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "one"]);
let artifact = dir.join("g.json");
assert!(roteiro(&dir, &["sync"]).status.success());
assert!(
roteiro(&dir, &["export", "--out", artifact.to_str().unwrap()])
.status
.success()
);
assert!(
roteiro(&dir, &["load", artifact.to_str().unwrap()])
.status
.success(),
"loading a matching artifact should succeed"
);
write(&dir, "src/lib.rs", "pub fn a() {}\npub fn b() {}\n");
git(&dir, &["commit", "-q", "-am", "two"]);
let stale = roteiro(&dir, &["load", artifact.to_str().unwrap()]);
assert!(
!stale.status.success(),
"a mismatched-tree load must be refused"
);
assert!(
String::from_utf8_lossy(&stale.stderr).contains("does not match HEAD"),
"explains the mismatch: {}",
String::from_utf8_lossy(&stale.stderr)
);
assert!(
roteiro(&dir, &["load", "--force", artifact.to_str().unwrap()])
.status
.success(),
"--force loads a mismatched artifact"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn load_refuses_a_tree_less_artifact() {
let dir = init_repo("notree");
let bad = dir.join("notree.json");
std::fs::write(
&bad,
r#"{"schema":"roteiro.graph/v1","tree":null,"facts":{"nodes":[],"edges":[]}}"#,
)
.expect("write");
let refused = roteiro(&dir, &["load", bad.to_str().unwrap()]);
assert!(
!refused.status.success(),
"a tree-less artifact must be refused"
);
assert!(
String::from_utf8_lossy(&refused.stderr).contains("no tree"),
"explains why: {}",
String::from_utf8_lossy(&refused.stderr)
);
assert!(
roteiro(&dir, &["load", "--force", bad.to_str().unwrap()])
.status
.success(),
"--force loads it anyway"
);
std::fs::remove_dir_all(&dir).ok();
}