From 391c410ad110360d76e3d7bbdfdcbac9068c0faf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 03:06:43 +0000 Subject: [PATCH] Add comprehensive test suite and CI workflow - Added pytest configuration to pyproject.toml. - Implemented mock project fixtures in tests/conftest.py. - Added test suites for walker, detector, explainer, and exporters. - Configured GitHub Actions workflow for automated testing. Co-authored-by: erensh27 <244986440+erensh27@users.noreply.github.com> --- .github/workflows/test.yml | 32 +++++++++ pyproject.toml | 4 ++ tests/conftest.py | 105 +++++++++++++++++++++++++++++ tests/test_detector.py | 80 +++++++++------------- tests/test_explainer.py | 72 +++++++------------- tests/test_exporters.py | 49 ++++++++++++++ tests/test_walker.py | 134 ++++++++++++++----------------------- 7 files changed, 298 insertions(+), 178 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 tests/conftest.py create mode 100644 tests/test_exporters.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0c7e54c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: Test Suite + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . + pip install pytest + + - name: Run tests + run: | + pytest --tb=short -v diff --git a/pyproject.toml b/pyproject.toml index 8a0e4aa..bdd6c47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,3 +20,7 @@ codemappr = "codemappr.cli:app" [tool.setuptools.packages.find] where = ["."] include = ["codemappr*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["."] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..7cf272c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,105 @@ +import pytest +import os + +@pytest.fixture +def mock_react_project(tmp_path): + project_dir = tmp_path / "mock_react_project" + project_dir.mkdir() + + package_json = project_dir / "package.json" + package_json.write_text('{"dependencies": {"react": "^18.2.0"}}') + + src_dir = project_dir / "src" + src_dir.mkdir() + (src_dir / "App.js").write_text("function App() { return
Hello
; }") + + public_dir = project_dir / "public" + public_dir.mkdir() + (public_dir / "index.html").write_text("") + + return project_dir + +@pytest.fixture +def mock_django_project(tmp_path): + project_dir = tmp_path / "mock_django_project" + project_dir.mkdir() + + (project_dir / "manage.py").write_text("# Django manage.py") + + app_dir = project_dir / "myproject" + app_dir.mkdir() + (app_dir / "settings.py").write_text("DEBUG = True") + (app_dir / "urls.py").write_text("urlpatterns = []") + + (project_dir / "requirements.txt").write_text("django==4.2") + + return project_dir + +@pytest.fixture +def mock_rust_project(tmp_path): + project_dir = tmp_path / "mock_rust_project" + project_dir.mkdir() + + (project_dir / "Cargo.toml").write_text('[package]\nname = "mock_rust"') + + src_dir = project_dir / "src" + src_dir.mkdir() + (src_dir / "main.rs").write_text("fn main() {}") + + return project_dir + +@pytest.fixture +def mock_python_package(tmp_path): + project_dir = tmp_path / "mock_python_package" + project_dir.mkdir() + + (project_dir / "pyproject.toml").write_text("[project]\nname = 'mock_python'") + + src_dir = project_dir / "src" + src_dir.mkdir() + (src_dir / "__init__.py").write_text("") + + tests_dir = project_dir / "tests" + tests_dir.mkdir() + (tests_dir / "__init__.py").write_text("") + + return project_dir + +@pytest.fixture +def mock_monorepo(tmp_path): + project_dir = tmp_path / "mock_monorepo" + project_dir.mkdir() + + (project_dir / "package.json").write_text('{"workspaces": ["packages/*"]}') + + packages_dir = project_dir / "packages" + packages_dir.mkdir() + + app1_dir = packages_dir / "app1" + app1_dir.mkdir() + (app1_dir / "package.json").write_text('{"name": "app1"}') + + app2_dir = packages_dir / "app2" + app2_dir.mkdir() + (app2_dir / "package.json").write_text('{"name": "app2"}') + + return project_dir + +@pytest.fixture +def mock_empty_dir(tmp_path): + project_dir = tmp_path / "mock_empty_dir" + project_dir.mkdir() + return project_dir + +@pytest.fixture +def mock_deep_nested(tmp_path): + project_dir = tmp_path / "mock_deep_nested" + project_dir.mkdir() + + current = project_dir + for i in range(1, 11): + current = current / f"level{i}" + current.mkdir() + (current / f"file{i}.txt").write_text(f"content{i}") + + return project_dir diff --git a/tests/test_detector.py b/tests/test_detector.py index 5ce4bd2..033be34 100644 --- a/tests/test_detector.py +++ b/tests/test_detector.py @@ -1,61 +1,47 @@ -import os -import shutil -import tempfile -import pytest from codemappr.walker import scan_directory from codemappr.detector import detect_project -@pytest.fixture -def temp_dir(): - d = tempfile.mkdtemp() - yield d - shutil.rmtree(d) +def test_detect_react(mock_react_project): + node = scan_directory(str(mock_react_project)) + profile = detect_project(node, str(mock_react_project)) -def test_detect_python_package(temp_dir): - # Create a dummy Python package structure - with open(os.path.join(temp_dir, "pyproject.toml"), "w") as f: - f.write("[project]\nname='test'") - os.makedirs(os.path.join(temp_dir, "src")) - with open(os.path.join(temp_dir, "src", "main.py"), "w") as f: - f.write("print('hello')") + assert profile.project_type == "React/Next.js" + assert profile.framework == "React" + assert "JavaScript" in profile.language_stack - node = scan_directory(temp_dir) - profile = detect_project(node, temp_dir) +def test_detect_django(mock_django_project): + node = scan_directory(str(mock_django_project)) + profile = detect_project(node, str(mock_django_project)) - assert profile.project_type == "Python Package/Library" + assert profile.project_type == "Python Django" + assert profile.framework == "Django" assert "Python" in profile.language_stack - assert profile.confidence in ["medium", "high"] -def test_detect_react_project(temp_dir): - # Create a dummy React structure - with open(os.path.join(temp_dir, "package.json"), "w") as f: - f.write('{"dependencies": {"react": "18.0.0"}}') - os.makedirs(os.path.join(temp_dir, "src")) - with open(os.path.join(temp_dir, "src", "App.js"), "w") as f: - f.write("function App() {}") +def test_detect_rust(mock_rust_project): + node = scan_directory(str(mock_rust_project)) + profile = detect_project(node, str(mock_rust_project)) - node = scan_directory(temp_dir) - profile = detect_project(node, temp_dir) + assert profile.project_type == "Rust" + assert "Rust" in profile.language_stack - assert profile.project_type == "React/Next.js" - assert profile.framework == "React" - assert "JavaScript" in profile.language_stack - assert profile.confidence == "high" +def test_detect_monorepo(mock_monorepo): + node = scan_directory(str(mock_monorepo)) + profile = detect_project(node, str(mock_monorepo)) -def test_detect_django_project(temp_dir): - # Create dummy Django structure - with open(os.path.join(temp_dir, "manage.py"), "w") as f: - f.write("# django manage.py") - os.makedirs(os.path.join(temp_dir, "myproject")) - with open(os.path.join(temp_dir, "myproject", "settings.py"), "w") as f: - f.write("DEBUG=True") - with open(os.path.join(temp_dir, "myproject", "urls.py"), "w") as f: - f.write("urlpatterns=[]") + assert profile.project_type == "Monorepo" - node = scan_directory(temp_dir) - profile = detect_project(node, temp_dir) +def test_detect_unknown(mock_empty_dir): + node = scan_directory(str(mock_empty_dir)) + profile = detect_project(node, str(mock_empty_dir)) - assert profile.project_type == "Python Django" - assert profile.framework == "Django" + assert profile.project_type == "Unknown" + +def test_language_stack(mock_react_project): + # Add a python file to check multi-language + (mock_react_project / "script.py").write_text("print(1)") + + node = scan_directory(str(mock_react_project)) + profile = detect_project(node, str(mock_react_project)) + + assert "JavaScript" in profile.language_stack assert "Python" in profile.language_stack - assert profile.confidence == "high" diff --git a/tests/test_explainer.py b/tests/test_explainer.py index 1a56b94..f1bd421 100644 --- a/tests/test_explainer.py +++ b/tests/test_explainer.py @@ -1,58 +1,34 @@ -from codemappr.models import DirectoryNode, ProjectProfile +from codemappr.walker import scan_directory +from codemappr.detector import detect_project from codemappr.explainer import explain_project -def test_explain_project_basic(): - profile = ProjectProfile( - project_type="Python Django", - language_stack=["Python"], - description="A Python Django project.", - root_path="/tmp/test", - total_files=10, - total_dirs=2, - total_size_bytes=1024, - confidence="high", - framework="Django" - ) - - node = DirectoryNode( - name="test", - path=".", - is_dir=True, - size=1024, - extension="", - depth=0, - children=[ - DirectoryNode("manage.py", "./manage.py", False, 100, ".py", 1), - DirectoryNode("src", "./src", True, 924, "", 1), - DirectoryNode("README.md", "./README.md", False, 50, ".md", 1) - ] - ) +def test_summary_not_empty(mock_react_project): + node = scan_directory(str(mock_react_project)) + profile = detect_project(node, str(mock_react_project)) + explanation = explain_project(profile, node) + + assert explanation["summary"] + assert isinstance(explanation["summary"], str) +def test_folder_purposes(mock_python_package): + node = scan_directory(str(mock_python_package)) + profile = detect_project(node, str(mock_python_package)) explanation = explain_project(profile, node) - assert "Python Django" in explanation["summary"] - assert "Django" in explanation["summary"] + # Check if 'src' and 'tests' are recognized assert explanation["structure"]["src"] == "Main source code" + assert explanation["structure"]["tests"] == "Test suite" + +def test_entry_points_detected(mock_django_project): + node = scan_directory(str(mock_django_project)) + profile = detect_project(node, str(mock_django_project)) + explanation = explain_project(profile, node) + assert "manage.py" in explanation["entry_points"] - assert "README.md" in explanation["notable_files"] - -def test_explain_project_empty(): - profile = ProjectProfile( - project_type="Unknown", - language_stack=[], - description="A Unknown project.", - root_path="/tmp/test", - total_files=0, - total_dirs=1, - total_size_bytes=0, - confidence="low" - ) - - node = DirectoryNode("test", ".", True, 0, "", 0) +def test_notable_files_detected(mock_react_project): + node = scan_directory(str(mock_react_project)) + profile = detect_project(node, str(mock_react_project)) explanation = explain_project(profile, node) - assert "Unknown" in explanation["summary"] - assert explanation["structure"] == {} - assert explanation["entry_points"] == [] - assert explanation["notable_files"] == [] + assert "package.json" in explanation["notable_files"] diff --git a/tests/test_exporters.py b/tests/test_exporters.py new file mode 100644 index 0000000..4497dd9 --- /dev/null +++ b/tests/test_exporters.py @@ -0,0 +1,49 @@ +import os +from codemappr.walker import scan_directory +from codemappr.detector import detect_project +from codemappr.explainer import explain_project +from codemappr.exporter import export_markdown, export_html + +def test_markdown_export(mock_react_project, tmp_path): + node = scan_directory(str(mock_react_project)) + profile = detect_project(node, str(mock_react_project)) + explanation = explain_project(profile, node) + + output_path = tmp_path / "REPORT.md" + export_markdown(node, profile, explanation, str(output_path)) + + assert output_path.exists() + content = output_path.read_text() + assert "# CodeMappr Report" in content + assert "## Project Profile" in content + assert "## Directory Tree" in content + assert "## Architecture Summary" in content + +def test_html_export(mock_react_project, tmp_path): + node = scan_directory(str(mock_react_project)) + profile = detect_project(node, str(mock_react_project)) + explanation = explain_project(profile, node) + + output_path = tmp_path / "REPORT.html" + export_html(node, profile, explanation, str(output_path)) + + assert output_path.exists() + content = output_path.read_text() + assert "