diff --git a/tests/test_knowledge_gap_identifier.py b/tests/test_knowledge_gap_identifier.py new file mode 100644 index 0000000..d26ca6d --- /dev/null +++ b/tests/test_knowledge_gap_identifier.py @@ -0,0 +1,141 @@ +"""Tests for knowledge_gap_identifier module.""" + +import sys +import os +import tempfile +import shutil +from pathlib import Path + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts')) + +from knowledge_gap_identifier import KnowledgeGapIdentifier, GapType, GapSeverity + + +def _make_repo(tmpdir, structure): + """Create a test repo from a dict of {path: content}.""" + for rel_path, content in structure.items(): + p = Path(tmpdir) / rel_path + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(content) + + +def test_undocumented_symbol(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/calculator.py": "def add(a, b):\n return a + b\n", + "README.md": "# Calculator\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + undocumented = [g for g in report.gaps if g.gap_type == GapType.UNDOCUMENTED] + assert any(g.name == "add" for g in undocumented), "add should be undocumented" + + +def test_documented_symbol_no_gap(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/calculator.py": "def add(a, b):\n return a + b\n", + "README.md": "# Calculator\nUse `add()` to add numbers.\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + undocumented = [g for g in report.gaps + if g.gap_type == GapType.UNDOCUMENTED and g.name == "add"] + assert len(undocumented) == 0, "add is documented, should not be flagged" + + +def test_untested_module(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/calculator.py": "def add(a, b):\n return a + b\n", + "src/helper.py": "def format(x):\n return str(x)\n", + "tests/test_calculator.py": "from src.calculator import add\nassert add(1,2) == 3\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + untested = [g for g in report.gaps if g.gap_type == GapType.UNTESTED] + assert any("helper" in g.name for g in untested), "helper should be untested" + + +def test_tested_module_no_gap(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/calculator.py": "def add(a, b):\n return a + b\n", + "tests/test_calculator.py": "def test_add():\n assert True\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + untested = [g for g in report.gaps + if g.gap_type == GapType.UNTESTED and "calculator" in g.name] + assert len(untested) == 0, "calculator has tests, should not be flagged" + + +def test_missing_implementation(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/app.py": "def run():\n pass\n", + "docs/api.md": "# API\nUse `NonExistentClass` to do things.\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + missing = [g for g in report.gaps if g.gap_type == GapType.MISSING_IMPLEMENTATION] + assert any(g.name == "NonExistentClass" for g in missing) + + +def test_private_symbols_skipped(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/app.py": "def _internal():\n pass\ndef public():\n pass\n", + "README.md": "# App\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + undocumented_names = [g.name for g in report.gaps if g.gap_type == GapType.UNDOCUMENTED] + assert "_internal" not in undocumented_names, "Private symbols should be skipped" + assert "public" in undocumented_names + + +def test_empty_repo(): + with tempfile.TemporaryDirectory() as tmpdir: + report = KnowledgeGapIdentifier().analyze(tmpdir) + assert len(report.gaps) == 0 + + +def test_invalid_path(): + report = KnowledgeGapIdentifier().analyze("/nonexistent/path/xyz") + assert len(report.gaps) == 1 + assert report.gaps[0].severity == GapSeverity.ERROR + + +def test_report_summary(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/app.py": "class MyService:\n def handle(self):\n pass\n", + "README.md": "# App\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + summary = report.summary() + assert "UNDOCUMENTED" in summary + assert "MyService" in summary + + +def test_report_to_dict(): + with tempfile.TemporaryDirectory() as tmpdir: + _make_repo(tmpdir, { + "src/app.py": "def hello():\n pass\n", + "README.md": "# App\n", + }) + report = KnowledgeGapIdentifier().analyze(tmpdir) + d = report.to_dict() + assert "total_gaps" in d + assert "gaps" in d + assert isinstance(d["gaps"], list) + assert d["total_gaps"] > 0 + + +if __name__ == "__main__": + test_undocumented_symbol() + test_documented_symbol_no_gap() + test_untested_module() + test_tested_module_no_gap() + test_missing_implementation() + test_private_symbols_skipped() + test_empty_repo() + test_invalid_path() + test_report_summary() + test_report_to_dict() + print("All 10 tests passed.")