"""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