Files
agentskills/skills-ref/tests/test_parser.py
T
Keith Lazuka 547831f3a2 Add skills-ref library (#2)
Reference library for Agent Skills with CLI and Python API:
- validate: Check skill directories for valid SKILL.md with proper frontmatter
- read-properties: Parse and output skill properties as JSON
- to-prompt: Generate suggested <available_skills> XML for agent prompts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 09:39:21 -05:00

189 lines
4.7 KiB
Python

"""Tests for parser module."""
import pytest
from skills_ref.parser import (
ParseError,
ValidationError,
find_skill_md,
parse_frontmatter,
read_properties,
)
def test_valid_frontmatter():
content = """---
name: my-skill
description: A test skill
---
# My Skill
Instructions here.
"""
metadata, body = parse_frontmatter(content)
assert metadata["name"] == "my-skill"
assert metadata["description"] == "A test skill"
assert "# My Skill" in body
def test_missing_frontmatter():
content = "# No frontmatter here"
with pytest.raises(ParseError, match="must start with YAML frontmatter"):
parse_frontmatter(content)
def test_unclosed_frontmatter():
content = """---
name: my-skill
description: A test skill
"""
with pytest.raises(ParseError, match="not properly closed"):
parse_frontmatter(content)
def test_invalid_yaml():
content = """---
name: [invalid
description: broken
---
Body here
"""
with pytest.raises(ParseError, match="Invalid YAML"):
parse_frontmatter(content)
def test_non_dict_frontmatter():
content = """---
- just
- a
- list
---
Body
"""
with pytest.raises(ParseError, match="must be a YAML mapping"):
parse_frontmatter(content)
def test_read_valid_skill(tmp_path):
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text("""---
name: my-skill
description: A test skill
license: MIT
---
# My Skill
""")
props = read_properties(skill_dir)
assert props.name == "my-skill"
assert props.description == "A test skill"
assert props.license == "MIT"
def test_read_with_metadata(tmp_path):
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text("""---
name: my-skill
description: A test skill
metadata:
author: Test Author
version: 1.0
---
Body
""")
props = read_properties(skill_dir)
assert props.metadata == {"author": "Test Author", "version": "1.0"}
def test_missing_skill_md(tmp_path):
with pytest.raises(ParseError, match="SKILL.md not found"):
read_properties(tmp_path)
def test_missing_name(tmp_path):
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text("""---
description: A test skill
---
Body
""")
with pytest.raises(ValidationError, match="Missing required field.*name"):
read_properties(skill_dir)
def test_missing_description(tmp_path):
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text("""---
name: my-skill
---
Body
""")
with pytest.raises(ValidationError, match="Missing required field.*description"):
read_properties(skill_dir)
def test_find_skill_md_prefers_uppercase(tmp_path):
"""SKILL.md should be preferred over skill.md when both exist."""
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text("uppercase")
(skill_dir / "skill.md").write_text("lowercase")
result = find_skill_md(skill_dir)
assert result is not None
assert result.name == "SKILL.md"
def test_find_skill_md_accepts_lowercase(tmp_path):
"""skill.md should be accepted when SKILL.md doesn't exist."""
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "skill.md").write_text("lowercase")
result = find_skill_md(skill_dir)
assert result is not None
# Check case-insensitively since some filesystems are case-insensitive
assert result.name.lower() == "skill.md"
def test_find_skill_md_returns_none_when_missing(tmp_path):
"""find_skill_md should return None when no skill.md exists."""
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
result = find_skill_md(skill_dir)
assert result is None
def test_read_properties_with_lowercase_skill_md(tmp_path):
"""read_properties should work with lowercase skill.md."""
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "skill.md").write_text("""---
name: my-skill
description: A test skill
---
# My Skill
""")
props = read_properties(skill_dir)
assert props.name == "my-skill"
assert props.description == "A test skill"
def test_read_with_allowed_tools(tmp_path):
"""allowed-tools should be parsed into SkillProperties."""
skill_dir = tmp_path / "my-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text("""---
name: my-skill
description: A test skill
allowed-tools: Bash(jq:*) Bash(git:*)
---
Body
""")
props = read_properties(skill_dir)
assert props.allowed_tools == "Bash(jq:*) Bash(git:*)"
# Verify to_dict outputs as "allowed-tools" (hyphenated)
d = props.to_dict()
assert d["allowed-tools"] == "Bash(jq:*) Bash(git:*)"