381 lines
14 KiB
Python
381 lines
14 KiB
Python
"""Tests for the universal project detector."""
|
|
|
|
import json
|
|
|
|
from codeflash.setup.detector import (
|
|
_detect_js_formatter,
|
|
_detect_js_module_root,
|
|
_detect_js_test_runner,
|
|
_detect_language,
|
|
_detect_python_formatter,
|
|
_detect_python_module_root,
|
|
_detect_python_test_runner,
|
|
_detect_tests_root,
|
|
_find_project_root,
|
|
detect_project,
|
|
has_existing_config,
|
|
)
|
|
|
|
|
|
class TestFindProjectRoot:
|
|
"""Tests for _find_project_root function."""
|
|
|
|
def test_finds_git_directory(self, tmp_path):
|
|
"""Should find project root by .git directory."""
|
|
(tmp_path / ".git").mkdir()
|
|
subdir = tmp_path / "src" / "deep"
|
|
subdir.mkdir(parents=True)
|
|
|
|
result = _find_project_root(subdir)
|
|
assert result == tmp_path
|
|
|
|
def test_finds_pyproject_toml(self, tmp_path):
|
|
"""Should find project root by pyproject.toml."""
|
|
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'test'")
|
|
subdir = tmp_path / "src"
|
|
subdir.mkdir()
|
|
|
|
result = _find_project_root(subdir)
|
|
assert result == tmp_path
|
|
|
|
def test_finds_package_json(self, tmp_path):
|
|
"""Should find project root by package.json."""
|
|
(tmp_path / "package.json").write_text('{"name": "test"}')
|
|
subdir = tmp_path / "lib"
|
|
subdir.mkdir()
|
|
|
|
result = _find_project_root(subdir)
|
|
assert result == tmp_path
|
|
|
|
def test_returns_none_when_no_markers(self, tmp_path):
|
|
"""Should return None when no project markers found."""
|
|
subdir = tmp_path / "orphan"
|
|
subdir.mkdir()
|
|
|
|
result = _find_project_root(subdir)
|
|
# Will walk up to filesystem root and not find anything
|
|
assert result is None or result == tmp_path
|
|
|
|
|
|
class TestDetectLanguage:
|
|
"""Tests for _detect_language function."""
|
|
|
|
def test_detects_typescript_from_tsconfig(self, tmp_path):
|
|
"""Should detect TypeScript when tsconfig.json exists."""
|
|
(tmp_path / "tsconfig.json").write_text("{}")
|
|
(tmp_path / "package.json").write_text('{"name": "test"}')
|
|
|
|
lang, confidence, detail = _detect_language(tmp_path)
|
|
assert lang == "typescript"
|
|
assert confidence == 1.0
|
|
assert "tsconfig.json" in detail
|
|
|
|
def test_detects_python_from_pyproject(self, tmp_path):
|
|
"""Should detect Python when pyproject.toml exists."""
|
|
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'test'")
|
|
|
|
lang, confidence, detail = _detect_language(tmp_path)
|
|
assert lang == "python"
|
|
assert confidence == 1.0
|
|
assert "pyproject.toml" in detail
|
|
|
|
def test_detects_python_from_setup_py(self, tmp_path):
|
|
"""Should detect Python when setup.py exists."""
|
|
(tmp_path / "setup.py").write_text("from setuptools import setup\nsetup()")
|
|
|
|
lang, confidence, detail = _detect_language(tmp_path)
|
|
assert lang == "python"
|
|
assert confidence == 1.0
|
|
assert "setup.py" in detail
|
|
|
|
def test_detects_javascript_from_package_json(self, tmp_path):
|
|
"""Should detect JavaScript when only package.json exists."""
|
|
(tmp_path / "package.json").write_text('{"name": "test"}')
|
|
|
|
lang, confidence, detail = _detect_language(tmp_path)
|
|
assert lang == "javascript"
|
|
assert confidence == 0.9
|
|
assert "package.json" in detail
|
|
|
|
def test_defaults_to_python(self, tmp_path):
|
|
"""Should default to Python when no markers found."""
|
|
lang, confidence, detail = _detect_language(tmp_path)
|
|
assert lang == "python"
|
|
assert confidence < 0.5 # Low confidence
|
|
|
|
|
|
class TestDetectModuleRoot:
|
|
"""Tests for module root detection."""
|
|
|
|
def test_python_detects_src_layout(self, tmp_path):
|
|
"""Should detect src/ layout for Python."""
|
|
src_dir = tmp_path / "src" / "mypackage"
|
|
src_dir.mkdir(parents=True)
|
|
(src_dir / "__init__.py").write_text("")
|
|
|
|
module_root, detail = _detect_python_module_root(tmp_path)
|
|
assert module_root == src_dir
|
|
assert module_root.name == "mypackage"
|
|
assert module_root.parent.name == "src"
|
|
|
|
def test_python_detects_package_at_root(self, tmp_path):
|
|
"""Should detect package at project root."""
|
|
pkg_dir = tmp_path / "mypackage"
|
|
pkg_dir.mkdir()
|
|
(pkg_dir / "__init__.py").write_text("")
|
|
|
|
module_root, detail = _detect_python_module_root(tmp_path)
|
|
assert module_root == pkg_dir
|
|
|
|
def test_python_uses_pyproject_name(self, tmp_path):
|
|
"""Should use project name from pyproject.toml."""
|
|
(tmp_path / "pyproject.toml").write_text('[project]\nname = "myapp"')
|
|
pkg_dir = tmp_path / "myapp"
|
|
pkg_dir.mkdir()
|
|
(pkg_dir / "__init__.py").write_text("")
|
|
|
|
module_root, detail = _detect_python_module_root(tmp_path)
|
|
assert module_root == pkg_dir
|
|
assert "pyproject.toml" in detail
|
|
|
|
def test_js_detects_from_exports(self, tmp_path):
|
|
"""Should detect module root from package.json exports."""
|
|
(tmp_path / "package.json").write_text(json.dumps({
|
|
"name": "test",
|
|
"exports": {".": "./src/index.js"}
|
|
}))
|
|
(tmp_path / "src").mkdir()
|
|
|
|
module_root, detail = _detect_js_module_root(tmp_path)
|
|
assert module_root == tmp_path / "src"
|
|
assert "exports" in detail
|
|
|
|
def test_js_detects_src_convention(self, tmp_path):
|
|
"""Should detect src/ directory for JS."""
|
|
(tmp_path / "package.json").write_text('{"name": "test"}')
|
|
(tmp_path / "src").mkdir()
|
|
|
|
module_root, detail = _detect_js_module_root(tmp_path)
|
|
assert module_root == tmp_path / "src"
|
|
|
|
|
|
class TestDetectTestsRoot:
|
|
"""Tests for tests root detection."""
|
|
|
|
def test_detects_tests_directory(self, tmp_path):
|
|
"""Should detect tests/ directory."""
|
|
(tmp_path / "tests").mkdir()
|
|
|
|
tests_root, detail = _detect_tests_root(tmp_path, "python")
|
|
assert tests_root == tmp_path / "tests"
|
|
|
|
def test_detects_test_directory(self, tmp_path):
|
|
"""Should detect test/ directory."""
|
|
(tmp_path / "test").mkdir()
|
|
|
|
tests_root, detail = _detect_tests_root(tmp_path, "python")
|
|
assert tests_root == tmp_path / "test"
|
|
|
|
def test_detects_dunder_tests(self, tmp_path):
|
|
"""Should detect __tests__/ directory (JS convention)."""
|
|
(tmp_path / "__tests__").mkdir()
|
|
|
|
tests_root, detail = _detect_tests_root(tmp_path, "javascript")
|
|
assert tests_root == tmp_path / "__tests__"
|
|
|
|
def test_returns_none_when_not_found(self, tmp_path):
|
|
"""Should return None when no tests directory found."""
|
|
tests_root, detail = _detect_tests_root(tmp_path, "python")
|
|
assert tests_root is None
|
|
|
|
|
|
class TestDetectTestRunner:
|
|
"""Tests for test runner detection."""
|
|
|
|
def test_python_detects_pytest_from_ini(self, tmp_path):
|
|
"""Should detect pytest from pytest.ini."""
|
|
(tmp_path / "pytest.ini").write_text("[pytest]")
|
|
|
|
runner, detail = _detect_python_test_runner(tmp_path)
|
|
assert runner == "pytest"
|
|
|
|
def test_python_detects_pytest_from_conftest(self, tmp_path):
|
|
"""Should detect pytest from conftest.py."""
|
|
(tmp_path / "conftest.py").write_text("import pytest")
|
|
|
|
runner, detail = _detect_python_test_runner(tmp_path)
|
|
assert runner == "pytest"
|
|
|
|
def test_js_detects_jest_from_deps(self, tmp_path):
|
|
"""Should detect jest from devDependencies."""
|
|
(tmp_path / "package.json").write_text(json.dumps({
|
|
"devDependencies": {"jest": "^29.0.0"}
|
|
}))
|
|
|
|
runner, detail = _detect_js_test_runner(tmp_path)
|
|
assert runner == "jest"
|
|
|
|
def test_js_detects_vitest_from_deps(self, tmp_path):
|
|
"""Should detect vitest from devDependencies (preferred over jest)."""
|
|
(tmp_path / "package.json").write_text(json.dumps({
|
|
"devDependencies": {"vitest": "^1.0.0", "jest": "^29.0.0"}
|
|
}))
|
|
|
|
runner, detail = _detect_js_test_runner(tmp_path)
|
|
assert runner == "vitest"
|
|
|
|
def test_js_detects_from_config_file(self, tmp_path):
|
|
"""Should detect test runner from config file."""
|
|
(tmp_path / "package.json").write_text('{"name": "test"}')
|
|
(tmp_path / "vitest.config.js").write_text("export default {}")
|
|
|
|
runner, detail = _detect_js_test_runner(tmp_path)
|
|
assert runner == "vitest"
|
|
|
|
|
|
class TestDetectFormatter:
|
|
"""Tests for formatter detection."""
|
|
|
|
def test_python_detects_ruff(self, tmp_path):
|
|
"""Should detect ruff from pyproject.toml."""
|
|
(tmp_path / "pyproject.toml").write_text("[tool.ruff]\nline-length = 120")
|
|
|
|
formatter, detail = _detect_python_formatter(tmp_path)
|
|
assert any("ruff" in cmd for cmd in formatter)
|
|
|
|
def test_python_detects_black(self, tmp_path):
|
|
"""Should detect black from pyproject.toml."""
|
|
(tmp_path / "pyproject.toml").write_text("[tool.black]\nline-length = 88")
|
|
|
|
formatter, detail = _detect_python_formatter(tmp_path)
|
|
assert any("black" in cmd for cmd in formatter)
|
|
|
|
def test_js_detects_prettier(self, tmp_path):
|
|
"""Should detect prettier from config file."""
|
|
(tmp_path / "package.json").write_text('{"name": "test"}')
|
|
(tmp_path / ".prettierrc").write_text("{}")
|
|
|
|
formatter, detail = _detect_js_formatter(tmp_path)
|
|
assert any("prettier" in cmd for cmd in formatter)
|
|
|
|
def test_js_detects_prettier_from_deps(self, tmp_path):
|
|
"""Should detect prettier from devDependencies."""
|
|
(tmp_path / "package.json").write_text(json.dumps({
|
|
"devDependencies": {"prettier": "^3.0.0"}
|
|
}))
|
|
|
|
formatter, detail = _detect_js_formatter(tmp_path)
|
|
assert any("prettier" in cmd for cmd in formatter)
|
|
|
|
|
|
class TestDetectProject:
|
|
"""Integration tests for detect_project function."""
|
|
|
|
def test_detects_python_project(self, tmp_path):
|
|
"""Should correctly detect a Python project."""
|
|
# Create Python project structure
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[project]\nname = "myapp"\n\n[tool.ruff]\nline-length = 120'
|
|
)
|
|
(tmp_path / "myapp").mkdir()
|
|
(tmp_path / "myapp" / "__init__.py").write_text("")
|
|
(tmp_path / "tests").mkdir()
|
|
(tmp_path / ".git").mkdir()
|
|
|
|
detected = detect_project(tmp_path)
|
|
|
|
assert detected.language == "python"
|
|
assert detected.project_root == tmp_path
|
|
assert detected.module_root == tmp_path / "myapp"
|
|
assert detected.tests_root == tmp_path / "tests"
|
|
assert detected.test_runner == "pytest"
|
|
assert any("ruff" in cmd for cmd in detected.formatter_cmds)
|
|
|
|
def test_detects_javascript_project(self, tmp_path):
|
|
"""Should correctly detect a JavaScript project."""
|
|
# Create JS project structure
|
|
(tmp_path / "package.json").write_text(json.dumps({
|
|
"name": "myapp",
|
|
"devDependencies": {"jest": "^29.0.0", "prettier": "^3.0.0"}
|
|
}))
|
|
(tmp_path / "src").mkdir()
|
|
(tmp_path / "tests").mkdir()
|
|
(tmp_path / ".git").mkdir()
|
|
|
|
detected = detect_project(tmp_path)
|
|
|
|
assert detected.language == "javascript"
|
|
assert detected.project_root == tmp_path
|
|
assert detected.module_root == tmp_path / "src"
|
|
assert detected.tests_root == tmp_path / "tests"
|
|
assert detected.test_runner == "jest"
|
|
assert any("prettier" in cmd for cmd in detected.formatter_cmds)
|
|
|
|
def test_detects_typescript_project(self, tmp_path):
|
|
"""Should correctly detect a TypeScript project."""
|
|
# Create TS project structure
|
|
(tmp_path / "package.json").write_text(json.dumps({
|
|
"name": "myapp",
|
|
"devDependencies": {"vitest": "^1.0.0", "typescript": "^5.0.0"}
|
|
}))
|
|
(tmp_path / "tsconfig.json").write_text("{}")
|
|
(tmp_path / "src").mkdir()
|
|
(tmp_path / ".git").mkdir()
|
|
|
|
detected = detect_project(tmp_path)
|
|
|
|
assert detected.language == "typescript"
|
|
assert detected.test_runner == "vitest"
|
|
|
|
def test_to_display_dict(self, tmp_path):
|
|
"""Should generate display dictionary correctly."""
|
|
(tmp_path / "pyproject.toml").write_text('[project]\nname = "test"')
|
|
(tmp_path / "test").mkdir()
|
|
(tmp_path / "test" / "__init__.py").write_text("")
|
|
(tmp_path / "tests").mkdir()
|
|
|
|
detected = detect_project(tmp_path)
|
|
display = detected.to_display_dict()
|
|
|
|
assert "Language" in display
|
|
assert "Module root" in display
|
|
assert "Test runner" in display
|
|
|
|
|
|
class TestHasExistingConfig:
|
|
"""Tests for has_existing_config function."""
|
|
|
|
def test_detects_pyproject_config(self, tmp_path):
|
|
"""Should detect config in pyproject.toml."""
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.codeflash]\nmodule-root = "src"'
|
|
)
|
|
|
|
has_config, config_type = has_existing_config(tmp_path)
|
|
assert has_config is True
|
|
assert config_type == "pyproject.toml"
|
|
|
|
def test_detects_package_json_config(self, tmp_path):
|
|
"""Should detect config in package.json."""
|
|
(tmp_path / "package.json").write_text(json.dumps({
|
|
"name": "test",
|
|
"codeflash": {"moduleRoot": "src"}
|
|
}))
|
|
|
|
has_config, config_type = has_existing_config(tmp_path)
|
|
assert has_config is True
|
|
assert config_type == "package.json"
|
|
|
|
def test_returns_false_when_no_config(self, tmp_path):
|
|
"""Should return False when no codeflash config exists."""
|
|
(tmp_path / "pyproject.toml").write_text('[project]\nname = "test"')
|
|
|
|
has_config, config_type = has_existing_config(tmp_path)
|
|
assert has_config is False
|
|
assert config_type is None
|
|
|
|
def test_returns_false_for_empty_directory(self, tmp_path):
|
|
"""Should return False for empty directory."""
|
|
has_config, config_type = has_existing_config(tmp_path)
|
|
assert has_config is False
|
|
assert config_type is None
|