Public Access
Post-merge / detect-type (push) Successful in 6s
Post-merge / validate-commit-msg (push) Successful in 6s
Post-merge / configure-repo (push) Successful in 9s
Post-merge / release (push) Successful in 1m0s
Post-merge / vikunja (push) Successful in 14s
Post-merge / sync-wiki (push) Successful in 59s
Post-merge / badges (push) Successful in 1m12s
139 lines
5.8 KiB
Python
139 lines
5.8 KiB
Python
"""Unit tests for devx.ci.doc_coverage."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from devx.ci.doc_coverage import (
|
|
check_command_documented,
|
|
check_module_documented,
|
|
extract_cli_commands,
|
|
main,
|
|
)
|
|
|
|
|
|
class TestExtractCliCommands:
|
|
def test_extracts_commands(self) -> None:
|
|
commands = extract_cli_commands()
|
|
# devx CLI has commands under ci, tools, and molecule groups
|
|
assert "auto-merge" in commands
|
|
assert "release" in commands
|
|
assert "publish" in commands
|
|
assert "setup" in commands
|
|
assert "install-tools" in commands
|
|
|
|
def test_returns_list(self) -> None:
|
|
commands = extract_cli_commands()
|
|
assert isinstance(commands, list)
|
|
assert len(commands) > 0
|
|
|
|
def test_no_cli_file(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Returns empty list when CLI file doesn't exist."""
|
|
from devx.ci import doc_coverage
|
|
|
|
monkeypatch.setattr(doc_coverage, "CLI_FILE", Path("/nonexistent/cli.py"))
|
|
commands = extract_cli_commands()
|
|
assert commands == []
|
|
|
|
def test_def_fallback_no_explicit_name(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""When a command decorator has no explicit name, falls back to the def name."""
|
|
from devx.ci import doc_coverage
|
|
|
|
fake_cli = tmp_path / "cli.py"
|
|
fake_cli.write_text("@click.group()\ndef cli():\n pass\n@cli.command()\ndef my_command():\n pass\n")
|
|
monkeypatch.setattr(doc_coverage, "CLI_FILE", fake_cli)
|
|
commands = extract_cli_commands()
|
|
assert "my_command" in commands
|
|
|
|
def test_command_decorator_no_def_fallback(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""When a command decorator has no name and no following def, it is skipped."""
|
|
from devx.ci import doc_coverage
|
|
|
|
fake_cli = tmp_path / "cli.py"
|
|
# The last @cli.command() has no explicit name and no def statement after it
|
|
fake_cli.write_text(
|
|
"@click.group()\ndef cli():\n pass\n@cli.command()\ndef real_cmd():\n pass\n@cli.command()\npass\n"
|
|
)
|
|
monkeypatch.setattr(doc_coverage, "CLI_FILE", fake_cli)
|
|
commands = extract_cli_commands()
|
|
# real_cmd should be found via def fallback; the bare @cli.command() is skipped
|
|
assert "real_cmd" in commands
|
|
assert "pass" not in commands
|
|
|
|
|
|
class TestCheckCommandDocumented:
|
|
def test_finds_command_in_heading(self) -> None:
|
|
content = "## auto-merge\n\nAuto-merge PR."
|
|
assert check_command_documented("auto-merge", content) is True
|
|
|
|
def test_finds_command_in_code_block(self) -> None:
|
|
content = "```bash\ndevx ci release --dry-run\n```"
|
|
assert check_command_documented("release", content) is True
|
|
|
|
def test_finds_command_with_devx_prefix(self) -> None:
|
|
content = "Use `devx tools setup` to install."
|
|
assert check_command_documented("setup", content) is True
|
|
|
|
def test_missing_command(self) -> None:
|
|
content = "## Other stuff\n\nNo commands here."
|
|
assert check_command_documented("release", content) is False
|
|
|
|
|
|
class TestCheckModuleDocumented:
|
|
def test_finds_module(self) -> None:
|
|
content = "The cli.py module handles..."
|
|
assert check_module_documented("cli.py", content) is True
|
|
|
|
def test_missing_module(self) -> None:
|
|
content = "No modules mentioned."
|
|
assert check_module_documented("cli.py", content) is False
|
|
|
|
|
|
class TestMain:
|
|
def test_all_present(self, tmp_path: Path) -> None:
|
|
"""When all docs exist and cover all commands/modules, exit 0."""
|
|
docs = tmp_path / "docs"
|
|
(docs / "user").mkdir(parents=True)
|
|
(docs / "tech").mkdir(parents=True)
|
|
# Get actual commands from the CLI
|
|
commands = extract_cli_commands()
|
|
# Write cli-commands.md with all commands
|
|
cli_content = "\n".join(f"## {cmd}" for cmd in commands)
|
|
(docs / "user" / "cli-commands.md").write_text(cli_content)
|
|
# Write architecture.md with all modules
|
|
from devx.ci.doc_coverage import REQUIRED_MODULES, REQUIRED_SCRIPTS
|
|
|
|
(docs / "tech" / "architecture.md").write_text(" ".join(REQUIRED_MODULES))
|
|
# Write ci-cd-workflow.md with all scripts
|
|
(docs / "tech" / "ci-cd-workflow.md").write_text(" ".join(REQUIRED_SCRIPTS))
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--docs-dir", str(docs)])
|
|
assert result.exit_code == 0
|
|
assert "100%" in result.output
|
|
|
|
def test_missing_docs_fail(self, tmp_path: Path) -> None:
|
|
"""When docs are missing and --fail-on-missing is set, exit 1."""
|
|
docs = tmp_path / "docs"
|
|
(docs / "user").mkdir(parents=True)
|
|
(docs / "tech").mkdir(parents=True)
|
|
(docs / "user" / "cli-commands.md").write_text("No commands here.")
|
|
(docs / "tech" / "architecture.md").write_text("No modules here.")
|
|
(docs / "tech" / "ci-cd-workflow.md").write_text("No scripts here.")
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--docs-dir", str(docs), "--fail-on-missing"])
|
|
assert result.exit_code == 1
|
|
|
|
def test_missing_docs_warn_only(self, tmp_path: Path) -> None:
|
|
"""Without --fail-on-missing, missing docs only warn (exit 0)."""
|
|
docs = tmp_path / "docs"
|
|
(docs / "user").mkdir(parents=True)
|
|
(docs / "tech").mkdir(parents=True)
|
|
(docs / "user" / "cli-commands.md").write_text("No commands here.")
|
|
(docs / "tech" / "architecture.md").write_text("No modules here.")
|
|
(docs / "tech" / "ci-cd-workflow.md").write_text("No scripts here.")
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--docs-dir", str(docs)])
|
|
assert result.exit_code == 0
|
|
assert "MISSING" in result.output
|