use std::fs;
use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(2)
.expect("wenlan-types is nested under crates/")
.to_path_buf()
}
fn read_text(relative: &str) -> String {
let path = repo_root().join(relative);
fs::read_to_string(&path).unwrap_or_else(|err| panic!("read {}: {err}", path.display()))
}
#[test]
fn plugin_init_repairs_stale_daemon_versions() {
let init = read_text("plugin/skills/init/SKILL.md");
let hook = read_text("plugin/hooks/check-daemon.sh");
for needle in [
"Compare daemon version vs plugin manifest version",
"If mismatch, repair the runtime",
"curl -fsSL https://raw.githubusercontent.com/7xuanlu/wenlan/v${EXPECTED_VER}/install.sh | bash",
"export PATH=\"$HOME/.wenlan/bin:$PATH\" && wenlan setup --basic && wenlan install",
] {
assert!(
init.contains(needle),
"/init missing stale-daemon repair contract: {needle}"
);
}
assert!(
hook.contains("Run /wenlan:init to repair"),
"SessionStart hook should direct version mismatches to the self-healing /init entrypoint"
);
assert!(
!hook.contains("Otherwise upgrade: curl -fsSL"),
"SessionStart hook should not print raw upgrade commands when /init owns repair"
);
assert!(
hook.contains("for i in 1 2 3") && hook.contains("curl -fsS -m 3"),
"SessionStart hook should retry daemon health checks to avoid false down reports"
);
}
#[test]
fn pages_skill_replaces_read() {
let pages_path = repo_root().join("plugin/skills/pages/SKILL.md");
assert!(
pages_path.is_file(),
"missing pages skill: {}",
pages_path.display()
);
let read_path = repo_root().join("plugin/skills/read/SKILL.md");
assert!(
!read_path.exists(),
"read skill should be deleted (renamed to /pages): {}",
read_path.display()
);
for doc in [
"plugin/skills/help/SKILL.md",
"plugin/skills/README.md",
"plugin/.claude-plugin/README.md",
] {
let text = read_text(doc);
assert!(
!text.contains("/read"),
"{doc} still advertises the removed /read command"
);
assert!(
text.contains("/pages"),
"{doc} should advertise the /pages command"
);
}
}