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).
248 lines
6.1 KiB
Rust
248 lines
6.1 KiB
Rust
//! 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());
|
|
}
|