aboutsummaryrefslogtreecommitdiff
path: root/tests/installer-steps/test_hyprlock_pam.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/installer-steps/test_hyprlock_pam.py')
-rw-r--r--tests/installer-steps/test_hyprlock_pam.py85
1 files changed, 85 insertions, 0 deletions
diff --git a/tests/installer-steps/test_hyprlock_pam.py b/tests/installer-steps/test_hyprlock_pam.py
new file mode 100644
index 0000000..8d7295f
--- /dev/null
+++ b/tests/installer-steps/test_hyprlock_pam.py
@@ -0,0 +1,85 @@
+"""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()