Public Access
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
209 lines
5.3 KiB
Python
209 lines
5.3 KiB
Python
"""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
|