"""Unit tests for devx/tools/generate_badges.py.""" from pathlib import Path from unittest.mock import MagicMock, patch from click.testing import CliRunner from devx.tools.generate_badges import ( COLOR_HEX, cli, collect_coverage_and_tests, collect_doc_coverage, collect_quality, coverage_color, detect_coverage_target, detect_package_name, detect_testpaths, doc_coverage_color, extract_coverage, extract_doc_coverage, extract_test_count, generate_badges, make_badge, read_version, render_svg, resolve_repo_root, run_command, ) class TestResolveRepoRoot: def test_uses_github_workspace_when_set(self, tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def] monkeypatch.setenv("GITHUB_WORKSPACE", str(tmp_path)) assert resolve_repo_root() == tmp_path def test_falls_back_to_cwd_when_no_workspace(self, tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def] monkeypatch.delenv("GITHUB_WORKSPACE", raising=False) monkeypatch.chdir(tmp_path) assert resolve_repo_root() == tmp_path def test_falls_back_to_cwd_when_workspace_invalid(self, monkeypatch) -> None: # type: ignore[no-untyped-def] monkeypatch.setenv("GITHUB_WORKSPACE", "/nonexistent/path") result = resolve_repo_root() assert result == Path.cwd() class TestDetectPackageName: def test_detects_package_with_init(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] src = tmp_path / "src" pkg = src / "mypkg" pkg.mkdir(parents=True) (pkg / "__init__.py").write_text('__version__ = "1.0.0"\n') assert detect_package_name(tmp_path) == "mypkg" def test_returns_none_when_no_src(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] assert detect_package_name(tmp_path) is None def test_returns_none_when_no_init(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] src = tmp_path / "src" pkg = src / "mypkg" pkg.mkdir(parents=True) # No __init__.py assert detect_package_name(tmp_path) is None def test_picks_first_package_alphabetically(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] src = tmp_path / "src" for name in ["zpkg", "apkg"]: d = src / name d.mkdir(parents=True) (d / "__init__.py").write_text("") assert detect_package_name(tmp_path) == "apkg" def test_skips_non_dir_entries(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] src = tmp_path / "src" src.mkdir(parents=True) (src / "README.md").write_text("not a package") pkg = src / "mypkg" pkg.mkdir() (pkg / "__init__.py").write_text("") assert detect_package_name(tmp_path) == "mypkg" class TestDetectCoverageTarget: def test_parses_from_pyproject(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] (tmp_path / "pyproject.toml").write_text( '[tool.pytest.ini_options]\naddopts = "--cov=src/devx --cov-report=term-missing"\n' ) assert detect_coverage_target(tmp_path) == "src/devx" def test_falls_back_to_src_package(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] src = tmp_path / "src" pkg = src / "mypkg" pkg.mkdir(parents=True) (pkg / "__init__.py").write_text('__version__ = "1.0"\n') assert detect_coverage_target(tmp_path) == "src/mypkg" def test_returns_none_when_no_package(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] assert detect_coverage_target(tmp_path) is None def test_pyproject_without_cov_falls_back_to_package(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] """When pyproject exists but has no --cov=, falls back to package name.""" src = tmp_path / "src" pkg = src / "mypkg" pkg.mkdir(parents=True) (pkg / "__init__.py").write_text('__version__ = "1.0"\n') (tmp_path / "pyproject.toml").write_text('[tool.pytest.ini_options]\naddopts = "-ra"\n') assert detect_coverage_target(tmp_path) == "src/mypkg" class TestDetectTestpaths: def test_parses_from_pyproject(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] (tmp_path / "scripts" / "tests").mkdir(parents=True) (tmp_path / "tests" / "unit").mkdir(parents=True) (tmp_path / "pyproject.toml").write_text( '[tool.pytest.ini_options]\ntestpaths = ["scripts/tests", "tests/unit"]\n' ) assert detect_testpaths(tmp_path) == ["scripts/tests", "tests/unit"] def test_filters_nonexistent_paths(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] (tmp_path / "tests").mkdir() (tmp_path / "pyproject.toml").write_text('[tool.pytest.ini_options]\ntestpaths = ["tests", "nonexistent"]\n') assert detect_testpaths(tmp_path) == ["tests"] def test_all_paths_nonexistent_falls_back_to_tests_dir(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] """When all testpaths are non-existent, falls back to tests/ directory.""" (tmp_path / "tests").mkdir() (tmp_path / "pyproject.toml").write_text( '[tool.pytest.ini_options]\ntestpaths = ["nonexistent1", "nonexistent2"]\n' ) assert detect_testpaths(tmp_path) == ["tests"] def test_falls_back_to_tests_dir(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] (tmp_path / "tests").mkdir() assert detect_testpaths(tmp_path) == ["tests"] def test_returns_empty_when_no_tests_dir(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] assert detect_testpaths(tmp_path) == [] def test_returns_empty_when_pyproject_has_no_testpaths(self, tmp_path: Path) -> None: # type: ignore[no-untyped-def] (tmp_path / "pyproject.toml").write_text("[tool.pytest.ini_options]\naddopts = '-ra'\n") assert detect_testpaths(tmp_path) == [] class TestRunCommand: @patch("devx.tools.generate_badges.subprocess.run") def test_returns_returncode_stdout_stderr(self, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock(returncode=0, stdout="out", stderr="err") rc, out, err = run_command(["echo", "hello"]) assert rc == 0 assert out == "out" assert err == "err" mock_run.assert_called_once() class TestMakeBadge: def test_creates_valid_badge_dict(self) -> None: badge = make_badge("coverage", "100%", "brightgreen") assert badge == { "schemaVersion": 1, "label": "coverage", "message": "100%", "color": "brightgreen", } class TestRenderSvg: def test_generates_valid_svg(self) -> None: svg = render_svg("coverage", "100%", "brightgreen") assert svg.startswith("\n") assert "coverage" in svg assert "100%" in svg assert COLOR_HEX["brightgreen"] in svg def test_uses_color_hex_for_known_colors(self) -> None: svg = render_svg("tests", "573 passing", "brightgreen") assert "#4c1" in svg def test_uses_hex_directly_for_unknown_hex_color(self) -> None: svg = render_svg("label", "msg", "#abc123") assert "#abc123" in svg def test_uses_lightgrey_for_unknown_named_color(self) -> None: svg = render_svg("label", "msg", "nonexistent") assert "#9f9f9f" in svg def test_escapes_xml_special_chars(self) -> None: svg = render_svg("label", "