| 1 | """Direct unit tests for skill_meta.read_skill_version. |
| 2 | |
| 3 | Covers the helper's own contract independent of render._skill_version which |
| 4 | exercises it transitively. Without these, regressions in error handling or |
| 5 | regex coverage inside the helper could pass CI because render.py's fallback |
| 6 | to "?" swallows the signal. |
| 7 | """ |
| 8 | |
| 9 | import tempfile |
| 10 | import unittest |
| 11 | from pathlib import Path |
| 12 | |
| 13 | from lib.skill_meta import read_skill_version |
| 14 | |
| 15 | ROOT = Path(__file__).resolve().parents[1] |
| 16 | |
| 17 | |
| 18 | class ReadSkillVersionTests(unittest.TestCase): |
| 19 | def setUp(self) -> None: |
| 20 | self._tmp = tempfile.TemporaryDirectory() |
| 21 | self.tmp_path = Path(self._tmp.name) |
| 22 | |
| 23 | def tearDown(self) -> None: |
| 24 | self._tmp.cleanup() |
| 25 | |
| 26 | def _write_skill_md(self, body: str) -> Path: |
| 27 | path = self.tmp_path / "SKILL.md" |
| 28 | path.write_text(body) |
| 29 | return path |
| 30 | |
| 31 | def test_double_quoted_version(self) -> None: |
| 32 | path = self._write_skill_md('---\nname: x\nversion: "9.9.9"\n---\n') |
| 33 | self.assertEqual("9.9.9", read_skill_version(path)) |
| 34 | |
| 35 | def test_single_quoted_version(self) -> None: |
| 36 | path = self._write_skill_md("---\nname: x\nversion: '8.8.8'\n---\n") |
| 37 | self.assertEqual("8.8.8", read_skill_version(path)) |
| 38 | |
| 39 | def test_unquoted_version(self) -> None: |
| 40 | path = self._write_skill_md("---\nname: x\nversion: 7.7.7\n---\n") |
| 41 | self.assertEqual("7.7.7", read_skill_version(path)) |
| 42 | |
| 43 | def test_missing_file_returns_none(self) -> None: |
| 44 | self.assertIsNone(read_skill_version(self.tmp_path / "does-not-exist.md")) |
| 45 | |
| 46 | def test_no_version_line_returns_none(self) -> None: |
| 47 | path = self._write_skill_md("---\nname: x\n---\n# body without version\n") |
| 48 | self.assertIsNone(read_skill_version(path)) |
| 49 | |
| 50 | def test_undecodable_bytes_returns_none(self) -> None: |
| 51 | # Bytes 128-255 don't form valid UTF-8 sequences; read_text() raises |
| 52 | # UnicodeDecodeError which the helper must catch. |
| 53 | path = self.tmp_path / "SKILL.md" |
| 54 | path.write_bytes(bytes(range(128, 256))) |
| 55 | self.assertIsNone(read_skill_version(path)) |
| 56 | |
| 57 | if __name__ == "__main__": |
| 58 | unittest.main() |
| 59 |