import sys import unittest from pathlib import Path from unittest import mock sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from cthulhu import notification_presenter from cthulhu.mako_notification_monitor import MakoNotificationMonitor class _FakeScript: def __init__(self): self.speechGenerator = mock.Mock() self.speechGenerator.voice.return_value = object() self.speakMessage = mock.Mock() self.displayBrailleMessage = mock.Mock() class _FakeScriptManager: def __init__(self, script): self._script = script def get_default_script(self): return self._script class _FakeApp: def __init__(self, script): self._script_manager = _FakeScriptManager(script) def getScriptManager(self): return self._script_manager class MakoNotificationMonitorTests(unittest.TestCase): def setUp(self): self.presenter = notification_presenter.NotificationPresenter() self.script = _FakeScript() self.app = _FakeApp(self.script) presenter_patcher = mock.patch( "cthulhu.mako_notification_monitor.notification_presenter.getPresenter", return_value=self.presenter, ) self.addCleanup(presenter_patcher.stop) presenter_patcher.start() self.monitor = MakoNotificationMonitor(self.app) self.monitor._generation = 1 def test_initial_seed_does_not_announce_existing_notifications(self): self.monitor._sync_notifications( [ { "id": 10, "app-name": "discord", "summary": "hello", "body": "world", "actions": {"default": "View"}, } ], announce_new=False, ) self.assertEqual(self.monitor._known_ids, {10}) self.assertEqual(self.presenter._notifications, []) self.script.speakMessage.assert_not_called() self.script.displayBrailleMessage.assert_not_called() def test_new_notification_is_spoken_and_saved(self): self.monitor._sync_notifications( [ { "id": 10, "app-name": "discord", "summary": "old", "body": "", "actions": {}, } ], announce_new=False, ) self.monitor._sync_notifications( [ { "id": 10, "app-name": "discord", "summary": "old", "body": "", "actions": {}, }, { "id": 11, "app-name": "discord", "summary": "new summary", "body": "new body", "actions": {"default": "View"}, }, ], announce_new=True, ) self.assertEqual(len(self.presenter._notifications), 1) entry = self.presenter._notifications[0] self.assertEqual(entry.notification_id, 11) self.assertTrue(entry.live) self.assertEqual(entry.source, "mako") self.assertEqual(entry.actions, {"default": "View"}) self.assertEqual(entry.message, "Notification new summary new body") self.script.speakMessage.assert_called_once() self.script.displayBrailleMessage.assert_called_once() def test_removed_notification_is_marked_not_live(self): self.monitor._sync_notifications( [ { "id": 11, "app-name": "discord", "summary": "new summary", "body": "new body", "actions": {"default": "View"}, }, ], announce_new=True, ) self.assertTrue(self.presenter._notifications[0].live) self.monitor._sync_notifications([], announce_new=False) self.assertFalse(self.presenter._notifications[0].live) self.assertEqual(self.monitor._known_ids, set()) def test_parse_notification_strips_markup_and_normalizes_actions(self): parsed = self.monitor._parse_notification( { "id": 22, "app-name": "Crash Reporting System", "summary": "Service Crash", "body": "/usr/bin/python3.14 crashed", "desktop-entry": "crash-handler", "urgency": 2, "actions": {"1": "Details"}, } ) self.assertEqual(parsed["notification_id"], 22) self.assertEqual(parsed["summary"], "Service Crash") self.assertEqual(parsed["body"], "/usr/bin/python3.14 crashed") self.assertEqual(parsed["actions"], {"1": "Details"}) self.assertEqual( parsed["message"], "Notification Service Crash /usr/bin/python3.14 crashed", ) if __name__ == "__main__": unittest.main()