Files
pkgmgr/tests/e2e/test_changelog_commands.py

126 lines
4.2 KiB
Python
Raw Normal View History

from __future__ import annotations
import os
import runpy
import sys
import unittest
2025-12-09 15:02:04 +01:00
from test_version_commands import (
PROJECT_ROOT,
_load_pkgmgr_repo_dir,
)
class TestIntegrationChangelogCommands(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
"""
Versuche, das pkgmgr-Repository-Verzeichnis aus der Config zu laden.
Wenn es im aktuellen Test-Container nicht existiert, merken wir uns
None und überspringen repo-spezifische Tests später sauber.
"""
try:
repo_dir = _load_pkgmgr_repo_dir()
fix: catch concrete exceptions instead of bare Exception Replace 46 blind `except Exception` handlers with the exception types the guarded code can actually raise, so an unforeseen failure surfaces instead of being silently swallowed. The types were derived per site from the try block: file reads get (OSError, UnicodeDecodeError), TOML and YAML parsing add their decode errors, int() guards get ValueError, imports get ImportError, subprocess calls get (OSError, subprocess.SubprocessError), git queries get GitRunError, and importlib.metadata lookups get PackageNotFoundError. Three handlers stay blind, now with a stated reason: the per-repository loops in install and update are batch boundaries where one failing repository must never abort the run. Three imports come along because the narrowing needs them: GitRunError in the changelog and version commands, and http.client in token validation -- urllib raises HTTPException, which is not an OSError subclass and would otherwise have escaped. Add tests/integration/test_error_path_degradation.py, which drives 44 of the 46 handlers into their except branch and asserts the documented degradation. Faults are injected for real rather than mocked wherever possible: undecodable bytes, malformed YAML, digit strings past sys.get_int_max_str_digits(), a real HTTPError with an unreadable body. That approach paid for itself immediately -- tomllib.load() decodes internally and raises UnicodeDecodeError, which was missing from the pyproject handlers and would have turned a malformed pyproject.toml into a traceback. The two handlers left uncovered are the branch-close path in _release_impl, reachable only through a full release, and the jinja2 import guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 18:37:14 +02:00
except (OSError, RuntimeError, ValueError):
repo_dir = None
if repo_dir is not None and not os.path.isdir(repo_dir):
repo_dir = None
cls.pkgmgr_repo_dir = repo_dir
def _run_pkgmgr_changelog(
self,
extra_args: list[str] | None = None,
cwd: str | None = None,
) -> None:
"""
Helper that executes the pkgmgr CLI with the 'changelog' command
via runpy, similar to the existing version integration tests.
"""
if extra_args is None:
extra_args = []
cmd_repr = "pkgmgr changelog " + " ".join(extra_args)
original_argv = list(sys.argv)
original_cwd = os.getcwd()
try:
if cwd is not None and os.path.isdir(cwd):
os.chdir(cwd)
# Simulate CLI invocation: pkgmgr changelog <args...>
sys.argv = ["pkgmgr", "changelog"] + list(extra_args)
try:
runpy.run_module("pkgmgr", run_name="__main__")
except SystemExit as exc:
code = exc.code if isinstance(exc.code, int) else str(exc.code)
if code != 0:
print()
print(f"[TEST] Command : {cmd_repr}")
print(f"[TEST] Working directory: {os.getcwd()}")
print(f"[TEST] Exit code : {code}")
raise AssertionError(
f"{cmd_repr!r} failed with exit code {code}. "
"Scroll up to inspect the pkgmgr output before failure."
) from exc
finally:
os.chdir(original_cwd)
sys.argv = original_argv
def test_changelog_default_range_current_repo(self) -> None:
"""
Run 'pkgmgr changelog' inside the pkgmgr repo, using the default range
(last two SemVer tags or fallback to full history).
Wird übersprungen, wenn das pkgmgr-Repo in dieser Umgebung
nicht lokal vorhanden ist.
"""
if self.pkgmgr_repo_dir is None:
self.skipTest(
"pkgmgr repo directory not available in this environment; "
"skipping repo-local changelog test."
)
self._run_pkgmgr_changelog(extra_args=[], cwd=self.pkgmgr_repo_dir)
def test_changelog_explicit_range_head_history(self) -> None:
"""
Run 'pkgmgr changelog HEAD~5..HEAD' inside the pkgmgr repo.
Selbst wenn HEAD~5 nicht existiert, sollte der Befehl den
refactor(git): introduce GitRunError hierarchy, surface non-repo errors, and improve verification queries * Replace legacy GitError usage with a clearer exception hierarchy: * GitBaseError as the common root for all git-related failures * GitRunError for subprocess execution failures * GitQueryError for read-only query failures * GitCommandError for state-changing command failures * GitNotRepositoryError to explicitly signal “not a git repository” situations * Update git runner to detect “not a git repository” stderr and raise GitNotRepositoryError with rich context (cwd, command, stderr) * Refactor repository verification to use dedicated query helpers instead of ad-hoc subprocess calls: * get_remote_head_commit (ls-remote) for pull mode * get_head_commit for local mode * get_latest_signing_key (%GK) for signature verification * Add strict vs best-effort behavior in verify_repository: * Best-effort collection for reporting (does not block when no verification config exists) * Strict retrieval and explicit error messages when verification is configured * Clear failure cases when commit/signing key cannot be determined * Add new unit tests covering: * get_latest_signing_key output stripping and error wrapping * get_remote_head_commit parsing, empty output, and error wrapping * verify_repository success/failure scenarios and “do not swallow GitNotRepositoryError” * Adjust imports and exception handling across actions/commands/queries to align with GitRunError-based handling while keeping GitNotRepositoryError uncaught for debugging clarity https://chatgpt.com/share/6943173c-508c-800f-8879-af75d131c79b
2025-12-17 21:48:03 +01:00
GitBaseError intern behandeln und mit Exit-Code 0 beenden
(es wird dann eine [ERROR]-Zeile gedruckt).
Wird übersprungen, wenn das pkgmgr-Repo nicht lokal vorhanden ist.
"""
if self.pkgmgr_repo_dir is None:
self.skipTest(
"pkgmgr repo directory not available in this environment; "
"skipping repo-local changelog range test."
)
self._run_pkgmgr_changelog(
extra_args=["HEAD~5..HEAD"],
cwd=self.pkgmgr_repo_dir,
)
def test_changelog_all_repositories_default(self) -> None:
"""
Run 'pkgmgr changelog --all' from the project root to ensure
that repository selection + changelog pipeline work in the
multi-repo scenario.
Dieser Test ist robust, selbst wenn einige Repos aus der Config
physisch nicht existieren: handle_changelog überspringt sie
mit einer INFO-Meldung.
"""
self._run_pkgmgr_changelog(
extra_args=["--all"],
cwd=PROJECT_ROOT,
)
if __name__ == "__main__":
unittest.main()