Files
ezra-environment/tests/test_skill_validator.py

200 lines
5.6 KiB
Python
Raw Normal View History

#!/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()