174 lines
6.6 KiB
Rust
174 lines
6.6 KiB
Rust
//! 通过真实 CLI 验证规则来源、原子合并及拒绝输入;只操作独占临时项目。
|
|
|
|
use super::{Directory, success};
|
|
use std::os::unix::fs::{MetadataExt, PermissionsExt, symlink};
|
|
use std::path::Path;
|
|
use std::process::{Command, Output};
|
|
|
|
fn cli(root: &Path, arguments: &[&str]) -> Output {
|
|
Command::new(env!("CARGO_BIN_EXE_dever"))
|
|
.current_dir(root)
|
|
.args(arguments)
|
|
.output()
|
|
.unwrap()
|
|
}
|
|
|
|
fn template() -> String {
|
|
std::fs::read_to_string(
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../skills/assets/template/AGENTS.md"),
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn path_uses_current_toolchain_without_project_version_or_mutation() {
|
|
let directory = Directory::new();
|
|
let root = directory.0.join("app");
|
|
dever::compiler::project::create(&root).unwrap();
|
|
let settings = root.join("config/setting.json");
|
|
let original = std::fs::read(&settings).unwrap();
|
|
let value: serde_json::Value = serde_json::from_slice(&original).unwrap();
|
|
assert!(value.get("dever").is_none());
|
|
let agents = std::fs::read(root.join("AGENTS.md")).unwrap();
|
|
let expected = Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../skills")
|
|
.canonicalize()
|
|
.unwrap();
|
|
for (cwd, arguments) in [
|
|
(root.as_path(), vec!["skill", "path", "."]),
|
|
(root.as_path(), vec!["skill", "path"]),
|
|
(directory.0.as_path(), vec!["skill", "path"]),
|
|
] {
|
|
assert_eq!(
|
|
success(cli(cwd, &arguments)).trim(),
|
|
expected.to_str().unwrap()
|
|
);
|
|
}
|
|
assert_eq!(std::fs::read(&settings).unwrap(), original);
|
|
assert_eq!(std::fs::read(root.join("AGENTS.md")).unwrap(), agents);
|
|
// 查询与安装都不创建项目配置,更不引入项目版本锁定。
|
|
std::fs::remove_file(&settings).unwrap();
|
|
success(cli(&root, &["skill", "path", "."]));
|
|
success(cli(&root, &["skill", "install"]));
|
|
assert!(!settings.exists());
|
|
assert!(!cli(&directory.0, &["skill", "path", "."]).status.success());
|
|
assert!(!cli(&directory.0, &["skill", "install"]).status.success());
|
|
assert!(!directory.0.join("AGENTS.md").exists());
|
|
assert!(!cli(&root, &["skill", "install", "."]).status.success());
|
|
}
|
|
|
|
#[test]
|
|
fn install_preserves_unmarked_content_permissions_and_is_idempotent() {
|
|
let directory = Directory::new();
|
|
let root = directory.0.join("app");
|
|
dever::compiler::project::create(&root).unwrap();
|
|
let agents = root.join("AGENTS.md");
|
|
std::fs::remove_file(&agents).unwrap();
|
|
success(cli(&root, &["skill", "install"]));
|
|
assert_eq!(std::fs::read_to_string(&agents).unwrap(), template());
|
|
assert_eq!(std::fs::metadata(&agents).unwrap().mode() & 0o777, 0o644);
|
|
|
|
let prefix = "# 项目规则\r\n保留这些内容。";
|
|
std::fs::write(&agents, prefix).unwrap();
|
|
std::fs::set_permissions(&agents, std::fs::Permissions::from_mode(0o640)).unwrap();
|
|
success(cli(&root, &["skill", "install"]));
|
|
let installed = format!("{prefix}\n\n{}", template());
|
|
assert_eq!(std::fs::read_to_string(&agents).unwrap(), installed);
|
|
let before = std::fs::metadata(&agents).unwrap();
|
|
assert_eq!(before.mode() & 0o777, 0o640);
|
|
success(cli(&root, &["skill", "install"]));
|
|
let after = std::fs::metadata(&agents).unwrap();
|
|
assert_eq!(std::fs::read_to_string(&agents).unwrap(), installed);
|
|
assert_eq!(before.ino(), after.ino());
|
|
assert_eq!(before.modified().unwrap(), after.modified().unwrap());
|
|
|
|
let prefix = "保留头部\r\n\r\n";
|
|
let suffix = "\r\n\r\n保留尾部\r\n";
|
|
std::fs::write(
|
|
&agents,
|
|
format!(
|
|
"{prefix}<!-- dever-language:start -->\r\n旧规则\r\n<!-- dever-language:end -->{suffix}"
|
|
),
|
|
)
|
|
.unwrap();
|
|
success(cli(&root, &["skill", "install"]));
|
|
assert_eq!(
|
|
std::fs::read_to_string(&agents).unwrap(),
|
|
format!("{prefix}{}{suffix}", template().trim_end_matches('\n'))
|
|
);
|
|
|
|
// 同一项目的并发安装不得附加两份规则或留下暂存文件。
|
|
std::fs::remove_file(&agents).unwrap();
|
|
let mut first = Command::new(env!("CARGO_BIN_EXE_dever"))
|
|
.current_dir(&root)
|
|
.args(["skill", "install"])
|
|
.stdout(std::process::Stdio::null())
|
|
.spawn()
|
|
.unwrap();
|
|
success(cli(&root, &["skill", "install"]));
|
|
assert!(first.wait().unwrap().success());
|
|
assert_eq!(std::fs::read_to_string(&agents).unwrap(), template());
|
|
assert!(!std::fs::read_dir(&root).unwrap().any(|entry| {
|
|
entry
|
|
.unwrap()
|
|
.file_name()
|
|
.to_string_lossy()
|
|
.starts_with(".dever-skill-")
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn install_rejects_malformed_markers_and_links_without_mutation() {
|
|
let directory = Directory::new();
|
|
let root = directory.0.join("app");
|
|
dever::compiler::project::create(&root).unwrap();
|
|
let agents = root.join("AGENTS.md");
|
|
let start = "<!-- dever-language:start -->";
|
|
let end = "<!-- dever-language:end -->";
|
|
for text in [
|
|
start.to_owned(),
|
|
end.to_owned(),
|
|
format!("{end}\n{start}"),
|
|
format!("{start}\n{end}\n{start}\n{end}"),
|
|
format!("{start}\n{start}\n{end}"),
|
|
format!("{start}\n{end}\n{end}"),
|
|
"<!-- dever-language:unknown -->".into(),
|
|
format!("inline {start}\n{end}"),
|
|
] {
|
|
std::fs::write(&agents, &text).unwrap();
|
|
assert!(
|
|
!cli(&root, &["skill", "install"]).status.success(),
|
|
"{text}"
|
|
);
|
|
assert_eq!(std::fs::read_to_string(&agents).unwrap(), text);
|
|
}
|
|
std::fs::write(&agents, [0xff, 0xfe]).unwrap();
|
|
assert!(!cli(&root, &["skill", "install"]).status.success());
|
|
assert_eq!(std::fs::read(&agents).unwrap(), [0xff, 0xfe]);
|
|
std::fs::remove_file(&agents).unwrap();
|
|
let target = directory.0.join("private");
|
|
std::fs::write(&target, "untouched").unwrap();
|
|
symlink(&target, &agents).unwrap();
|
|
assert!(!cli(&root, &["skill", "install"]).status.success());
|
|
assert_eq!(std::fs::read_to_string(&target).unwrap(), "untouched");
|
|
assert!(std::fs::symlink_metadata(&agents).unwrap().is_symlink());
|
|
std::fs::remove_file(&agents).unwrap();
|
|
std::fs::create_dir(&agents).unwrap();
|
|
assert!(!cli(&root, &["skill", "install"]).status.success());
|
|
assert!(agents.is_dir());
|
|
let linked = directory.0.join("linked");
|
|
symlink(&root, &linked).unwrap();
|
|
assert!(
|
|
!cli(&directory.0, &["skill", "path", linked.to_str().unwrap()])
|
|
.status
|
|
.success()
|
|
);
|
|
assert!(!std::fs::read_dir(&root).unwrap().any(|entry| {
|
|
entry
|
|
.unwrap()
|
|
.file_name()
|
|
.to_string_lossy()
|
|
.starts_with(".dever-skill-")
|
|
}));
|
|
}
|