From 72f3d95acfab26cf3a9fe1a28d6133e53c08d5a9 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Wed, 13 May 2026 16:59:29 -0700 Subject: [PATCH] Fix gap in don't-ingest logic. Closes #247. --- app/event_handlers.py | 18 +++++++++++ tests/test_event_handlers.py | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/app/event_handlers.py b/app/event_handlers.py index 157bbde..432b05b 100644 --- a/app/event_handlers.py +++ b/app/event_handlers.py @@ -237,6 +237,24 @@ async def on_new_contact(event: "Event") -> None: logger.debug("New contact: %s", public_key[:12]) contact_upsert = ContactUpsert.from_radio_dict(public_key.lower(), payload, on_radio=False) + + # Block new contacts whose type is in discovery_blocked_types, matching + # the same guard in _process_advertisement. Existing contacts (already + # in the DB) are always updated. + existing = await ContactRepository.get_by_key(public_key.lower()) + contact_type = contact_upsert.type or 0 + if existing is None and contact_type > 0: + from app.repository import AppSettingsRepository + + settings = await AppSettingsRepository.get() + if contact_type in settings.discovery_blocked_types: + logger.debug( + "Skipping new contact %s: type %d is in discovery_blocked_types", + public_key[:12], + contact_type, + ) + return + # Intentionally do not set first_seen or last_seen here: NEW_CONTACT # fires from the radio's stored contact DB, not an RF observation. # Both first_seen and last_seen are RF-only timestamps — they track diff --git a/tests/test_event_handlers.py b/tests/test_event_handlers.py index b0317e4..c2b7b4d 100644 --- a/tests/test_event_handlers.py +++ b/tests/test_event_handlers.py @@ -1182,3 +1182,62 @@ class TestOnNewContact: contacts = await ContactRepository.get_all() assert len(contacts) == 0 + + @pytest.mark.asyncio + async def test_blocks_new_contact_with_discovery_blocked_type(self, test_db): + """NEW_CONTACT for a blocked type should not create a contact.""" + from app.event_handlers import on_new_contact + from app.repository import AppSettingsRepository + + # Block clients (type 1) and rooms (type 3) + await AppSettingsRepository.update(discovery_blocked_types=[1, 3]) + + with ( + patch("app.event_handlers.broadcast_event") as mock_broadcast, + patch("app.event_handlers.time") as mock_time, + ): + mock_time.time.return_value = 1700000000 + + class MockEvent: + payload = { + "public_key": "dd" * 32, + "adv_name": "BlockedClient", + "type": 1, + "flags": 0, + } + + await on_new_contact(MockEvent()) + + contact = await ContactRepository.get_by_key("dd" * 32) + assert contact is None + mock_broadcast.assert_not_called() + + @pytest.mark.asyncio + async def test_allows_new_contact_with_non_blocked_type(self, test_db): + """NEW_CONTACT for a non-blocked type should still be created.""" + from app.event_handlers import on_new_contact + from app.repository import AppSettingsRepository + + # Block only clients (type 1) + await AppSettingsRepository.update(discovery_blocked_types=[1]) + + with ( + patch("app.event_handlers.broadcast_event") as mock_broadcast, + patch("app.event_handlers.time") as mock_time, + ): + mock_time.time.return_value = 1700000000 + + class MockEvent: + payload = { + "public_key": "ee" * 32, + "adv_name": "AllowedRepeater", + "type": 2, + "flags": 0, + } + + await on_new_contact(MockEvent()) + + contact = await ContactRepository.get_by_key("ee" * 32) + assert contact is not None + assert contact.name == "AllowedRepeater" + mock_broadcast.assert_called_once()