use std::{
fs,
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use mentra::{BuiltinProvider, Runtime, SkillInfo, SkillLoadError};
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
fn temp_dir(label: &str) -> PathBuf {
let unique = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let stamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time")
.as_nanos();
let path = std::env::temp_dir().join(format!("mentra-skills-api-{label}-{stamp}-{unique}"));
fs::create_dir_all(&path).expect("create temp dir");
path
}
fn write_skill(root: &Path, dir: &str, name: &str, description: &str, body: &str) {
let skill_dir = root.join(dir);
fs::create_dir_all(&skill_dir).expect("create skill dir");
fs::write(
skill_dir.join("SKILL.md"),
format!("---\nname: {name}\ndescription: {description}\n---\n{body}\n"),
)
.expect("write skill");
}
fn runtime() -> Runtime {
Runtime::builder()
.with_provider(BuiltinProvider::OpenAI, "test-key")
.build()
.expect("runtime builds")
}
fn register(runtime: &Runtime, path: &Path) -> Result<(), SkillLoadError> {
runtime.register_skills_dir(path)
}
#[test]
fn a_host_can_name_the_error_type() {
let runtime = runtime();
let missing = temp_dir("nameable").join("does-not-exist");
let error = register(&runtime, &missing).expect_err("an unreadable root is an error");
assert!(matches!(error, SkillLoadError::ReadDir { .. }));
}
#[test]
fn registering_a_second_root_keeps_the_first() {
let workspace = temp_dir("layer-workspace");
write_skill(&workspace, "review", "review", "project review", "PROJECT");
let global = temp_dir("layer-global");
write_skill(&global, "review", "review", "personal review", "PERSONAL");
write_skill(&global, "deploy", "deploy", "personal deploy", "DEPLOY");
let runtime = runtime();
runtime
.register_skills_dirs([workspace.as_path(), global.as_path()])
.expect("both roots register");
let skills = runtime.skills();
let names: Vec<&str> = skills.iter().map(|skill| skill.name.as_str()).collect();
assert_eq!(names, vec!["deploy", "review"]);
let review = skills
.iter()
.find(|skill| skill.name == "review")
.expect("review present");
assert_eq!(
review.description, "project review",
"the earlier root must win the collision"
);
assert!(review.path.starts_with(&workspace));
}
#[test]
fn enumeration_reports_name_description_and_source() {
let root = temp_dir("enumerate");
write_skill(&root, "haiku", "haiku", "writes haiku", "BODY");
let runtime = runtime();
runtime.register_skills_dir(&root).expect("registers");
let skills = runtime.skills();
assert_eq!(skills.len(), 1);
let SkillInfo {
name,
description,
path,
} = &skills[0];
assert_eq!(name, "haiku");
assert_eq!(description, "writes haiku");
assert_eq!(path, &root.join("haiku").join("SKILL.md"));
}
#[test]
fn a_runtime_without_skills_lists_none() {
assert!(runtime().skills().is_empty());
}
#[test]
fn a_duplicate_name_inside_one_root_is_still_an_error() {
let root = temp_dir("duplicate");
write_skill(&root, "first", "shared", "one", "A");
write_skill(&root, "second", "shared", "two", "B");
let error = runtime()
.register_skills_dir(&root)
.expect_err("a repeated name in one root is a mistake");
assert!(matches!(error, SkillLoadError::DuplicateSkillName { .. }));
}
#[test]
fn roots_before_a_failing_one_stay_registered() {
let good = temp_dir("partial-good");
write_skill(&good, "keep", "keep", "kept", "BODY");
let missing = temp_dir("partial-missing").join("absent");
let runtime = runtime();
let error = runtime
.register_skills_dirs([good.as_path(), missing.as_path()])
.expect_err("the second root fails");
assert!(matches!(error, SkillLoadError::ReadDir { .. }));
assert_eq!(
runtime.skills().len(),
1,
"the root that loaded before the failure stays registered"
);
}