"""Tests for configure_hyprlock_pam in the archsetup installer. hyprlock's packaged /etc/pam.d/hyprlock ships only `auth include login`, with no account or session sections. pam_end() then crashes cleaning up handles that were never initialised -- one of the documented causes of the hyprlock lockout where the session wedges with no prompt (hyprlock#953). configure_hyprlock_pam writes a complete stack so the account and session phases run. These tests exercise the REAL function body, extracted from the `archsetup` script at run time, against a fixture PAM path. Run from repo root: python3 -m unittest tests.installer-steps.test_hyprlock_pam """ import os import subprocess import tempfile import unittest REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) ARCHSETUP = os.path.join(REPO_ROOT, "archsetup") def run(path): script = ( 'logfile=/dev/null\n' 'display() { :; }\n' 'backup_system_file() { :; }\n' 'error_warn() { echo "WARN:$1"; return 1; }\n' "source <(sed -n '/^configure_hyprlock_pam() {/,/^}/p' \"%s\")\n" 'configure_hyprlock_pam "%s"\n' ) % (ARCHSETUP, path) return subprocess.run(["bash", "-c", script], capture_output=True, text=True, timeout=10) class ConfigureHyprlockPam(unittest.TestCase): def _write(self, initial="auth include login\n"): fd, path = tempfile.mkstemp(prefix="pam-hyprlock-") os.close(fd) with open(path, "w") as f: f.write(initial) self.addCleanup(os.remove, path) return path def _stack(self, path): with open(path) as f: return [l.split()[0] for l in f if l.strip() and not l.lstrip().startswith("#")] # ---------------------------------------------------------- normal ---- def test_writes_all_three_phases(self): path = self._write() run(path) phases = set(self._stack(path)) self.assertEqual(phases, {"auth", "account", "session"}, "the complete stack must carry all three PAM phases") def test_account_and_session_are_the_added_ones(self): path = self._write() run(path) stack = self._stack(path) self.assertIn("account", stack) self.assertIn("session", stack) # -------------------------------------------------------- boundary ---- def test_idempotent_on_rerun(self): path = self._write() run(path) first = open(path).read() run(path) self.assertEqual(open(path).read(), first, "a second run must not stack duplicate lines") def test_overwrites_the_incomplete_package_default(self): # The package default is exactly `auth include login`; a re-run replaces # it wholesale rather than appending to it. path = self._write("auth include login\n") run(path) self.assertEqual(self._stack(path).count("auth"), 1) if __name__ == "__main__": unittest.main()