"""Tests for scripts/audit-packages.sh. The auditor extracts every package archsetup installs — direct pacman_install/aur_install calls plus `for software in ...` loop lists — then verifies each against the right source: official packages must exist in the sync databases, AUR packages in the AUR. Packages that moved between sources are flagged in both directions. Tests run the real script against a fixture installer snippet, a fake pacman, and a fake curl that serves controlled AUR RPC JSON; no network, no real pacman db. Run from repo root: make test-unit (or python3 -m unittest tests.audit-packages.test_audit_packages) """ import json import os import shutil import stat import subprocess import tempfile import unittest REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) SCRIPT = os.path.join(REPO_ROOT, "scripts/audit-packages.sh") FIXTURE = """ pacman_install good-official # exists in repos pacman_install gone-official # vanished from repos, not in AUR pacman_install moved-to-aur # left repos, lives in AUR now aur_install good-aur # exists in AUR aur_install gone-aur # vanished from AUR aur_install now-official # promoted into the repos aur_install "$software" # variable — not a literal package for software in loop-pkg-a loop-pkg-b \\ loop-pkg-c; do pacman_install "$software" done """ class AuditHarness(unittest.TestCase): def setUp(self): self.tmp = tempfile.mkdtemp(prefix="audit-pkgs-test-") self.fixture = os.path.join(self.tmp, "installer-snippet") with open(self.fixture, "w") as f: f.write(FIXTURE) # Fake pacman: -Si exits 0 only for the packages "in the repos". official = ["good-official", "now-official", "loop-pkg-a", "loop-pkg-b", "loop-pkg-c"] self.fake_pacman = os.path.join(self.tmp, "pacman") self._stub(self.fake_pacman, ( 'case "$2" in\n' + "".join(f" {p}) exit 0 ;;\n" for p in official) + ' *) exit 1 ;;\nesac\n' )) # Fake curl: serves AUR RPC v5 info JSON for the packages "in the AUR". aur = ["good-aur", "moved-to-aur"] rpc = json.dumps({"results": [{"Name": p} for p in aur]}) self.fake_curl = os.path.join(self.tmp, "curl") self._stub(self.fake_curl, f"printf '%s' '{rpc}'\nexit 0\n") def tearDown(self): shutil.rmtree(self.tmp, ignore_errors=True) def _stub(self, path, body): with open(path, "w") as f: f.write("#!/bin/sh\n" + body) os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR) def run_audit(self, *args): env = os.environ.copy() env["AUDIT_PACMAN"] = self.fake_pacman env["AUDIT_CURL"] = self.fake_curl return subprocess.run( [SCRIPT, *args, self.fixture], env=env, capture_output=True, text=True, timeout=20, ) class TestParser(AuditHarness): def test_list_mode_extracts_direct_and_loop_packages(self): r = self.run_audit("--list") self.assertEqual(r.returncode, 0, msg=r.stderr) for p in ("good-official", "gone-official", "moved-to-aur", "loop-pkg-a", "loop-pkg-b", "loop-pkg-c"): self.assertIn(p, r.stdout) for p in ("good-aur", "gone-aur", "now-official"): self.assertIn(p, r.stdout) def test_variable_args_are_not_packages(self): r = self.run_audit("--list") self.assertNotIn("$software", r.stdout) class TestAudit(AuditHarness): def test_clean_packages_pass_silently(self): r = self.run_audit() self.assertNotIn("good-official", r.stdout) self.assertNotIn("good-aur", r.stdout) def test_missing_everywhere_reported(self): r = self.run_audit() self.assertNotEqual(r.returncode, 0) self.assertIn("gone-official", r.stdout) self.assertIn("gone-aur", r.stdout) def test_movers_flagged_in_both_directions(self): r = self.run_audit() self.assertIn("moved-to-aur", r.stdout) self.assertIn("now-official", r.stdout) def test_clean_fixture_exits_zero(self): clean = os.path.join(self.tmp, "clean-snippet") with open(clean, "w") as f: f.write("pacman_install good-official\naur_install good-aur\n") env = os.environ.copy() env["AUDIT_PACMAN"] = self.fake_pacman env["AUDIT_CURL"] = self.fake_curl r = subprocess.run([SCRIPT, clean], env=env, capture_output=True, text=True, timeout=20) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) if __name__ == "__main__": unittest.main()