Add cross-distribution OS package installers (Arch PKGBUILD, Debian control, RPM spec) and restructure tests.
Remove deprecated AUR and Ansible requirements installers. Introduce Nix init + wrapper scripts and full packaging (Arch/DEB/RPM). Associated conversation: https://chatgpt.com/share/693476a8-b9f0-800f-8e0c-ea5151295ce2
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
# tests/unit/pkgmgr/installers/test_pkgbuild.py
|
||||
# tests/unit/pkgmgr/installers/os_packages/test_arch_pkgbuild.py
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.pkgbuild import PkgbuildInstaller
|
||||
from pkgmgr.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller
|
||||
|
||||
|
||||
class TestPkgbuildInstaller(unittest.TestCase):
|
||||
class TestArchPkgbuildInstaller(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repo = {"name": "test-repo"}
|
||||
self.ctx = RepoContext(
|
||||
@@ -24,7 +24,7 @@ class TestPkgbuildInstaller(unittest.TestCase):
|
||||
clone_mode="ssh",
|
||||
update_dependencies=False,
|
||||
)
|
||||
self.installer = PkgbuildInstaller()
|
||||
self.installer = ArchPkgbuildInstaller()
|
||||
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
@@ -38,7 +38,7 @@ class TestPkgbuildInstaller(unittest.TestCase):
|
||||
def test_supports_false_when_pkgbuild_missing(self, mock_which, mock_exists):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.pkgbuild.run_command")
|
||||
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.run_command")
|
||||
@patch("subprocess.check_output", return_value="python\ngit\n")
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
@@ -47,14 +47,14 @@ class TestPkgbuildInstaller(unittest.TestCase):
|
||||
):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
# Check subprocess.check_output arguments (clean shell)
|
||||
# subprocess.check_output call
|
||||
args, kwargs = mock_check_output.call_args
|
||||
cmd_list = args[0]
|
||||
self.assertEqual(cmd_list[0], "bash")
|
||||
self.assertIn("--noprofile", cmd_list)
|
||||
self.assertIn("--norc", cmd_list)
|
||||
|
||||
# Check that pacman is called with the extracted packages
|
||||
# pacman install command
|
||||
cmd = mock_run_command.call_args[0][0]
|
||||
self.assertTrue(cmd.startswith("sudo pacman -S --noconfirm "))
|
||||
self.assertIn("python", cmd)
|
||||
@@ -0,0 +1,66 @@
|
||||
# tests/unit/pkgmgr/installers/os_packages/test_debian_control.py
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.os_packages.debian_control import DebianControlInstaller
|
||||
|
||||
|
||||
class TestDebianControlInstaller(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repo = {"name": "repo"}
|
||||
self.ctx = RepoContext(
|
||||
repo=self.repo,
|
||||
identifier="id",
|
||||
repo_dir="/tmp/repo",
|
||||
repositories_base_dir="/tmp",
|
||||
bin_dir="/bin",
|
||||
all_repos=[self.repo],
|
||||
no_verification=False,
|
||||
preview=False,
|
||||
quiet=False,
|
||||
clone_mode="ssh",
|
||||
update_dependencies=False,
|
||||
)
|
||||
self.installer = DebianControlInstaller()
|
||||
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/apt-get")
|
||||
def test_supports_true(self, mock_which, mock_exists):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value=None)
|
||||
def test_supports_false_without_apt(self, mock_which, mock_exists):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.debian_control.run_command")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="""
|
||||
Build-Depends: python3, git (>= 2.0)
|
||||
Depends: curl | wget
|
||||
""")
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/apt-get")
|
||||
def test_run_installs_parsed_packages(
|
||||
self,
|
||||
mock_which,
|
||||
mock_exists,
|
||||
mock_file,
|
||||
mock_run_command
|
||||
):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
# First call: apt-get update
|
||||
self.assertIn("apt-get update", mock_run_command.call_args_list[0][0][0])
|
||||
|
||||
# Second call: install packages
|
||||
install_cmd = mock_run_command.call_args_list[1][0][0]
|
||||
self.assertIn("apt-get install -y", install_cmd)
|
||||
self.assertIn("python3", install_cmd)
|
||||
self.assertIn("git", install_cmd)
|
||||
self.assertIn("curl", install_cmd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
60
tests/unit/pkgmgr/installers/os_packages/test_rpm_spec.py
Normal file
60
tests/unit/pkgmgr/installers/os_packages/test_rpm_spec.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# tests/unit/pkgmgr/installers/os_packages/test_rpm_spec.py
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.os_packages.rpm_spec import RpmSpecInstaller
|
||||
|
||||
|
||||
class TestRpmSpecInstaller(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repo = {"name": "repo"}
|
||||
self.ctx = RepoContext(
|
||||
repo=self.repo,
|
||||
identifier="id",
|
||||
repo_dir="/tmp/repo",
|
||||
repositories_base_dir="/tmp",
|
||||
bin_dir="/bin",
|
||||
all_repos=[self.repo],
|
||||
no_verification=False,
|
||||
preview=False,
|
||||
quiet=False,
|
||||
clone_mode="ssh",
|
||||
update_dependencies=False,
|
||||
)
|
||||
self.installer = RpmSpecInstaller()
|
||||
|
||||
@patch("glob.glob", return_value=["/tmp/repo/test.spec"])
|
||||
@patch("shutil.which", return_value="/usr/bin/dnf")
|
||||
def test_supports_true(self, mock_which, mock_glob):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("glob.glob", return_value=[])
|
||||
@patch("shutil.which", return_value="/usr/bin/dnf")
|
||||
def test_supports_false_missing_spec(self, mock_which, mock_glob):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.rpm_spec.run_command")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="""
|
||||
BuildRequires: python3-devel, git >= 2.0
|
||||
Requires: curl
|
||||
""")
|
||||
@patch("glob.glob", return_value=["/tmp/repo/test.spec"])
|
||||
@patch("shutil.which", return_value="/usr/bin/dnf")
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_installs_parsed_dependencies(
|
||||
self, mock_exists, mock_which, mock_glob, mock_file, mock_run_command
|
||||
):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
install_cmd = mock_run_command.call_args_list[0][0][0]
|
||||
|
||||
self.assertIn("dnf install -y", install_cmd)
|
||||
self.assertIn("python3-devel", install_cmd)
|
||||
self.assertIn("git", install_cmd)
|
||||
self.assertIn("curl", install_cmd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,168 +0,0 @@
|
||||
# tests/unit/pkgmgr/installers/test_ansible_requirements.py
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.ansible_requirements import AnsibleRequirementsInstaller
|
||||
|
||||
|
||||
class TestAnsibleRequirementsInstaller(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repo = {"name": "test-repo"}
|
||||
self.ctx = RepoContext(
|
||||
repo=self.repo,
|
||||
identifier="test-id",
|
||||
repo_dir="/tmp/repo",
|
||||
repositories_base_dir="/tmp",
|
||||
bin_dir="/bin",
|
||||
all_repos=[self.repo],
|
||||
no_verification=False,
|
||||
preview=False,
|
||||
quiet=False,
|
||||
clone_mode="ssh",
|
||||
update_dependencies=False,
|
||||
)
|
||||
self.installer = AnsibleRequirementsInstaller()
|
||||
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_supports_true_when_requirements_exist(self, mock_exists):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "requirements.yml"))
|
||||
|
||||
@patch("os.path.exists", return_value=False)
|
||||
def test_supports_false_when_requirements_missing(self, mock_exists):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.ansible_requirements.run_command")
|
||||
@patch("tempfile.NamedTemporaryFile")
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="""
|
||||
collections:
|
||||
- name: community.docker
|
||||
roles:
|
||||
- src: geerlingguy.docker
|
||||
""",
|
||||
)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_installs_collections_and_roles(
|
||||
self, mock_exists, mock_file, mock_tmp, mock_run_command
|
||||
):
|
||||
# Fake temp file name
|
||||
mock_tmp().__enter__().name = "/tmp/req.yml"
|
||||
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
cmds = [call[0][0] for call in mock_run_command.call_args_list]
|
||||
self.assertIn(
|
||||
"ansible-galaxy collection install -r /tmp/req.yml",
|
||||
cmds,
|
||||
)
|
||||
self.assertIn(
|
||||
"ansible-galaxy role install -r /tmp/req.yml",
|
||||
cmds,
|
||||
)
|
||||
|
||||
# --- Neue Tests für den Validator -------------------------------------
|
||||
|
||||
@patch("pkgmgr.installers.ansible_requirements.run_command")
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="""
|
||||
- not:
|
||||
- a: mapping
|
||||
""",
|
||||
)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_raises_when_top_level_is_not_mapping(
|
||||
self, mock_exists, mock_file, mock_run_command
|
||||
):
|
||||
# YAML ist eine Liste -> Validator soll fehlschlagen
|
||||
with self.assertRaises(SystemExit):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
mock_run_command.assert_not_called()
|
||||
|
||||
@patch("pkgmgr.installers.ansible_requirements.run_command")
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="""
|
||||
collections: community.docker
|
||||
roles:
|
||||
- src: geerlingguy.docker
|
||||
""",
|
||||
)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_raises_when_collections_is_not_list(
|
||||
self, mock_exists, mock_file, mock_run_command
|
||||
):
|
||||
# collections ist ein String statt Liste -> invalid
|
||||
with self.assertRaises(SystemExit):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
mock_run_command.assert_not_called()
|
||||
|
||||
@patch("pkgmgr.installers.ansible_requirements.run_command")
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="""
|
||||
collections:
|
||||
- name: community.docker
|
||||
roles:
|
||||
- version: "latest"
|
||||
""",
|
||||
)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_raises_when_role_mapping_has_no_name(
|
||||
self, mock_exists, mock_file, mock_run_command
|
||||
):
|
||||
# roles-Eintrag ist Mapping ohne 'name' -> invalid
|
||||
with self.assertRaises(SystemExit):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
mock_run_command.assert_not_called()
|
||||
|
||||
@patch("pkgmgr.installers.ansible_requirements.run_command")
|
||||
@patch("tempfile.NamedTemporaryFile")
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="""
|
||||
collections:
|
||||
- name: community.docker
|
||||
extra_key: should_be_ignored_but_warned
|
||||
""",
|
||||
)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_accepts_unknown_top_level_keys(
|
||||
self, mock_exists, mock_file, mock_tmp, mock_run_command
|
||||
):
|
||||
"""
|
||||
Unknown top-level keys (z.B. 'extra_key') sollen nur eine Warnung
|
||||
auslösen, aber keine Validation-Exception.
|
||||
"""
|
||||
mock_tmp().__enter__().name = "/tmp/req.yml"
|
||||
|
||||
# Erwartung: kein SystemExit, run_command wird für collections aufgerufen
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
cmds = [call[0][0] for call in mock_run_command.call_args_list]
|
||||
self.assertIn(
|
||||
"ansible-galaxy collection install -r /tmp/req.yml",
|
||||
cmds,
|
||||
)
|
||||
# Keine roles definiert -> kein role-install
|
||||
self.assertNotIn(
|
||||
"ansible-galaxy role install -r /tmp/req.yml",
|
||||
cmds,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,97 +0,0 @@
|
||||
# tests/unit/pkgmgr/installers/test_aur.py
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.aur import AurInstaller, AUR_CONFIG_FILENAME
|
||||
|
||||
|
||||
class TestAurInstaller(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repo = {"name": "test-repo"}
|
||||
self.ctx = RepoContext(
|
||||
repo=self.repo,
|
||||
identifier="test-id",
|
||||
repo_dir="/tmp/repo",
|
||||
repositories_base_dir="/tmp",
|
||||
bin_dir="/bin",
|
||||
all_repos=[self.repo],
|
||||
no_verification=False,
|
||||
preview=False,
|
||||
quiet=False,
|
||||
clone_mode="ssh",
|
||||
update_dependencies=False,
|
||||
)
|
||||
self.installer = AurInstaller()
|
||||
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="""
|
||||
helper: yay
|
||||
packages:
|
||||
- aurutils
|
||||
- name: some-aur-only-tool
|
||||
reason: "Test tool"
|
||||
""",
|
||||
)
|
||||
def test_supports_true_when_arch_and_aur_config_present(
|
||||
self, mock_file, mock_exists, mock_which
|
||||
):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
mock_which.assert_called_with("pacman")
|
||||
mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, AUR_CONFIG_FILENAME))
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
def test_supports_false_when_not_arch(self, mock_which):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
@patch("os.path.exists", return_value=False)
|
||||
def test_supports_false_when_no_config(self, mock_exists, mock_which):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("shutil.which", side_effect=lambda name: "/usr/bin/pacman" if name == "pacman" else "/usr/bin/yay")
|
||||
@patch("pkgmgr.installers.aur.run_command")
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="""
|
||||
helper: yay
|
||||
packages:
|
||||
- aurutils
|
||||
- some-aur-only-tool
|
||||
""",
|
||||
)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_installs_packages_with_helper(
|
||||
self, mock_exists, mock_file, mock_run_command, mock_which
|
||||
):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
cmd = mock_run_command.call_args[0][0]
|
||||
self.assertTrue(cmd.startswith("yay -S --noconfirm "))
|
||||
self.assertIn("aurutils", cmd)
|
||||
self.assertIn("some-aur-only-tool", cmd)
|
||||
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="packages: []",
|
||||
)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_skips_when_no_packages(
|
||||
self, mock_exists, mock_file, mock_which
|
||||
):
|
||||
with patch("pkgmgr.installers.aur.run_command") as mock_run_command:
|
||||
self.installer.run(self.ctx)
|
||||
mock_run_command.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user