use std::process::Command;
use assert_cmd::Command as CargoBin;
use tempfile::TempDir;
fn sdivi() -> CargoBin {
CargoBin::cargo_bin("sdivi").expect("sdivi binary must be built")
}
fn git(dir: &std::path::Path, args: &[&str]) {
let status = Command::new("git")
.current_dir(dir)
.args(args)
.status()
.expect("git must be available");
assert!(status.success(), "git {args:?} failed");
}
fn capture_snapshots(repo: &TempDir, n: usize) {
let d = repo.path();
git(d, &["init"]);
git(d, &["config", "user.email", "test@test.com"]);
git(d, &["config", "user.name", "Test"]);
for i in 0..n {
let fname = format!("file{i}.rs");
std::fs::write(d.join(&fname), format!("fn f{i}() {{}}")).unwrap();
git(d, &["add", &fname]);
git(d, &["commit", "-m", &format!("add file{i}")]);
sdivi()
.arg("--repo")
.arg(d)
.arg("snapshot")
.arg("--commit")
.arg("HEAD")
.assert()
.success();
}
}
#[test]
fn infer_after_snapshots_exits_zero() {
let repo = tempfile::tempdir().unwrap();
capture_snapshots(&repo, 4);
let out = sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("infer")
.output()
.unwrap();
assert!(
out.status.success(),
"boundaries infer must exit 0 after enough snapshots"
);
}
#[test]
fn ratify_writes_valid_yaml() {
let repo = tempfile::tempdir().unwrap();
capture_snapshots(&repo, 4);
sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("ratify")
.assert()
.success();
let boundary_path = repo.path().join(".sdivi").join("boundaries.yaml");
if boundary_path.exists() {
let content = std::fs::read_to_string(&boundary_path).unwrap();
let _spec: sdivi_config::BoundarySpec =
serde_yml::from_str(&content).expect("ratify output must be valid BoundarySpec YAML");
}
}
#[test]
fn show_after_ratify_prints_to_stdout() {
let repo = tempfile::tempdir().unwrap();
capture_snapshots(&repo, 4);
sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("ratify")
.assert()
.success();
let boundary_path = repo.path().join(".sdivi").join("boundaries.yaml");
if !boundary_path.exists() {
return;
}
let out = sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("show")
.output()
.unwrap();
assert!(out.status.success(), "boundaries show must exit 0");
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(
!stdout.is_empty(),
"boundaries show must print to stdout when spec exists"
);
}
#[test]
fn show_json_format_after_ratify() {
let repo = tempfile::tempdir().unwrap();
capture_snapshots(&repo, 4);
sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("ratify")
.assert()
.success();
let boundary_path = repo.path().join(".sdivi").join("boundaries.yaml");
if !boundary_path.exists() {
return;
}
let out = sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("show")
.arg("--format")
.arg("json")
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout).unwrap();
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("show --format json must produce valid JSON");
assert!(parsed["boundaries"].is_array());
}
#[test]
fn snapshot_with_boundary_violations_reports_nonzero_count() {
let repo = tempfile::tempdir().unwrap();
let d = repo.path();
git(d, &["init"]);
git(d, &["config", "user.email", "test@test.com"]);
git(d, &["config", "user.name", "Test"]);
std::fs::create_dir_all(d.join("layer_a")).unwrap();
std::fs::create_dir_all(d.join("layer_b")).unwrap();
std::fs::write(d.join("layer_b/helper.rs"), "pub fn helper() {}").unwrap();
std::fs::write(
d.join("layer_a/lib.rs"),
"use crate::helper;\nfn call() { helper::helper(); }",
)
.unwrap();
git(d, &["add", "."]);
git(d, &["commit", "-m", "add layer files"]);
let sdivi_dir = d.join(".sdivi");
std::fs::create_dir_all(&sdivi_dir).unwrap();
std::fs::write(
sdivi_dir.join("boundaries.yaml"),
"boundaries:\n - name: layer_a\n modules: [\"layer_a/**\"]\n allow_imports_from: []\n - name: layer_b\n modules: [\"layer_b/**\"]\n allow_imports_from: []\n",
)
.unwrap();
sdivi()
.arg("--repo")
.arg(d)
.arg("snapshot")
.assert()
.success();
let snap_dir = sdivi_dir.join("snapshots");
let entries: Vec<_> = std::fs::read_dir(&snap_dir)
.unwrap()
.filter_map(|e| e.ok())
.collect();
assert!(!entries.is_empty(), "at least one snapshot must be written");
let snap_path = entries[0].path();
let content = std::fs::read_to_string(&snap_path).unwrap();
let snap: serde_json::Value = serde_json::from_str(&content).unwrap();
let violation_count = snap["intent_divergence"]["violation_count"]
.as_u64()
.expect("intent_divergence.violation_count must be present");
assert!(
violation_count > 0,
"snapshot with cross-boundary edges and no allow_imports_from must report violations; got {violation_count}"
);
}
#[test]
fn ratify_read_back_equivalent() {
let repo = tempfile::tempdir().unwrap();
capture_snapshots(&repo, 4);
sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("ratify")
.assert()
.success();
let boundary_path = repo.path().join(".sdivi").join("boundaries.yaml");
if !boundary_path.exists() {
return;
}
let content = std::fs::read_to_string(&boundary_path).unwrap();
let spec: sdivi_config::BoundarySpec = serde_yml::from_str(&content).unwrap();
sdivi()
.arg("--repo")
.arg(repo.path())
.arg("boundaries")
.arg("ratify")
.assert()
.success();
let content2 = std::fs::read_to_string(&boundary_path).unwrap();
let spec2: sdivi_config::BoundarySpec = serde_yml::from_str(&content2).unwrap();
assert_eq!(spec.boundaries.len(), spec2.boundaries.len());
}