from __future__ import annotations import io import sys import tempfile import textwrap import unittest from contextlib import redirect_stdout from pathlib import Path from unittest.mock import patch # Ensure we import THIS repo's src/ implementation, not an installed site-packages one. REPO_ROOT = Path(__file__).resolve().parents[1] SRC_DIR = REPO_ROOT / "src" sys.path.insert(0, str(SRC_DIR)) from p2pkg.__main__ import main, migrate_one # noqa: E402 class TestMigration(unittest.TestCase): def test_migrate_one_creates_package_and_exports_public_api(self) -> None: with tempfile.TemporaryDirectory() as td: root = Path(td) mod = root / "roles_list.py" mod.write_text( textwrap.dedent( """\ __all__ = ["add", "PUBLIC_CONST"] PUBLIC_CONST = 123 _PRIVATE_CONST = 999 def add(a: int, b: int) -> int: return a + b def _hidden() -> int: return 1 """ ), encoding="utf-8", ) migrate_one(mod, use_git=False, repo_root=root) pkg = root / "roles_list" self.assertTrue((pkg / "__main__.py").exists()) self.assertTrue((pkg / "__init__.py").exists()) self.assertFalse(mod.exists()) sys.path.insert(0, str(root)) try: import roles_list # type: ignore self.assertTrue(hasattr(roles_list, "add")) self.assertEqual(roles_list.add(2, 3), 5) self.assertTrue(hasattr(roles_list, "PUBLIC_CONST")) self.assertEqual(roles_list.PUBLIC_CONST, 123) self.assertEqual(set(roles_list.__all__), {"add", "PUBLIC_CONST"}) self.assertFalse(hasattr(roles_list, "_hidden")) finally: sys.path.remove(str(root)) sys.modules.pop("roles_list", None) def test_main_non_recursive_prompts_and_applies_on_yes(self) -> None: with tempfile.TemporaryDirectory() as td: root = Path(td) mod = root / "foo.py" mod.write_text("X = 1\n", encoding="utf-8") buf = io.StringIO() with redirect_stdout(buf), patch("builtins.input", return_value="y"): rc = main(["--no-git", "--repo-root", str(root), str(mod)]) self.assertEqual(rc, 0) self.assertFalse(mod.exists()) self.assertTrue((root / "foo" / "__main__.py").exists()) self.assertTrue((root / "foo" / "__init__.py").exists()) def test_main_recursive_preview_does_not_apply_and_does_not_prompt(self) -> None: with tempfile.TemporaryDirectory() as td: root = Path(td) target = root / "tree" target.mkdir() (target / "x.py").write_text("X = 1\n", encoding="utf-8") (target / "y.py").write_text("Y = 2\n", encoding="utf-8") buf = io.StringIO() with redirect_stdout(buf), patch("builtins.input") as mocked_input: rc = main(["-R", "-p", "--no-git", "--repo-root", str(root), str(target)]) self.assertEqual(rc, 0) mocked_input.assert_not_called() self.assertTrue((target / "x.py").exists()) self.assertTrue((target / "y.py").exists()) self.assertFalse((target / "x").exists()) self.assertFalse((target / "y").exists()) def test_main_recursive_force_applies_without_prompt(self) -> None: with tempfile.TemporaryDirectory() as td: root = Path(td) target = root / "tree" target.mkdir() (target / "x.py").write_text("X = 1\n", encoding="utf-8") (target / "y.py").write_text("Y = 2\n", encoding="utf-8") buf = io.StringIO() with redirect_stdout(buf), patch("builtins.input") as mocked_input: rc = main(["-R", "-f", "--no-git", "--repo-root", str(root), str(target)]) self.assertEqual(rc, 0) mocked_input.assert_not_called() self.assertFalse((target / "x.py").exists()) self.assertFalse((target / "y.py").exists()) self.assertTrue((target / "x" / "__main__.py").exists()) self.assertTrue((target / "y" / "__main__.py").exists()) def test_main_recursive_prompts_and_aborts_on_no(self) -> None: with tempfile.TemporaryDirectory() as td: root = Path(td) target = root / "tree" target.mkdir() (target / "x.py").write_text("X = 1\n", encoding="utf-8") buf = io.StringIO() with redirect_stdout(buf), patch("builtins.input", return_value="n"): rc = main(["-R", "--no-git", "--repo-root", str(root), str(target)]) self.assertEqual(rc, 1) self.assertTrue((target / "x.py").exists()) self.assertFalse((target / "x").exists()) def test_main_recursive_prompts_and_applies_on_yes(self) -> None: with tempfile.TemporaryDirectory() as td: root = Path(td) target = root / "tree" target.mkdir() (target / "x.py").write_text("X = 1\n", encoding="utf-8") (target / "y.py").write_text("Y = 2\n", encoding="utf-8") buf = io.StringIO() with redirect_stdout(buf), patch("builtins.input", return_value="y"): rc = main(["-R", "--no-git", "--repo-root", str(root), str(target)]) self.assertEqual(rc, 0) self.assertFalse((target / "x.py").exists()) self.assertFalse((target / "y.py").exists()) self.assertTrue((target / "x" / "__main__.py").exists()) self.assertTrue((target / "y" / "__main__.py").exists()) if __name__ == "__main__": unittest.main(verbosity=2)