use std::fs;
use std::path::PathBuf;
use std::process::Command;
use beck::agents::manifest::Manifest;
fn unique_root(name: &str) -> PathBuf {
let base = std::env::temp_dir().join(format!(
"beck-check-e2e-{name}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
));
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(&base).unwrap();
base
}
fn beck_bin() -> &'static str {
env!("CARGO_BIN_EXE_beck")
}
fn run(beck_home: &PathBuf, fake_home: &PathBuf, args: &[&str]) -> std::process::Output {
Command::new(beck_bin())
.args(args)
.env("BECK_HOME", beck_home)
.env("HOME", fake_home)
.output()
.expect("failed to spawn beck")
}
fn assert_ok(label: &str, out: &std::process::Output) {
assert!(
out.status.success(),
"{label} failed (exit {:?}): stdout={} stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn check_classifies_beck_managed_foreign_and_orphan_then_prunes() {
let root = unique_root("three-state");
let beck_home = root.join("beck");
let fake_home = root.join("home");
let claude_skills = fake_home.join(".claude").join("skills");
fs::create_dir_all(fake_home.join(".claude")).unwrap();
let out = run(&beck_home, &fake_home, &["bootstrap"]);
assert_ok("bootstrap", &out);
let skill_dir = beck_home.join("skills").join("caveman");
fs::create_dir_all(&skill_dir).unwrap();
fs::write(
skill_dir.join("SKILL.md"),
"---\nname: caveman\ndescription: e2e sample\n---\nbody\n",
)
.unwrap();
let out = run(&beck_home, &fake_home, &["link", "--agent", "claude-code"]);
assert_ok("link", &out);
let linked_target = claude_skills.join("caveman").join("SKILL.md");
assert!(
linked_target.exists(),
"link should create {linked_target:?}"
);
let foreign_dir = claude_skills.join("user-handmade");
fs::create_dir_all(&foreign_dir).unwrap();
fs::write(foreign_dir.join("SKILL.md"), b"user's own rule\n").unwrap();
let manifest_file = beck_home.join(".beck-manifest.json");
let mut manifest = Manifest::load(&manifest_file).unwrap();
manifest.add(beck::agents::manifest::Entry {
skill: "ghost".into(),
agent: "claude-code".into(),
target: claude_skills.join("ghost").join("SKILL.md"),
mode: beck::agents::manifest::InstallMode::Symlink,
sha256: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".into(),
installed_at: "2026-04-11T00:00:00Z".into(),
});
manifest.save(&manifest_file).unwrap();
let out = run(&beck_home, &fake_home, &["check", "--json"]);
assert_ok("check", &out);
let body = String::from_utf8_lossy(&out.stdout).to_string();
let report: serde_json::Value = serde_json::from_str(&body).expect("valid json");
let detected = report["adapters_detected"]
.as_array()
.expect("adapters_detected array");
assert!(
detected.iter().any(|v| v == "claude-code"),
"expected claude-code in detected, body={body}"
);
assert_eq!(
report["beck_managed"].as_u64().unwrap(),
1,
"expected exactly one beck-managed file"
);
let foreign = report["foreign"].as_array().unwrap();
assert_eq!(foreign.len(), 1);
assert!(
foreign[0]["path"]
.as_str()
.unwrap()
.contains("user-handmade"),
"foreign[0]={:?}",
foreign[0]
);
let orphans = report["orphans"].as_array().unwrap();
assert_eq!(orphans.len(), 1);
assert_eq!(orphans[0]["skill"].as_str().unwrap(), "ghost");
let out = run(&beck_home, &fake_home, &["check", "--prune"]);
assert_ok("check --prune", &out);
let pruned = Manifest::load(&manifest_file).unwrap();
assert!(
pruned.entries.iter().all(|e| e.skill != "ghost"),
"ghost should have been pruned, got entries={:?}",
pruned.entries
);
assert_eq!(
pruned.entries.len(),
1,
"the legitimate caveman entry should still be there"
);
assert!(foreign_dir.join("SKILL.md").exists());
let _ = fs::remove_dir_all(&root);
}