Files
ezra-environment/tests/test_skill_validator.py

200 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Tests for skill validator module."""
import os
import sys
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from tools.skill_validator import SkillValidator, SkillValidationError
VALID_SKILL = """---
name: test-skill
description: A valid test skill for validation
version: "1.0.0"
author: ezra
tags: [testing, validation]
---
# Test Skill
## Trigger
Use when testing skill validation.
## Steps
1. First step: do something
2. Second step: verify
3. Third step: done
```bash
echo "hello world"
```
## Pitfalls
- Don't forget to test edge cases
## Verification
- Check the output matches expected
"""
MINIMAL_SKILL = """---
name: minimal
description: Minimal skill
version: "1.0"
---
## Trigger
When needed.
## Steps
1. Do it.
2. Done.
"""
BROKEN_SKILL_NO_FM = """# No Frontmatter Skill
## Steps
1. This will fail validation
"""
BROKEN_SKILL_BAD_YAML = """---
name: [invalid yaml
---
## Steps
1. test
"""
BROKEN_SKILL_MISSING_FIELDS = """---
description: Missing name and version
---
## Steps
1. test
"""
class TestSkillValidationError(unittest.TestCase):
def test_repr_error(self):
e = SkillValidationError("ERROR", "bad thing", "frontmatter")
self.assertIn("", repr(e))
self.assertIn("bad thing", repr(e))
def test_repr_warning(self):
e = SkillValidationError("WARNING", "maybe bad")
self.assertIn("⚠️", repr(e))
def test_repr_info(self):
e = SkillValidationError("INFO", "just fyi")
self.assertIn("", repr(e))
class TestSkillValidator(unittest.TestCase):
def setUp(self):
self.validator = SkillValidator()
self.tmp_dir = tempfile.mkdtemp()
def tearDown(self):
import shutil
shutil.rmtree(self.tmp_dir, ignore_errors=True)
def _write_skill(self, content: str, name: str = "test-skill") -> Path:
skill_dir = Path(self.tmp_dir) / name
skill_dir.mkdir(parents=True, exist_ok=True)
path = skill_dir / "SKILL.md"
path.write_text(content)
return path
def test_valid_skill_no_errors(self):
path = self._write_skill(VALID_SKILL)
errors = self.validator.validate_file(path)
error_count = len([e for e in errors if e.level == "ERROR"])
self.assertEqual(error_count, 0, f"Unexpected errors: {errors}")
def test_minimal_skill_warnings_only(self):
path = self._write_skill(MINIMAL_SKILL, "minimal")
errors = self.validator.validate_file(path)
error_count = len([e for e in errors if e.level == "ERROR"])
self.assertEqual(error_count, 0)
# Should have warnings for missing recommended sections
warning_count = len([e for e in errors if e.level == "WARNING"])
self.assertGreater(warning_count, 0)
def test_no_frontmatter_error(self):
path = self._write_skill(BROKEN_SKILL_NO_FM, "broken1")
errors = self.validator.validate_file(path)
fm_errors = [e for e in errors if "frontmatter" in e.field and e.level == "ERROR"]
self.assertGreater(len(fm_errors), 0)
def test_bad_yaml_error(self):
path = self._write_skill(BROKEN_SKILL_BAD_YAML, "broken2")
errors = self.validator.validate_file(path)
yaml_errors = [e for e in errors if "YAML" in e.message or "frontmatter" in e.field]
self.assertGreater(len(yaml_errors), 0)
def test_missing_required_fields(self):
path = self._write_skill(BROKEN_SKILL_MISSING_FIELDS, "broken3")
errors = self.validator.validate_file(path)
missing = [e for e in errors if "Missing required" in e.message]
self.assertGreater(len(missing), 0)
def test_file_not_found(self):
errors = self.validator.validate_file(Path("/nonexistent/SKILL.md"))
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].level, "ERROR")
def test_empty_file(self):
path = self._write_skill("", "empty")
errors = self.validator.validate_file(path)
self.assertTrue(any(e.message == "File is empty" for e in errors))
def test_invalid_name_format(self):
skill = """---
name: BAD NAME!
description: test
version: "1.0"
---
## Trigger
test
## Steps
1. test
2. done
"""
path = self._write_skill(skill, "badname")
errors = self.validator.validate_file(path)
name_errors = [e for e in errors if "Invalid name" in e.message]
self.assertGreater(len(name_errors), 0)
def test_validate_all(self):
self._write_skill(VALID_SKILL, "skill-a")
self._write_skill(MINIMAL_SKILL, "skill-b")
results = self.validator.validate_all(Path(self.tmp_dir))
self.assertEqual(len(results), 2)
self.assertIn("skill-a", results)
self.assertIn("skill-b", results)
def test_format_report(self):
self._write_skill(VALID_SKILL, "good")
self._write_skill(BROKEN_SKILL_NO_FM, "bad")
results = self.validator.validate_all(Path(self.tmp_dir))
report = self.validator.format_report(results)
self.assertIn("Skill Validation Report", report)
self.assertIn("good", report)
self.assertIn("bad", report)
def test_nonstandard_subdir_warning(self):
skill_dir = Path(self.tmp_dir) / "weirdskill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text(VALID_SKILL)
(skill_dir / "random_dir").mkdir()
errors = self.validator.validate_file(skill_dir / "SKILL.md")
dir_warnings = [e for e in errors if "Non-standard" in e.message]
self.assertGreater(len(dir_warnings), 0)
if __name__ == "__main__":
unittest.main()