"""dbus-monitor parsing + the forward/rate-limit gate. No session bus needed — the parser is fed canned dbus-monitor output.""" import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from bolt_pet.notifications import Notification, NotificationGate, iter_notifications SAMPLE = '''signal time=1710000000.1 sender=org.freedesktop.DBus -> destination=:1.7 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired string ":1.7" method call time=1710000001.2 sender=:1.72 -> destination=org.freedesktop.Notifications serial=88 path=/org/freedesktop/Notifications; interface=org.freedesktop.Notifications; member=Notify string "Firefox" uint32 0 string "" string "Build finished" string "All 42 tests passed" array [ ] int32 -1 method call time=1710000002.3 sender=:1.80 -> destination=org.freedesktop.Notifications serial=91 path=/org/freedesktop/Notifications; interface=org.freedesktop.Notifications; member=Notify string "Calendar" uint32 0 string "calendar-icon" string "Standup in 5 minutes" string "" array [ ] ''' def _parse(text=SAMPLE): return list(iter_notifications(text.splitlines())) def test_parses_each_notify_call(): parsed = _parse() assert parsed == [ Notification(app="Firefox", summary="Build finished", body="All 42 tests passed"), Notification(app="Calendar", summary="Standup in 5 minutes", body=""), ] def test_unrelated_dbus_traffic_is_ignored(): noise = SAMPLE.split("method call")[0] assert _parse(noise) == [] def test_trailing_notification_without_a_following_block_is_still_emitted(): assert _parse()[-1].summary == "Standup in 5 minutes" def test_as_text_is_what_gets_sent_to_the_server(): assert _parse()[0].as_text() == "Firefox: Build finished — All 42 tests passed" assert _parse()[1].as_text() == "Calendar: Standup in 5 minutes" # ── gate ──────────────────────────────────────────────────────────────────── def _notification(summary="Build finished", app="Firefox"): return Notification(app=app, summary=summary, body="") def test_empty_filter_forwards_everything(): gate = NotificationGate("", min_interval=0) assert gate.should_forward(_notification(), now=0) is True def test_filter_regex_selects_what_is_worth_a_round_trip(): gate = NotificationGate(r"build|deploy", min_interval=0) assert gate.should_forward(_notification("Build finished"), now=0) is True assert gate.should_forward(_notification("New message from Dave"), now=1) is False def test_rate_limit_drops_a_burst(): gate = NotificationGate("", min_interval=60) assert gate.should_forward(_notification(), now=100) is True assert gate.should_forward(_notification(), now=120) is False assert gate.should_forward(_notification(), now=161) is True def test_a_broken_regex_does_not_silence_the_bridge(): gate = NotificationGate("(unclosed", min_interval=0) assert gate.should_forward(_notification(), now=0) is True def test_empty_notifications_are_dropped(): gate = NotificationGate("", min_interval=0) assert gate.should_forward(Notification(app="", summary="", body=""), now=0) is False