# SPDX-License-Identifier: GPL-3.0-or-later """Tests for file integrity monitoring: hashing, diff, alerts, pacman parse.""" import os import tempfile import unittest from pathlib import Path from enodia_sentinel import fim from enodia_sentinel.alert import Severity class TestHashAndScan(unittest.TestCase): def setUp(self): self.d = tempfile.TemporaryDirectory() self.tmp = Path(self.d.name) def tearDown(self): self.d.cleanup() def test_hash_stable_and_sensitive(self): p = self.tmp / "f" p.write_text("hello") h1 = fim.hash_file(str(p)) self.assertEqual(h1, fim.hash_file(str(p))) p.write_text("hell0") self.assertNotEqual(h1, fim.hash_file(str(p))) def test_scan_collects_entries(self): (self.tmp / "a").write_text("x") (self.tmp / "b").write_text("y") scan = fim.scan_paths([str(self.tmp)]) self.assertEqual(len(scan), 2) self.assertIn("sha256", next(iter(scan.values()))) class TestDiff(unittest.TestCase): def setUp(self): self.d = tempfile.TemporaryDirectory() self.tmp = Path(self.d.name) (self.tmp / "keep").write_text("keep") (self.tmp / "gone").write_text("gone") self.base = fim.scan_paths([str(self.tmp)]) def tearDown(self): self.d.cleanup() def test_modified_detected_with_preserved_mtime(self): p = self.tmp / "keep" st = p.stat() p.write_text("TAMPERED") os.utime(p, (st.st_atime, st.st_mtime)) # preserve mtime new = fim.scan_paths([str(self.tmp)]) d = fim.diff(self.base, new) self.assertIn((str(p), ["sha256"]), [(x, c) for x, c in d["modified"]]) def test_added_and_removed(self): (self.tmp / "new").write_text("n") (self.tmp / "gone").unlink() d = fim.diff(self.base, fim.scan_paths([str(self.tmp)])) self.assertIn(str(self.tmp / "new"), d["added"]) self.assertIn(str(self.tmp / "gone"), d["removed"]) def test_alerts_have_sids(self): (self.tmp / "x").write_text("x") d = fim.diff(self.base, fim.scan_paths([str(self.tmp)])) alerts = list(fim.diff_alerts(d)) sids = {a.sid for a in alerts} self.assertIn(fim.SID_ADDED, sids) class TestPacmanParse(unittest.TestCase): SAMPLE = """\ warning: qbittorrent: /usr/bin/qbittorrent (SHA256 checksum mismatch) openssh: /usr/bin/ssh (Permissions mismatch) coreutils: 105 total files, 1 altered files warning: No package owns /usr/local/bin/custom bar: /etc/bar.conf (Modification time mismatch) """ def test_parse(self): hits = fim.parse_pacman_verify(self.SAMPLE) paths = {p for _pkg, p, _r in hits} self.assertIn("/usr/bin/qbittorrent", paths) self.assertIn("/usr/bin/ssh", paths) # mtime-only and summary/no-owner lines are ignored self.assertNotIn("/etc/bar.conf", paths) self.assertEqual(len(hits), 2) def test_pkg_alert_is_critical(self): # exercise the alert builder via a monkeypatched verify orig = fim.pacman_verify fim.pacman_verify = lambda timeout=600: [("openssh", "/usr/bin/ssh", "SHA256 checksum mismatch")] try: alerts = list(fim.pacman_verify_alerts()) finally: fim.pacman_verify = orig self.assertEqual(alerts[0].severity, Severity.CRITICAL) self.assertEqual(alerts[0].sid, fim.SID_PKG) if __name__ == "__main__": unittest.main()