mod common;
use predicates::prelude::*;
use std::path::Path;
use common::TestProject;
#[test]
fn link_creates_relative_dir_symlink() {
let p = TestProject::new();
p.skills()
.args(["link", "claude-code"])
.assert()
.success()
.stdout(predicate::str::contains("linked"));
let link = p.path().join(".claude/skills");
assert!(link.is_symlink());
assert_eq!(
std::fs::read_link(&link).unwrap(),
Path::new("../.agents/skills")
);
}
#[test]
fn link_refuses_content_and_migrate_adopts_it() {
let p = TestProject::new();
let existing = p.path().join(".claude/skills/my-skill");
std::fs::create_dir_all(&existing).unwrap();
std::fs::write(existing.join("SKILL.md"), "x").unwrap();
p.skills()
.args(["link", "claude-code"])
.assert()
.success()
.stdout(predicate::str::contains("migrate"));
p.skills()
.args(["link", "claude-code", "--migrate"])
.assert()
.success()
.stdout(predicate::str::contains("migrated"));
p.assert_exists(".agents/skills/my-skill/SKILL.md");
assert!(p.path().join(".claude/skills").is_symlink());
}
#[test]
fn link_refuses_dir_with_only_stray_files() {
let p = TestProject::new();
std::fs::create_dir_all(p.path().join(".claude/skills")).unwrap();
std::fs::write(p.path().join(".claude/skills/README.txt"), "x").unwrap();
p.skills()
.args(["link", "claude-code"])
.assert()
.success()
.stdout(predicate::str::contains("non-skill files"))
.stdout(predicate::str::contains("rerun with --migrate").not());
assert!(p.path().join(".claude/skills/README.txt").exists());
assert!(!p.path().join(".claude/skills").is_symlink());
}
#[test]
fn unlink_restores_real_dir() {
let p = TestProject::new();
p.skills().args(["link", "claude-code"]).assert().success();
p.skills()
.args(["link", "--unlink", "claude-code"])
.assert()
.success()
.stdout(predicate::str::contains("unlinked"));
let dir = p.path().join(".claude/skills");
assert!(dir.is_dir());
assert!(!dir.is_symlink());
}
#[test]
fn link_status_prints_installed_agents_and_link_state() {
let p = TestProject::new();
p.skills().args(["link", "claude-code"]).assert().success();
std::fs::create_dir_all(p.path().join(".codebuddy")).unwrap();
p.skills()
.args(["link", "--status"])
.assert()
.success()
.stdout(predicate::str::contains("Agent link status"))
.stdout(predicate::str::contains("Claude Code"))
.stdout(predicate::str::contains("(linked)"))
.stdout(predicate::str::contains("CodeBuddy"))
.stdout(predicate::str::contains(
"not linked) — run `agents-skills link codebuddy`",
));
}
#[test]
fn link_status_marks_universal_agents_as_canonical() {
let p = TestProject::new();
std::fs::create_dir_all(p.path().join(".warp")).unwrap();
p.skills()
.env("HOME", p.path())
.args(["link", "--status"])
.assert()
.success()
.stdout(predicate::str::contains("Warp"))
.stdout(predicate::str::contains("(canonical dir)"));
}
#[test]
fn link_status_conflicts_with_unlink_and_migrate() {
let p = TestProject::new();
p.skills()
.args(["link", "--status", "--unlink"])
.assert()
.failure()
.code(1)
.stderr(predicate::str::contains("cannot be used with"));
p.skills()
.args(["link", "--status", "--migrate"])
.assert()
.failure()
.code(1)
.stderr(predicate::str::contains("cannot be used with"));
p.skills()
.args(["link", "--unlink", "--migrate"])
.assert()
.failure()
.code(1)
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn add_then_link_ensures_agent_links() {
let p = TestProject::new();
let src = p.write_skill_source("my-skill", "pdf");
p.skills()
.args(["add", src.to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("Installed 1 skill"));
p.assert_exists(".agents/skills/pdf/SKILL.md");
assert!(!p.path().join(".claude/skills").is_symlink());
p.skills().args(["link", "claude-code"]).assert().success();
let link = p.path().join(".claude/skills");
assert!(link.is_symlink());
assert!(link.join("pdf/SKILL.md").exists());
}
#[test]
fn remove_skill_disappears_from_linked_agents() {
let p = TestProject::new();
let src = p.write_skill_source("my-skill", "pdf");
p.skills()
.args(["add", src.to_str().unwrap()])
.assert()
.success();
p.skills().args(["link", "claude-code"]).assert().success();
p.skills().args(["remove", "pdf", "-y"]).assert().success();
p.assert_absent(".agents/skills/pdf");
assert!(p.path().join(".claude/skills").is_symlink());
assert!(!p.path().join(".claude/skills/pdf").exists());
}
#[test]
fn link_alias_ln_works() {
let p = TestProject::new();
p.skills().args(["ln", "claude-code"]).assert().success();
assert!(p.path().join(".claude/skills").is_symlink());
}