diff --git a/tests/test_package_skill.py b/tests/test_package_skill.py index 4f08f18..b05b3c2 100644 --- a/tests/test_package_skill.py +++ b/tests/test_package_skill.py @@ -42,7 +42,7 @@ class TestPackageSkill(unittest.TestCase): with tempfile.TemporaryDirectory() as tmpdir: skill_dir = self.create_test_skill_directory(tmpdir) - success, zip_path = package_skill(skill_dir, open_folder_after=False) + success, zip_path = package_skill(skill_dir, open_folder_after=False, skip_quality_check=True) self.assertTrue(success) self.assertIsNotNone(zip_path) @@ -55,7 +55,7 @@ class TestPackageSkill(unittest.TestCase): with tempfile.TemporaryDirectory() as tmpdir: skill_dir = self.create_test_skill_directory(tmpdir) - success, zip_path = package_skill(skill_dir, open_folder_after=False) + success, zip_path = package_skill(skill_dir, open_folder_after=False, skip_quality_check=True) self.assertTrue(success) @@ -78,7 +78,7 @@ class TestPackageSkill(unittest.TestCase): # Add a backup file (skill_dir / "SKILL.md.backup").write_text("# Backup") - success, zip_path = package_skill(skill_dir, open_folder_after=False) + success, zip_path = package_skill(skill_dir, open_folder_after=False, skip_quality_check=True) self.assertTrue(success) @@ -89,7 +89,7 @@ class TestPackageSkill(unittest.TestCase): def test_package_nonexistent_directory(self): """Test packaging a nonexistent directory""" - success, zip_path = package_skill("/nonexistent/path", open_folder_after=False) + success, zip_path = package_skill("/nonexistent/path", open_folder_after=False, skip_quality_check=True) self.assertFalse(success) self.assertIsNone(zip_path) @@ -100,7 +100,7 @@ class TestPackageSkill(unittest.TestCase): skill_dir = Path(tmpdir) / "invalid-skill" skill_dir.mkdir() - success, zip_path = package_skill(skill_dir, open_folder_after=False) + success, zip_path = package_skill(skill_dir, open_folder_after=False, skip_quality_check=True) self.assertFalse(success) self.assertIsNone(zip_path) @@ -119,7 +119,7 @@ class TestPackageSkill(unittest.TestCase): (skill_dir / "scripts").mkdir() (skill_dir / "assets").mkdir() - success, zip_path = package_skill(skill_dir, open_folder_after=False) + success, zip_path = package_skill(skill_dir, open_folder_after=False, skip_quality_check=True) self.assertTrue(success) # Zip should be in output directory, not inside skill directory @@ -136,7 +136,7 @@ class TestPackageSkill(unittest.TestCase): (skill_dir / "scripts").mkdir() (skill_dir / "assets").mkdir() - success, zip_path = package_skill(skill_dir, open_folder_after=False) + success, zip_path = package_skill(skill_dir, open_folder_after=False, skip_quality_check=True) self.assertTrue(success) self.assertEqual(zip_path.name, "my-awesome-skill.zip") diff --git a/tests/test_quality_checker.py b/tests/test_quality_checker.py new file mode 100644 index 0000000..104d5b9 --- /dev/null +++ b/tests/test_quality_checker.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +""" +Tests for cli/quality_checker.py functionality +""" + +import unittest +import tempfile +from pathlib import Path +import os + +from skill_seekers.cli.quality_checker import SkillQualityChecker, QualityReport + + +class TestQualityChecker(unittest.TestCase): + """Test quality checker functionality""" + + def create_test_skill(self, tmpdir, skill_md_content, create_references=True): + """Helper to create a test skill directory""" + skill_dir = Path(tmpdir) / "test-skill" + skill_dir.mkdir() + + # Create SKILL.md + skill_md = skill_dir / "SKILL.md" + skill_md.write_text(skill_md_content, encoding='utf-8') + + # Create references directory + if create_references: + refs_dir = skill_dir / "references" + refs_dir.mkdir() + (refs_dir / "index.md").write_text("# Index\n\nTest reference.", encoding='utf-8') + (refs_dir / "getting_started.md").write_text("# Getting Started\n\nHow to start.", encoding='utf-8') + + return skill_dir + + def test_checker_detects_missing_skill_md(self): + """Test that checker detects missing SKILL.md""" + with tempfile.TemporaryDirectory() as tmpdir: + skill_dir = Path(tmpdir) / "test-skill" + skill_dir.mkdir() + + checker = SkillQualityChecker(skill_dir) + report = checker.check_all() + + # Should have error about missing SKILL.md + self.assertTrue(report.has_errors) + self.assertTrue(any('SKILL.md' in issue.message for issue in report.errors)) + + def test_checker_detects_missing_references(self): + """Test that checker warns about missing references""" + with tempfile.TemporaryDirectory() as tmpdir: + skill_md = """--- +name: test +--- + +# Test Skill + +This is a test. +""" + skill_dir = self.create_test_skill(tmpdir, skill_md, create_references=False) + + checker = SkillQualityChecker(skill_dir) + report = checker.check_all() + + # Should have warning about missing references + self.assertTrue(report.has_warnings) + self.assertTrue(any('references' in issue.message.lower() for issue in report.warnings)) + + def test_checker_detects_invalid_frontmatter(self): + """Test that checker detects invalid YAML frontmatter""" + with tempfile.TemporaryDirectory() as tmpdir: + skill_md = """# Test Skill + +No frontmatter here! +""" + skill_dir = self.create_test_skill(tmpdir, skill_md) + + checker = SkillQualityChecker(skill_dir) + report = checker.check_all() + + # Should have error about missing frontmatter + self.assertTrue(report.has_errors) + self.assertTrue(any('frontmatter' in issue.message.lower() for issue in report.errors)) + + def test_checker_detects_missing_name_field(self): + """Test that checker detects missing name field in frontmatter""" + with tempfile.TemporaryDirectory() as tmpdir: + skill_md = """--- +description: test +--- + +# Test Skill +""" + skill_dir = self.create_test_skill(tmpdir, skill_md) + + checker = SkillQualityChecker(skill_dir) + report = checker.check_all() + + # Should have error about missing name field + self.assertTrue(report.has_errors) + self.assertTrue(any('name' in issue.message.lower() for issue in report.errors)) + + def test_checker_detects_code_without_language(self): + """Test that checker warns about code blocks without language tags""" + with tempfile.TemporaryDirectory() as tmpdir: + skill_md = """--- +name: test +--- + +# Test Skill + +Here's some code: + +``` +print("hello") +``` +""" + skill_dir = self.create_test_skill(tmpdir, skill_md) + + checker = SkillQualityChecker(skill_dir) + report = checker.check_all() + + # Should have warning about code without language + self.assertTrue(report.has_warnings) + self.assertTrue(any('language' in issue.message.lower() for issue in report.warnings)) + + def test_checker_approves_good_skill(self): + """Test that checker gives high score to well-formed skill""" + with tempfile.TemporaryDirectory() as tmpdir: + skill_md = """--- +name: test +description: A test skill +--- + +# Test Skill + +## When to Use This Skill + +Use this when you need to test. + +## Quick Reference + +Here are some examples: + +```python +def hello(): + print("hello") +``` + +```javascript +console.log("hello"); +``` + +## Example: Basic Usage + +This shows how to use it. + +## Reference Files + +See the references directory for more: +- [Getting Started](references/getting_started.md) +- [Index](references/index.md) +""" + skill_dir = self.create_test_skill(tmpdir, skill_md) + + checker = SkillQualityChecker(skill_dir) + report = checker.check_all() + + # Should have no errors + self.assertFalse(report.has_errors) + + # Quality score should be high + self.assertGreaterEqual(report.quality_score, 80.0) + + def test_checker_detects_broken_links(self): + """Test that checker detects broken internal links""" + with tempfile.TemporaryDirectory() as tmpdir: + skill_md = """--- +name: test +--- + +# Test Skill + +See [this file](nonexistent.md) for more info. +""" + skill_dir = self.create_test_skill(tmpdir, skill_md) + + checker = SkillQualityChecker(skill_dir) + report = checker.check_all() + + # Should have warning about broken link + self.assertTrue(report.has_warnings) + self.assertTrue(any('broken link' in issue.message.lower() for issue in report.warnings)) + + def test_quality_score_calculation(self): + """Test that quality score is calculated correctly""" + with tempfile.TemporaryDirectory() as tmpdir: + report = QualityReport("test", Path(tmpdir)) + + # Perfect score to start + self.assertEqual(report.quality_score, 100.0) + + # Add an error (should deduct 15 points) + report.add_error('test', 'Test error') + self.assertEqual(report.quality_score, 85.0) + + # Add a warning (should deduct 5 points) + report.add_warning('test', 'Test warning') + self.assertEqual(report.quality_score, 80.0) + + # Add more errors + report.add_error('test', 'Another error') + report.add_error('test', 'Yet another error') + self.assertEqual(report.quality_score, 50.0) + + def test_quality_grade_calculation(self): + """Test that quality grades are assigned correctly""" + with tempfile.TemporaryDirectory() as tmpdir: + report = QualityReport("test", Path(tmpdir)) + + # Grade A (90-100) + self.assertEqual(report.quality_grade, 'A') + + # Grade B (80-89) + report.add_error('test', 'Error 1') + self.assertEqual(report.quality_grade, 'B') + + # Grade C (70-79) + report.add_warning('test', 'Warning 1') + report.add_warning('test', 'Warning 2') + self.assertEqual(report.quality_grade, 'C') + + # Grade D (60-69) + report.add_warning('test', 'Warning 3') + report.add_warning('test', 'Warning 4') + self.assertEqual(report.quality_grade, 'D') + + # Grade F (below 60) + report.add_error('test', 'Error 2') + report.add_error('test', 'Error 3') + self.assertEqual(report.quality_grade, 'F') + + def test_is_excellent_property(self): + """Test is_excellent property""" + with tempfile.TemporaryDirectory() as tmpdir: + report = QualityReport("test", Path(tmpdir)) + + # Should be excellent with no issues + self.assertTrue(report.is_excellent) + + # Adding an error should make it not excellent + report.add_error('test', 'Test error') + self.assertFalse(report.is_excellent) + + # Clean report + report2 = QualityReport("test", Path(tmpdir)) + # Adding a warning should also make it not excellent + report2.add_warning('test', 'Test warning') + self.assertFalse(report2.is_excellent) + + +class TestQualityCheckerCLI(unittest.TestCase): + """Test quality checker CLI""" + + def test_cli_help_output(self): + """Test that CLI help works""" + import subprocess + + try: + result = subprocess.run( + ['python3', '-m', 'skill_seekers.cli.quality_checker', '--help'], + capture_output=True, + text=True, + timeout=5 + ) + + # Should include usage info + output = result.stdout + result.stderr + self.assertTrue('usage:' in output.lower() or 'quality' in output.lower()) + except FileNotFoundError: + self.skipTest("Module not installed") + + def test_cli_with_nonexistent_directory(self): + """Test CLI behavior with nonexistent directory""" + import subprocess + + result = subprocess.run( + ['python3', '-m', 'skill_seekers.cli.quality_checker', '/nonexistent/path'], + capture_output=True, + text=True + ) + + # Should fail + self.assertNotEqual(result.returncode, 0) + + +if __name__ == '__main__': + unittest.main()