use assert_cmd::prelude::*;
use predicates::prelude::*;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use tempfile::tempdir;
fn write_file(path: &PathBuf, content: &str) {
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
let mut f = fs::File::create(path).unwrap();
f.write_all(content.as_bytes()).unwrap();
}
#[test]
fn build_uses_config_overrides_and_save() {
let dir = tempdir().unwrap();
let root = dir.path();
let src = root.join("src");
fs::create_dir_all(&src).unwrap();
write_file(&src.join("lib.rs"), "pub fn top() {}\n");
let cfg_path = root.join("rust-relations-explorer.toml");
write_file(
&cfg_path,
r#"
[dot]
clusters = true
legend = true
theme = "dark"
rankdir = "TB"
splines = "polyline"
rounded = false
[svg]
interactive = true
[query]
default_format = "json"
"#,
);
let dot_out = root.join("graph.dot");
let svg_out = root.join("graph.svg");
let json_out = root.join("graph.json");
let mut cmd = Command::cargo_bin("rust-relations-explorer").unwrap();
cmd.arg("build")
.arg("--path")
.arg(root)
.arg("--config")
.arg(&cfg_path)
.arg("--dot")
.arg(&dot_out)
.arg("--json")
.arg(&json_out)
.arg("--save")
.arg(root.join("saved.json"));
let dot_available = Command::new("dot").arg("-V").output().is_ok();
if dot_available {
cmd.arg("--svg").arg(&svg_out);
}
cmd.assert().success().stdout(predicate::str::contains("Build completed for path"));
let dot_str = fs::read_to_string(&dot_out).unwrap();
assert!(dot_str.contains("rankdir=TB"));
assert!(dot_str.contains("splines=polyline"));
if dot_available {
assert!(svg_out.exists());
}
assert!(json_out.exists());
}
#[test]
fn queries_without_graph_build_from_path_and_text_formats() {
let dir = tempdir().unwrap();
let root = dir.path();
let src = root.join("src");
fs::create_dir_all(&src).unwrap();
write_file(&src.join("lib.rs"), "mod a; mod b; pub use a::foo;\n");
write_file(&src.join("a.rs"), "pub fn foo() {}\n");
write_file(&src.join("b.rs"), "pub fn bar() { crate::a::foo(); }\n");
let mut cf = Command::cargo_bin("rust-relations-explorer").unwrap();
cf.arg("query")
.arg("connected-files")
.arg("--path")
.arg(root)
.arg("--file")
.arg(src.join("a.rs"))
.arg("--format")
.arg("text");
cf.assert()
.success()
.stdout(predicate::str::contains("#").and(predicate::str::contains("Path")));
let mut fu = Command::cargo_bin("rust-relations-explorer").unwrap();
fu.arg("query")
.arg("function-usage")
.arg("--path")
.arg(root)
.arg("--function")
.arg("bar")
.arg("--direction")
.arg("callees")
.arg("--format")
.arg("text");
fu.assert().success();
let mut hubs = Command::cargo_bin("rust-relations-explorer").unwrap();
hubs.arg("query")
.arg("hubs")
.arg("--path")
.arg(root)
.arg("--metric")
.arg("in")
.arg("--top")
.arg("5")
.arg("--format")
.arg("text");
hubs.assert()
.success()
.stdout(predicate::str::contains("In").and(predicate::str::contains("Out")));
let mut mc = Command::cargo_bin("rust-relations-explorer").unwrap();
mc.arg("query")
.arg("module-centrality")
.arg("--path")
.arg(root)
.arg("--metric")
.arg("out")
.arg("--top")
.arg("5")
.arg("--format")
.arg("text");
mc.assert()
.success()
.stdout(predicate::str::contains("Module").and(predicate::str::contains("Total")));
let mut pathq = Command::cargo_bin("rust-relations-explorer").unwrap();
pathq
.arg("query")
.arg("path")
.arg("--path")
.arg(root)
.arg("--from")
.arg(src.join("a.rs"))
.arg("--to")
.arg(src.join("b.rs"))
.arg("--format")
.arg("text");
pathq.assert().success().stdout(predicate::str::contains("<no path>"));
}
#[test]
fn cycles_text_output_branch() {
let dir = tempdir().unwrap();
let root = dir.path();
let src = root.join("src");
fs::create_dir_all(&src).unwrap();
write_file(&src.join("lib.rs"), "mod a; mod b;\n");
write_file(&src.join("a.rs"), "pub fn fa() {}\n");
write_file(&src.join("b.rs"), "pub fn fb() { }\n");
let mut cy = Command::cargo_bin("rust-relations-explorer").unwrap();
cy.arg("query").arg("cycles").arg("--path").arg(root).arg("--format").arg("text");
cy.assert().success();
}