use crate::skills::*;
use std::path::PathBuf;
#[cfg(test)]
fn get_test_skill_path(skill_name: &str) -> String {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
format!("{}/examples/.claude/skills/{}/SKILL.md", manifest_dir, skill_name)
}
#[cfg(test)]
fn get_skills_dir() -> PathBuf {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
PathBuf::from(format!("{}/examples/.claude/skills", manifest_dir))
}
#[cfg(test)]
mod skill_md_tests {
use super::*;
#[test]
fn test_parse_pdf_processor_skill() {
let skill_md_path = get_test_skill_path("pdf-processor");
println!("Looking for skill at: {}", skill_md_path);
println!("File exists: {}", std::path::Path::new(&skill_md_path).exists());
let skill = SkillMdFile::parse(&skill_md_path).expect("Failed to parse SKILL.md");
assert_eq!(skill.metadata.name, "pdf-processor");
assert!(skill.metadata.description.contains("PDF"));
assert_eq!(skill.metadata.version, "2.0.0");
assert_eq!(skill.metadata.author, Some("Doc Team <docs@example.com>".to_string()));
assert_eq!(skill.metadata.tags.len(), 4);
assert!(skill.metadata.tags.contains(&"pdf".to_string()));
assert!(skill.metadata.tags.contains(&"documents".to_string()));
assert!(skill.metadata.allowed_tools.is_some());
let tools = skill.metadata.allowed_tools.as_ref().unwrap();
assert_eq!(tools.len(), 3);
assert!(tools.contains(&"Read".to_string()));
assert!(tools.contains(&"Bash(python:*)".to_string()));
assert!(tools.contains(&"Grep".to_string()));
assert_eq!(skill.metadata.model, Some("claude-sonnet-4-20250514".to_string()));
assert!(skill.reference.is_some()); assert!(skill.forms.is_some()); assert!(skill.scripts.len() > 0);
println!("✅ pdf-processor skill parsed successfully");
println!(" - Name: {}", skill.metadata.name);
println!(" - Version: {}", skill.metadata.version);
println!(" - Tags: {:?}", skill.metadata.tags);
println!(" - Allowed tools: {:?}", skill.metadata.allowed_tools);
println!(" - Scripts: {:?}", skill.scripts);
println!(" - Reference: {:?}", skill.reference);
println!(" - Forms: {:?}", skill.forms);
}
#[test]
fn test_parse_hooks_test_skill() {
let skill_md_path = get_test_skill_path("hooks-test-skill");
let skill = SkillMdFile::parse(&skill_md_path).expect("Failed to parse SKILL.md");
println!("Skill name: {}", skill.metadata.name);
println!("Hooks present: {:?}", skill.metadata.hooks.is_some());
assert_eq!(skill.metadata.name, "hooks-test");
assert!(skill.metadata.description.contains("hooks"));
assert!(skill.metadata.hooks.is_some(), "Hooks field should be Some");
let hooks = skill.metadata.hooks.as_ref().unwrap();
assert!(hooks.pre_tool_use.is_some(), "PreToolUse should be Some");
let pre_tool_use = hooks.pre_tool_use.as_ref().unwrap();
assert_eq!(pre_tool_use.len(), 3);
let bash_hook = &pre_tool_use[0];
assert_eq!(bash_hook.matcher, "Bash");
assert_eq!(bash_hook.command, "echo '🔍 PreToolUse: About to execute Bash tool'");
assert_eq!(bash_hook.r#type, Some(HookType::Command));
assert_eq!(bash_hook.once, Some(false));
assert!(hooks.post_tool_use.is_some());
let post_tool_use = hooks.post_tool_use.as_ref().unwrap();
assert_eq!(post_tool_use.len(), 2);
assert!(hooks.stop.is_some());
let stop = hooks.stop.as_ref().unwrap();
assert_eq!(stop.len(), 1);
assert_eq!(stop[0].matcher, "*");
println!("✅ hooks-test-skill parsed successfully");
println!(" - PreToolUse hooks: {}", pre_tool_use.len());
println!(" - PostToolUse hooks: {}", post_tool_use.len());
println!(" - Stop hooks: {}", stop.len());
}
#[test]
fn test_parse_context_fork_skill() {
let skill_md_path = get_test_skill_path("context-fork-skill");
let skill = SkillMdFile::parse(&skill_md_path).expect("Failed to parse SKILL.md");
assert_eq!(skill.metadata.name, "context-fork-skill");
assert!(skill.metadata.description.contains("forked context"));
assert_eq!(skill.metadata.context, Some(SkillContext::Fork));
assert_eq!(skill.metadata.agent, Some("general-purpose".to_string()));
assert!(skill.metadata.allowed_tools.is_some());
let tools = skill.metadata.allowed_tools.as_ref().unwrap();
assert_eq!(tools.len(), 5);
assert!(tools.contains(&"Read".to_string()));
assert!(tools.contains(&"Write".to_string()));
println!("✅ context-fork-skill parsed successfully");
println!(" - Context: {:?}", skill.metadata.context);
println!(" - Agent: {:?}", skill.metadata.agent);
println!(" - Allowed tools: {:?}", tools);
}
#[test]
fn test_parse_code_reviewer_skill() {
let skill_md_path = get_test_skill_path("code-reviewer");
let skill = SkillMdFile::parse(&skill_md_path).expect("Failed to parse SKILL.md");
assert_eq!(skill.metadata.dependencies.len(), 2);
assert!(skill.metadata.dependencies.contains(&"linter".to_string()));
assert!(skill.metadata.dependencies.contains(&"security-analyzer".to_string()));
println!("✅ code-reviewer skill parsed successfully");
println!(" - Dependencies: {:?}", skill.metadata.dependencies);
}
#[test]
fn test_progressive_disclosure_pdf_processor() {
let skill_md_path = get_test_skill_path("pdf-processor");
let skill = SkillMdFile::parse(&skill_md_path).expect("Failed to parse SKILL.md");
assert!(skill.reference.is_some(), "reference.md should be discovered");
assert!(skill.forms.is_some(), "forms.md should be discovered");
assert!(!skill.scripts.is_empty(), "scripts/ should contain files");
assert!(skill.scripts.iter().any(|p| p.ends_with("validate.py")));
assert!(skill.scripts.iter().any(|p| p.ends_with("merge.py")));
assert!(skill.scripts.iter().any(|p| p.ends_with("extract_forms.py")));
let reference_path = skill.reference.as_ref().unwrap();
assert!(reference_path.exists(), "reference.md should exist");
let forms_path = skill.forms.as_ref().unwrap();
assert!(forms_path.exists(), "forms.md should exist");
for script in &skill.scripts {
assert!(script.exists(), "Script should exist: {}", script.display());
}
println!("✅ Progressive disclosure test passed");
println!(" - reference.md: {} bytes", std::fs::metadata(reference_path).unwrap().len());
println!(" - forms.md: {} bytes", std::fs::metadata(forms_path).unwrap().len());
println!(" - scripts: {} files found", skill.scripts.len());
}
#[test]
fn test_hook_config_serialization() {
let yaml = r#"---
name: test-hooks
description: Test hook serialization
hooks:
pre_tool_use:
- matcher: "Bash"
command: "echo 'test'"
type: command
once: true
post_tool_use:
- matcher: "*"
command: "scripts/test.sh"
type: script
stop:
- matcher: "Write"
command: "cleanup"
type: function
---
# Test Hooks
This is a test skill for hook serialization.
"#;
let temp_dir = std::env::temp_dir();
let skill_path = temp_dir.join("test-hooks-skill.md");
std::fs::write(&skill_path, yaml).expect("Failed to write test skill");
let skill = SkillMdFile::parse(&skill_path).expect("Failed to parse test skill");
assert!(skill.metadata.hooks.is_some());
let hooks = skill.metadata.hooks.as_ref().unwrap();
assert_eq!(hooks.pre_tool_use.as_ref().unwrap().len(), 1);
let pre = &hooks.pre_tool_use.as_ref().unwrap()[0];
assert_eq!(pre.matcher, "Bash");
assert_eq!(pre.r#type, Some(HookType::Command));
assert_eq!(pre.once, Some(true));
assert_eq!(hooks.post_tool_use.as_ref().unwrap().len(), 1);
let post = &hooks.post_tool_use.as_ref().unwrap()[0];
assert_eq!(post.matcher, "*");
assert_eq!(post.r#type, Some(HookType::Script));
assert_eq!(hooks.stop.as_ref().unwrap().len(), 1);
let stop = &hooks.stop.as_ref().unwrap()[0];
assert_eq!(stop.matcher, "Write");
assert_eq!(stop.r#type, Some(HookType::Function));
std::fs::remove_file(&skill_path).ok();
println!("✅ Hook serialization test passed");
}
#[test]
fn test_user_invocable_and_disable_model_invocation() {
let yaml = r#"---
name: internal-standards
description: Internal company standards (Claude-only)
user_invocable: false
disable_model_invocation: false
---
# Internal Standards
Internal company standards skill.
"#;
let temp_dir = std::env::temp_dir();
let skill_path = temp_dir.join("test-skill.md");
std::fs::write(&skill_path, yaml).expect("Failed to write test skill");
let skill = SkillMdFile::parse(&skill_path).expect("Failed to parse test skill");
assert!(!skill.metadata.user_invocable);
assert_eq!(skill.metadata.disable_model_invocation, Some(false));
std::fs::remove_file(&skill_path).ok();
println!("✅ User invocable settings test passed");
}
}
#[cfg(test)]
mod skill_discovery_tests {
use super::*;
#[test]
fn test_discover_all_example_skills() {
let skills_dir = get_skills_dir();
let packages = SkillRegistry::discover_skill_md_from_dir(&skills_dir)
.expect("Failed to discover skills");
assert!(packages.len() >= 25, "Should find at least 25 skills (22 original + 3 new), found {}", packages.len());
let skill_names: Vec<String> = packages.iter()
.map(|p| p.metadata.clone().name)
.collect();
assert!(skill_names.contains(&"pdf-processor".to_string()));
assert!(skill_names.contains(&"hooks-test".to_string()));
assert!(skill_names.contains(&"context-fork-skill".to_string()));
assert!(skill_names.contains(&"code-reviewer".to_string()));
println!("✅ Discovered {} skills:", packages.len());
for name in &skill_names {
println!(" - {}", name);
}
}
#[test]
fn test_skill_priority_override() {
let _personal_dir = PathBuf::from("test-data/personal-skills");
let _project_dir = PathBuf::from("examples/.claude/skills");
println!("✅ Skill priority test API verified");
}
}
#[cfg(test)]
mod dependency_tests {
use super::*;
#[test]
fn test_dependency_resolution() {
let mut resolver = DependencyResolver::new();
resolver.add_skill("linter", "1.0.0");
resolver.add_skill("security-analyzer", "1.0.0");
resolver.add_skill("code-reviewer", "2.0.0");
let mut skills_graph = std::collections::HashMap::new();
skills_graph.insert("code-reviewer".to_string(), vec![
Dependency::new("linter"),
Dependency::new("security-analyzer")
]);
skills_graph.insert("linter".to_string(), vec![]);
skills_graph.insert("security-analyzer".to_string(), vec![]);
match resolver.resolve(&skills_graph) {
ResolutionResult::Resolved { load_order } => {
println!("✅ Dependency resolution successful");
println!(" Load order: {:?}", load_order);
assert_eq!(load_order.last(), Some(&"code-reviewer".to_string()));
},
ResolutionResult::CircularDependency { cycle } => {
panic!("Unexpected circular dependency: {:?}", cycle);
},
ResolutionResult::MissingDependencies { missing } => {
panic!("Unexpected missing dependencies: {:?}", missing);
},
}
}
#[test]
fn test_circular_dependency_detection() {
let mut resolver = DependencyResolver::new();
resolver.add_skill("skill-a", "1.0.0");
resolver.add_skill("skill-b", "1.0.0");
resolver.add_skill("skill-c", "1.0.0");
let mut skills_graph = std::collections::HashMap::new();
skills_graph.insert("skill-a".to_string(), vec![Dependency::new("skill-b")]);
skills_graph.insert("skill-b".to_string(), vec![Dependency::new("skill-c")]);
skills_graph.insert("skill-c".to_string(), vec![Dependency::new("skill-a")]);
match resolver.resolve(&skills_graph) {
ResolutionResult::CircularDependency { cycle } => {
println!("✅ Circular dependency detected correctly");
println!(" Cycle: {:?}", cycle);
assert_eq!(cycle.len(), 4); },
_ => {
panic!("Expected circular dependency error");
},
}
}
}
#[cfg(test)]
mod tool_restriction_tests {
use super::*;
#[test]
fn test_tool_restriction_patterns() {
let allowed_tools = vec![
"Read".to_string(),
"Grep".to_string(),
"Bash(python:*)".to_string(),
];
let restriction = ToolRestriction::new(Some(allowed_tools));
assert!(restriction.is_tool_allowed("Read"));
assert!(restriction.is_tool_allowed("Grep"));
assert!(!restriction.is_tool_allowed("Write"));
assert!(!restriction.is_tool_allowed("Delete"));
println!("✅ Tool restriction test passed");
}
#[test]
fn test_wildcard_tool_restriction() {
let allowed_tools = vec!["*".to_string()];
let restriction = ToolRestriction::new(Some(allowed_tools));
assert!(restriction.is_tool_allowed("Read"));
assert!(restriction.is_tool_allowed("Write"));
assert!(restriction.is_tool_allowed("Bash"));
assert!(restriction.is_tool_allowed("AnyTool"));
println!("✅ Wildcard tool restriction test passed");
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[tokio::test]
async fn test_skill_registration_and_retrieval() {
let registry = SkillRegistry::new();
let _registry = registry;
println!("✅ Skill registration API verified");
}
#[tokio::test]
async fn test_multi_skill_discovery() {
let skills_dir = get_skills_dir();
let packages = SkillRegistry::discover_skill_md_from_dir(&skills_dir)
.expect("Failed to discover skills");
assert!(packages.len() >= 25, "Should find at least 25 skills, found {}", packages.len());
println!("✅ Multi-skill discovery test passed");
println!(" Found {} skills", packages.len());
}
}