diff --git a/AppImage/scripts/notification_manager.py b/AppImage/scripts/notification_manager.py index 765942ce..8dcb4ca1 100644 --- a/AppImage/scripts/notification_manager.py +++ b/AppImage/scripts/notification_manager.py @@ -1636,20 +1636,33 @@ class NotificationManager: conn = sqlite3.connect(str(DB_PATH), timeout=10) conn.execute('PRAGMA journal_mode=WAL') conn.execute('PRAGMA busy_timeout=5000') - # Adjacent collectors can observe the same completed task. Only - # coalesce byte-for-byte identical rows, so a different version, - # source or result stays visible in the same digest. + # Adjacent collectors can observe one completed LXC update with + # different transport details (for example source or duration). + # The rendered title already identifies its LXC and result, so + # coalesce that narrowly. Other event types retain the stricter + # title-and-body comparison so distinct updates stay visible. now = int(time.time()) - duplicate = conn.execute( - 'SELECT 1 FROM digest_pending ' - 'WHERE channel = ? AND event_type = ? AND event_group = ? ' - 'AND severity = ? AND title = ? AND body = ? AND ts >= ? ' - 'LIMIT 1', - ( - ch_name, event_type, event_group, severity, title, body, - now - self._DIGEST_DUPLICATE_WINDOW, - ), - ).fetchone() + if event_type == 'lxc_update_applied': + duplicate = conn.execute( + 'SELECT 1 FROM digest_pending ' + 'WHERE channel = ? AND event_type = ? AND event_group = ? ' + 'AND severity = ? AND title = ? AND ts >= ? LIMIT 1', + ( + ch_name, event_type, event_group, severity, title, + now - self._DIGEST_DUPLICATE_WINDOW, + ), + ).fetchone() + else: + duplicate = conn.execute( + 'SELECT 1 FROM digest_pending ' + 'WHERE channel = ? AND event_type = ? AND event_group = ? ' + 'AND severity = ? AND title = ? AND body = ? AND ts >= ? ' + 'LIMIT 1', + ( + ch_name, event_type, event_group, severity, title, body, + now - self._DIGEST_DUPLICATE_WINDOW, + ), + ).fetchone() if duplicate: conn.close() return diff --git a/AppImage/scripts/tests/test_notification_runtime_i18n.py b/AppImage/scripts/tests/test_notification_runtime_i18n.py index c792d85d..3e1b2193 100644 --- a/AppImage/scripts/tests/test_notification_runtime_i18n.py +++ b/AppImage/scripts/tests/test_notification_runtime_i18n.py @@ -707,7 +707,7 @@ class RuntimeCatalogTests(unittest.TestCase): self.assertNotIn("System", channel.payload[1]) self.assertTrue(channel.payload[3]["_quiet_hours_summary"]) - def test_digest_buffer_coalesces_only_identical_recent_events(self): + def test_digest_buffer_coalesces_lxc_results_despite_detail_changes(self): manager = notification_manager.NotificationManager() with tempfile.TemporaryDirectory() as directory: db_path = Path(directory) / "settings.db" @@ -721,21 +721,26 @@ class RuntimeCatalogTests(unittest.TestCase): conn.close() with mock.patch.object(notification_manager, "DB_PATH", db_path), \ - mock.patch.object(notification_manager.time, "time", side_effect=(1000, 1060, 1070, 1301)): + mock.patch.object(notification_manager.time, "time", side_effect=(1000, 1060, 1070, 1080, 1301)): manager._buffer_digest_event( "telegram", "lxc_update_applied", "vm_ct", "INFO", "pve01: LXC wireguard (101) update completed", "Source: Manual", ) - # Same completed task from an adjacent collector: do not list it twice. - manager._buffer_digest_event( - "telegram", "lxc_update_applied", "vm_ct", "INFO", - "pve01: LXC wireguard (101) update completed", "Source: Manual", - ) - # Different result/body for the same LXC remains visible. + # An adjacent collector can report the same completion with + # a different source or duration. It must not appear twice. manager._buffer_digest_event( "telegram", "lxc_update_applied", "vm_ct", "INFO", "pve01: LXC wireguard (101) update completed", "Source: Scheduled", ) + # Other event types still require the full message to match. + manager._buffer_digest_event( + "telegram", "app_update_available", "applications", "INFO", + "pve01: Update available", "Version: 1.0 → 1.1", + ) + manager._buffer_digest_event( + "telegram", "app_update_available", "applications", "INFO", + "pve01: Update available", "Version: 1.0 → 1.2", + ) # The same result is allowed again outside the short window. manager._buffer_digest_event( "telegram", "lxc_update_applied", "vm_ct", "INFO", @@ -750,7 +755,12 @@ class RuntimeCatalogTests(unittest.TestCase): self.assertEqual( rows, - [(1000, "Source: Manual"), (1070, "Source: Scheduled"), (1301, "Source: Manual")], + [ + (1000, "Source: Manual"), + (1070, "Version: 1.0 → 1.1"), + (1080, "Version: 1.0 → 1.2"), + (1301, "Source: Manual"), + ], ) def test_visible_templates_use_known_backend_and_frontend_groups(self):