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