Files
zclaw_openfang/crates/zclaw-skills/tests/loader_tests.rs
iven beeb529d8f 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).
2026-04-19 11:24:57 +08:00

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());
}