test(protocols,skills): add 90 tests for MCP types + skill loader/runner
zclaw-protocols: +43 tests covering mcp_types serde, ContentBlock variants, transport config builders, and domain type roundtrips. zclaw-skills: +47 tests covering SKILL.md/TOML parsing, auto-classify, PromptOnlySkill execution, and SkillManifest/SkillResult roundtrips. Batch 8 of audit plan (plans/stateless-petting-rossum.md).
This commit is contained in:
247
crates/zclaw-skills/tests/loader_tests.rs
Normal file
247
crates/zclaw-skills/tests/loader_tests.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
//! Tests for skill loader — SKILL.md and TOML parsing
|
||||
|
||||
use zclaw_skills::*;
|
||||
|
||||
// === parse_skill_md ===
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_basic_frontmatter() {
|
||||
let content = r#"---
|
||||
name: "Code Reviewer"
|
||||
description: "Reviews code"
|
||||
version: "1.0.0"
|
||||
mode: prompt-only
|
||||
tags: coding, review
|
||||
---
|
||||
# Code Reviewer
|
||||
Reviews code for quality.
|
||||
"#;
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.name, "Code Reviewer");
|
||||
assert_eq!(manifest.description, "Reviews code");
|
||||
assert_eq!(manifest.version, "1.0.0");
|
||||
assert_eq!(manifest.mode, zclaw_skills::SkillMode::PromptOnly);
|
||||
assert_eq!(manifest.tags, vec!["coding", "review"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_with_triggers_list() {
|
||||
let content = r#"---
|
||||
name: "Translator"
|
||||
description: "Translates text"
|
||||
version: "1.0.0"
|
||||
mode: prompt-only
|
||||
triggers:
|
||||
- "翻译"
|
||||
- "translate"
|
||||
- "中译英"
|
||||
---
|
||||
# Translator
|
||||
"#;
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.triggers, vec!["翻译", "translate", "中译英"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_with_tools_list() {
|
||||
let content = r#"---
|
||||
name: "Builder"
|
||||
description: "Builds projects"
|
||||
version: "1.0.0"
|
||||
mode: shell
|
||||
tools:
|
||||
- "bash"
|
||||
- "cargo"
|
||||
---
|
||||
# Builder
|
||||
"#;
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.tools, vec!["bash", "cargo"]);
|
||||
assert_eq!(manifest.mode, zclaw_skills::SkillMode::Shell);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_with_category() {
|
||||
let content = r#"---
|
||||
name: "Math Solver"
|
||||
description: "Solves math problems"
|
||||
version: "1.0.0"
|
||||
mode: prompt-only
|
||||
category: "math"
|
||||
---
|
||||
# Math Solver
|
||||
"#;
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.category.unwrap(), "math");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_auto_classify_coding() {
|
||||
let content = r#"---
|
||||
name: "Code Helper"
|
||||
description: "Helps with programming and debugging"
|
||||
version: "1.0.0"
|
||||
mode: prompt-only
|
||||
---
|
||||
# Code Helper
|
||||
"#;
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
// Should auto-classify as "coding" based on description
|
||||
assert_eq!(manifest.category.unwrap(), "coding");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_auto_classify_translation() {
|
||||
let content = r#"---
|
||||
name: "Translator"
|
||||
description: "Helps with translation between languages"
|
||||
version: "1.0.0"
|
||||
mode: prompt-only
|
||||
---
|
||||
# Translator
|
||||
"#;
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
// Should auto-classify based on "translat" keyword
|
||||
assert!(manifest.category.is_some(), "Should auto-classify translation skill");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_no_frontmatter_extracts_name() {
|
||||
let content = "# My Skill\n\nThis is a cool skill.";
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.name, "My Skill");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_fallback_name() {
|
||||
let content = "Just some text without structure.";
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.name, "unnamed-skill");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_id_generation() {
|
||||
let content = "---\nname: \"Hello World\"\n---\n";
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.id.as_str(), "hello-world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_all_modes() {
|
||||
for (mode_str, expected) in &[
|
||||
("prompt-only", zclaw_skills::SkillMode::PromptOnly),
|
||||
("python", zclaw_skills::SkillMode::Python),
|
||||
("shell", zclaw_skills::SkillMode::Shell),
|
||||
("wasm", zclaw_skills::SkillMode::Wasm),
|
||||
("native", zclaw_skills::SkillMode::Native),
|
||||
] {
|
||||
let content = format!("---\nname: \"Test\"\nmode: {}\n---\n", mode_str);
|
||||
let manifest = parse_skill_md(&content).unwrap();
|
||||
assert_eq!(&manifest.mode, expected, "Failed for mode: {}", mode_str);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_md_capabilities_csv() {
|
||||
let content = "---\nname: \"Multi\"\ncapabilities: llm, web, file\n---\n";
|
||||
let manifest = parse_skill_md(content).unwrap();
|
||||
assert_eq!(manifest.capabilities, vec!["llm", "web", "file"]);
|
||||
}
|
||||
|
||||
// === parse_skill_toml ===
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_basic() {
|
||||
let content = r#"
|
||||
name = "Calculator"
|
||||
description = "Performs calculations"
|
||||
version = "2.0.0"
|
||||
mode = "prompt_only"
|
||||
"#;
|
||||
let manifest = parse_skill_toml(content).unwrap();
|
||||
assert_eq!(manifest.name, "Calculator");
|
||||
assert_eq!(manifest.description, "Performs calculations");
|
||||
assert_eq!(manifest.version, "2.0.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_with_id() {
|
||||
let content = r#"
|
||||
id = "my-calc"
|
||||
name = "Calculator"
|
||||
description = "Calc"
|
||||
"#;
|
||||
let manifest = parse_skill_toml(content).unwrap();
|
||||
assert_eq!(manifest.id.as_str(), "my-calc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_generates_id_from_name() {
|
||||
let content = "name = \"Hello World\"\ndescription = \"x\"";
|
||||
let manifest = parse_skill_toml(content).unwrap();
|
||||
assert_eq!(manifest.id.as_str(), "hello-world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_requires_name() {
|
||||
let content = r#"description = "no name""#;
|
||||
let result = parse_skill_toml(content);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_arrays() {
|
||||
let content = r#"
|
||||
name = "X"
|
||||
description = "x"
|
||||
tags = ["a", "b", "c"]
|
||||
capabilities = ["llm"]
|
||||
triggers = ["go", "run"]
|
||||
"#;
|
||||
let manifest = parse_skill_toml(content).unwrap();
|
||||
assert_eq!(manifest.tags, vec!["a", "b", "c"]);
|
||||
assert_eq!(manifest.capabilities, vec!["llm"]);
|
||||
assert_eq!(manifest.triggers, vec!["go", "run"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_category() {
|
||||
let content = r#"
|
||||
name = "X"
|
||||
description = "x"
|
||||
category = "data"
|
||||
"#;
|
||||
let manifest = parse_skill_toml(content).unwrap();
|
||||
assert_eq!(manifest.category.unwrap(), "data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_tools() {
|
||||
let content = r#"
|
||||
name = "X"
|
||||
description = "x"
|
||||
tools = ["bash", "cargo"]
|
||||
"#;
|
||||
let manifest = parse_skill_toml(content).unwrap();
|
||||
assert_eq!(manifest.tools, vec!["bash", "cargo"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_toml_ignores_comments_and_sections() {
|
||||
let content = r#"
|
||||
# This is a comment
|
||||
[section]
|
||||
name = "X"
|
||||
description = "x"
|
||||
"#;
|
||||
let manifest = parse_skill_toml(content).unwrap();
|
||||
assert_eq!(manifest.name, "X");
|
||||
}
|
||||
|
||||
// === discover_skills ===
|
||||
|
||||
#[test]
|
||||
fn discover_skills_nonexistent_dir() {
|
||||
let result = discover_skills(std::path::Path::new("/nonexistent/path")).unwrap();
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
Reference in New Issue
Block a user