Public Access
DEVX-63: feat: extract generic tools into devx, expand devx.mak, remove personal references
Post-merge / detect-type (push) Successful in 37s
Post-merge / validate-commit-msg (push) Successful in 42s
Post-merge / sync-wiki (push) Successful in 52s
Post-merge / vikunja (push) Successful in 49s
Post-merge / release (push) Successful in 59s
Post-merge / badges (push) Successful in 1m0s
Post-merge / configure-repo (push) Successful in 49s
Post-merge / detect-type (push) Successful in 37s
Post-merge / validate-commit-msg (push) Successful in 42s
Post-merge / sync-wiki (push) Successful in 52s
Post-merge / vikunja (push) Successful in 49s
Post-merge / release (push) Successful in 59s
Post-merge / badges (push) Successful in 1m0s
Post-merge / configure-repo (push) Successful in 49s
This commit was merged in pull request #103.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
"""Unit tests for devx.tools.check_pyproject_deps."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from devx.tools.check_pyproject_deps import check_deps, cli
|
||||
|
||||
|
||||
class TestCheckDeps:
|
||||
def test_no_issues_when_all_documented(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
# HTTP client
|
||||
"requests>=2.0"
|
||||
# CLI framework
|
||||
"click>=8.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert issues == []
|
||||
|
||||
def test_finds_undocumented_dependency(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
# HTTP client
|
||||
"requests>=2.0"
|
||||
"click>=8.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert len(issues) == 1
|
||||
assert "click" in issues[0]
|
||||
|
||||
def test_finds_multiple_undocumented(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
"requests>=2.0"
|
||||
"click>=8.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert len(issues) == 2
|
||||
|
||||
def test_handles_optional_dependencies(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.optional-dependencies]
|
||||
ci = [
|
||||
# Test runner
|
||||
"pytest>=8",
|
||||
"pytest-cov>=4",
|
||||
]
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert len(issues) == 1
|
||||
assert "pytest-cov" in issues[0]
|
||||
|
||||
def test_returns_file_not_found_for_missing_file(self, tmp_path: Path) -> None:
|
||||
issues = check_deps(tmp_path / "nonexistent.toml")
|
||||
assert len(issues) == 1
|
||||
assert "not found" in issues[0]
|
||||
|
||||
def test_empty_deps_section_no_issues(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert issues == []
|
||||
|
||||
def test_skips_non_deps_sections(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.dependencies]
|
||||
# HTTP
|
||||
"requests>=2.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert issues == []
|
||||
|
||||
def test_handles_dash_prefixed_deps(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
# HTTP client
|
||||
-requests>=2.0
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert issues == []
|
||||
|
||||
def test_empty_lines_in_deps_section(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
|
||||
# HTTP client
|
||||
"requests>=2.0"
|
||||
|
||||
# CLI
|
||||
"click>=8.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
assert issues == []
|
||||
|
||||
def test_non_dep_non_comment_line_resets_prev(self, tmp_path: Path) -> None:
|
||||
# A line that's not a comment, not a dep, not empty — resets prev_was_comment
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
# Comment
|
||||
ci = [
|
||||
"requests>=2.0",
|
||||
]
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
# "requests" is preceded by a comment, but the `ci = [` line resets prev_was_comment
|
||||
# Actually `ci = [` doesn't start with - or ", so it hits the else branch
|
||||
assert len(issues) == 1
|
||||
|
||||
def test_section_transition_exits_deps(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
# HTTP
|
||||
"requests>=2.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
# Test runner
|
||||
"pytest>=8"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
# Both deps are documented
|
||||
assert issues == []
|
||||
|
||||
def test_deps_after_other_section_not_checked(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project]
|
||||
name = "test"
|
||||
|
||||
[project.dependencies]
|
||||
# Documented
|
||||
"requests>=2.0"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
"undocumented-dep>=1.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
issues = check_deps(f)
|
||||
# The "undocumented-dep" is in [tool.ruff], not a deps section
|
||||
assert issues == []
|
||||
|
||||
|
||||
class TestCli:
|
||||
def test_passes_when_all_documented(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
# HTTP client
|
||||
"requests>=2.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
runner = CliRunner()
|
||||
with __import__("contextlib").chdir(tmp_path):
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 0
|
||||
assert "Passed" in result.output
|
||||
|
||||
def test_fails_when_undocumented(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
"requests>=2.0"
|
||||
"""
|
||||
f = tmp_path / "pyproject.toml"
|
||||
f.write_text(content)
|
||||
runner = CliRunner()
|
||||
with __import__("contextlib").chdir(tmp_path):
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code != 0
|
||||
assert "FAILED" in result.output
|
||||
|
||||
def test_custom_file_option(self, tmp_path: Path) -> None:
|
||||
content = """\
|
||||
[project.dependencies]
|
||||
# Documented
|
||||
"requests>=2.0"
|
||||
"""
|
||||
f = tmp_path / "custom.toml"
|
||||
f.write_text(content)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--file", str(f)])
|
||||
assert result.exit_code == 0
|
||||
Reference in New Issue
Block a user