Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ codemappr = "codemappr.cli:app"
[tool.setuptools.packages.find]
where = ["."]
include = ["codemappr*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
105 changes: 105 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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 <div>Hello</div>; }")

public_dir = project_dir / "public"
public_dir.mkdir()
(public_dir / "index.html").write_text("<!DOCTYPE html><html></html>")

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
80 changes: 33 additions & 47 deletions tests/test_detector.py
Original file line number Diff line number Diff line change
@@ -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"
72 changes: 24 additions & 48 deletions tests/test_explainer.py
Original file line number Diff line number Diff line change
@@ -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"]
49 changes: 49 additions & 0 deletions tests/test_exporters.py
Original file line number Diff line number Diff line change
@@ -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 "<html" in content
assert "CodeMappr Report" in content
# Verify no external CDN links (basic check)
assert "https://cdn" not in content
assert "http://cdn" not in content

def test_output_path_respected(mock_python_package, tmp_path):
node = scan_directory(str(mock_python_package))
profile = detect_project(node, str(mock_python_package))
explanation = explain_project(profile, node)

custom_dir = tmp_path / "custom_reports"
custom_dir.mkdir()
output_path = custom_dir / "my_report.md"

export_markdown(node, profile, explanation, str(output_path))

assert output_path.exists()
Loading
Loading