From 562a6da29155ac9620c0bbcf421f455cfba25410 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sun, 14 Dec 2025 16:14:17 +0100 Subject: [PATCH] test(integration): move mirror CLI tests from e2e to integration and patch side effects https://chatgpt.com/share/693ed188-eb80-800f-8541-356e3fbd98c5 --- tests/e2e/test_mirror_commands.py | 164 ---------------------- tests/integration/test_mirror_commands.py | 160 +++++++++++++++++++++ 2 files changed, 160 insertions(+), 164 deletions(-) delete mode 100644 tests/e2e/test_mirror_commands.py create mode 100644 tests/integration/test_mirror_commands.py diff --git a/tests/e2e/test_mirror_commands.py b/tests/e2e/test_mirror_commands.py deleted file mode 100644 index 5055132..0000000 --- a/tests/e2e/test_mirror_commands.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -E2E integration tests for the `pkgmgr mirror` command family. - -Covered commands: - - - pkgmgr mirror --help - - pkgmgr mirror list --preview --all - - pkgmgr mirror diff --preview --all - - pkgmgr mirror merge config file --preview --all - - pkgmgr mirror setup --preview --all - - pkgmgr mirror check --preview --all - - pkgmgr mirror provision --preview --all - -All commands are executed via the real CLI entry point (main module). -With --preview enabled, all operations are non-destructive and safe -to run inside CI containers. -""" - -import io -import runpy -import sys -import unittest -from contextlib import redirect_stdout, redirect_stderr - - -class TestIntegrationMirrorCommands(unittest.TestCase): - """ - End-to-end tests for `pkgmgr mirror` commands. - """ - - # ------------------------------------------------------------ - # Helper - # ------------------------------------------------------------ - def _run_pkgmgr(self, args): - """ - Execute pkgmgr with the given arguments and return captured output. - - - Treat SystemExit(0) or SystemExit(None) as success. - - Any other exit code is considered a test failure. - """ - original_argv = list(sys.argv) - buffer = io.StringIO() - cmd_repr = "pkgmgr " + " ".join(args) - - try: - sys.argv = ["pkgmgr"] + list(args) - - try: - with redirect_stdout(buffer), redirect_stderr(buffer): - runpy.run_module("pkgmgr", run_name="__main__") - except SystemExit as exc: - code = exc.code if isinstance(exc.code, int) else None - if code not in (0, None): - raise AssertionError( - "%r failed with exit code %r.\n\nOutput:\n%s" - % (cmd_repr, exc.code, buffer.getvalue()) - ) - - return buffer.getvalue() - - finally: - sys.argv = original_argv - - # ------------------------------------------------------------ - # Tests - # ------------------------------------------------------------ - - def test_mirror_help(self): - """ - `pkgmgr mirror --help` should run without error and print usage info. - """ - output = self._run_pkgmgr(["mirror", "--help"]) - self.assertIn("usage:", output) - self.assertIn("pkgmgr mirror", output) - - def test_mirror_list_preview_all(self): - """ - `pkgmgr mirror list --preview --all` - """ - output = self._run_pkgmgr( - ["mirror", "list", "--preview", "--all"] - ) - self.assertTrue( - output.strip(), - "Expected output from mirror list", - ) - - def test_mirror_diff_preview_all(self): - """ - `pkgmgr mirror diff --preview --all` - """ - output = self._run_pkgmgr( - ["mirror", "diff", "--preview", "--all"] - ) - self.assertTrue( - output.strip(), - "Expected output from mirror diff", - ) - - def test_mirror_merge_config_to_file_preview_all(self): - """ - `pkgmgr mirror merge config file --preview --all` - """ - output = self._run_pkgmgr( - [ - "mirror", - "merge", - "config", - "file", - "--preview", - "--all", - ] - ) - self.assertTrue( - output.strip(), - "Expected output from mirror merge (config -> file)", - ) - - def test_mirror_setup_preview_all(self): - """ - `pkgmgr mirror setup --preview --all` - """ - output = self._run_pkgmgr( - ["mirror", "setup", "--preview", "--all"] - ) - self.assertTrue( - output.strip(), - "Expected output from mirror setup", - ) - - def test_mirror_check_preview_all(self): - """ - `pkgmgr mirror check --preview --all` - - Performs non-destructive remote checks (git ls-remote). - """ - output = self._run_pkgmgr( - ["mirror", "check", "--preview", "--all"] - ) - self.assertTrue( - output.strip(), - "Expected output from mirror check", - ) - - def test_mirror_provision_preview_all(self): - """ - `pkgmgr mirror provision --preview --all` - - In preview mode this MUST NOT create remote repositories. - """ - output = self._run_pkgmgr( - ["mirror", "provision", "--preview", "--all"] - ) - self.assertTrue( - output.strip(), - "Expected output from mirror provision (preview)", - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/integration/test_mirror_commands.py b/tests/integration/test_mirror_commands.py new file mode 100644 index 0000000..3c1626e --- /dev/null +++ b/tests/integration/test_mirror_commands.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +CLI integration tests for `pkgmgr mirror`. + +These tests validate: +- CLI argument parsing +- command dispatch +- command orchestration + +All side effects (git, network, remote provisioning, filesystem writes) +are patched to keep tests deterministic and CI-safe. +""" + +from __future__ import annotations + +import io +import os +import runpy +import sys +import unittest +from contextlib import ExitStack, redirect_stderr, redirect_stdout +from typing import Dict, List, Optional +from unittest.mock import MagicMock, PropertyMock, patch + + +class TestIntegrationMirrorCommands(unittest.TestCase): + """ + End-to-end tests for `pkgmgr mirror` commands. + """ + + def _run_pkgmgr(self, args: List[str], extra_env: Optional[Dict[str, str]] = None) -> str: + """ + Execute pkgmgr with the given arguments and return captured output. + + - Treat SystemExit(0) or SystemExit(None) as success. + - Any other exit code is considered a test failure. + - Mirror commands are patched to avoid network/destructive operations. + """ + original_argv = list(sys.argv) + original_env = dict(os.environ) + buffer = io.StringIO() + cmd_repr = "pkgmgr " + " ".join(args) + + # Shared dummy context used by multiple mirror commands + dummy_ctx = MagicMock() + dummy_ctx.identifier = "dummy-repo" + dummy_ctx.repo_dir = "/tmp/dummy-repo" + dummy_ctx.config_mirrors = {"origin": "git@github.com:alice/repo.git"} + dummy_ctx.file_mirrors = {"backup": "ssh://git@git.example:2201/alice/repo.git"} + type(dummy_ctx).resolved_mirrors = PropertyMock( + return_value={ + "origin": "git@github.com:alice/repo.git", + "backup": "ssh://git@git.example:2201/alice/repo.git", + } + ) + + # Helper: patch with create=True so missing modules/symbols don't explode + def _p(target: str, **kwargs): + return patch(target, create=True, **kwargs) + + # Fake result for remote provisioning (preview-safe) + def _fake_ensure_remote_repo(spec, provider_hint=None, options=None): + # Safety: E2E should only ever call this in preview mode + if options is not None and getattr(options, "preview", False) is not True: + raise AssertionError(f"{cmd_repr} attempted ensure_remote_repo without preview=True in E2E.") + r = MagicMock() + r.status = "preview" + r.message = "Preview mode (E2E patched): no remote provisioning performed." + r.url = None + return r + + try: + sys.argv = ["pkgmgr"] + list(args) + if extra_env: + os.environ.update(extra_env) + + with ExitStack() as stack: + # build_context is imported directly in these modules: + stack.enter_context(_p("pkgmgr.actions.mirror.list_cmd.build_context", return_value=dummy_ctx)) + stack.enter_context(_p("pkgmgr.actions.mirror.diff_cmd.build_context", return_value=dummy_ctx)) + stack.enter_context(_p("pkgmgr.actions.mirror.merge_cmd.build_context", return_value=dummy_ctx)) + stack.enter_context(_p("pkgmgr.actions.mirror.setup_cmd.build_context", return_value=dummy_ctx)) + stack.enter_context(_p("pkgmgr.actions.mirror.remote_provision.build_context", return_value=dummy_ctx)) + stack.enter_context(_p("pkgmgr.actions.mirror.check_cmd.build_context", return_value=dummy_ctx)) + + # setup_cmd imports ensure_origin_remote and probe_mirror directly: + stack.enter_context(_p("pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote", return_value=None)) + stack.enter_context(_p("pkgmgr.actions.mirror.setup_cmd.probe_mirror", return_value=(True, ""))) + + # check_cmd likely imports probe_mirror directly too (make it deterministic) + stack.enter_context(_p("pkgmgr.actions.mirror.check_cmd.probe_mirror", return_value=(True, ""))) + + # remote provisioning: remote_provision imports ensure_remote_repo directly from core: + stack.enter_context( + _p( + "pkgmgr.actions.mirror.remote_provision.ensure_remote_repo", + side_effect=_fake_ensure_remote_repo, + ) + ) + + # Extra safety: if anything calls remote_check.run_git directly, make it inert + stack.enter_context(_p("pkgmgr.actions.mirror.remote_check.run_git", return_value="dummy")) + + with redirect_stdout(buffer), redirect_stderr(buffer): + try: + runpy.run_module("pkgmgr", run_name="__main__") + except SystemExit as exc: + code = exc.code if isinstance(exc.code, int) else None + if code not in (0, None): + raise AssertionError( + "%r failed with exit code %r.\n\nOutput:\n%s" + % (cmd_repr, exc.code, buffer.getvalue()) + ) + + + return buffer.getvalue() + + finally: + sys.argv = original_argv + os.environ.clear() + os.environ.update(original_env) + + # ------------------------------------------------------------ + # Tests + # ------------------------------------------------------------ + + def test_mirror_help(self) -> None: + output = self._run_pkgmgr(["mirror", "--help"]) + self.assertIn("usage:", output.lower()) + self.assertIn("mirror", output.lower()) + + def test_mirror_list_preview_all(self) -> None: + output = self._run_pkgmgr(["mirror", "list", "--preview", "--all"]) + self.assertTrue(output.strip(), "Expected output from mirror list") + + def test_mirror_diff_preview_all(self) -> None: + output = self._run_pkgmgr(["mirror", "diff", "--preview", "--all"]) + self.assertTrue(output.strip(), "Expected output from mirror diff") + + def test_mirror_merge_config_to_file_preview_all(self) -> None: + output = self._run_pkgmgr(["mirror", "merge", "config", "file", "--preview", "--all"]) + self.assertTrue(output.strip(), "Expected output from mirror merge (config -> file)") + + def test_mirror_setup_preview_all(self) -> None: + output = self._run_pkgmgr(["mirror", "setup", "--preview", "--all"]) + self.assertTrue(output.strip(), "Expected output from mirror setup") + + def test_mirror_check_preview_all(self) -> None: + output = self._run_pkgmgr(["mirror", "check", "--preview", "--all"]) + self.assertTrue(output.strip(), "Expected output from mirror check") + + def test_mirror_provision_preview_all(self) -> None: + output = self._run_pkgmgr(["mirror", "provision", "--preview", "--all"]) + self.assertTrue(output.strip(), "Expected output from mirror provision (preview)") + + +if __name__ == "__main__": + unittest.main()