From 5512f9e677cf6f1f252f2cee52c0418dfef564e0 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Fri, 13 Mar 2026 22:11:02 -0700 Subject: [PATCH] Fix last-advert selection logic for path recency --- app/packet_processor.py | 4 +-- tests/test_packet_pipeline.py | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/app/packet_processor.py b/app/packet_processor.py index 8a11310..094a058 100644 --- a/app/packet_processor.py +++ b/app/packet_processor.py @@ -442,8 +442,8 @@ async def _process_advertisement( PATH_FRESHNESS_SECONDS = 60 use_existing_path = False - if existing and existing.last_seen: - path_age = timestamp - existing.last_seen + if existing and existing.last_advert: + path_age = timestamp - existing.last_advert existing_path_len = existing.last_path_len if existing.last_path_len >= 0 else float("inf") # Keep existing path if it's fresh and shorter (or equal) diff --git a/tests/test_packet_pipeline.py b/tests/test_packet_pipeline.py index f821ccd..0e2c067 100644 --- a/tests/test_packet_pipeline.py +++ b/tests/test_packet_pipeline.py @@ -291,6 +291,7 @@ class TestAdvertisementPipeline: "public_key": test_pubkey, "name": "TestNode", "type": 1, + "last_advert": 1000, "last_seen": 1000, "last_path_len": 3, "last_path": "aabbcc", # 3 bytes = 3 hops @@ -353,6 +354,54 @@ class TestAdvertisementPipeline: contact = await ContactRepository.get_by_key(test_pubkey) assert contact.last_path_len == 1 # Still the shorter path + @pytest.mark.asyncio + async def test_advertisement_path_freshness_uses_last_advert_not_last_seen( + self, test_db, captured_broadcasts + ): + """Non-advert contact activity should not keep an old advert path artificially fresh.""" + from unittest.mock import MagicMock + + from app.decoder import ParsedAdvertisement + from app.packet_processor import _process_advertisement + + test_pubkey = "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + await ContactRepository.upsert( + { + "public_key": test_pubkey, + "name": "TestNode", + "type": 1, + "last_advert": 1000, + "last_seen": 1055, # Simulates later non-advert activity + "last_path_len": 1, + "last_path": "aa", + } + ) + + broadcasts, mock_broadcast = captured_broadcasts + + longer_packet_info = MagicMock() + longer_packet_info.path_length = 3 + longer_packet_info.path = bytes.fromhex("aabbcc") + longer_packet_info.path_hash_size = 1 + longer_packet_info.payload = b"" + + with patch("app.packet_processor.broadcast_event", mock_broadcast): + with patch("app.packet_processor.parse_advertisement") as mock_parse: + mock_parse.return_value = ParsedAdvertisement( + public_key=test_pubkey, + name="TestNode", + timestamp=1070, + lat=None, + lon=None, + device_role=1, + ) + await _process_advertisement(b"", timestamp=1070, packet_info=longer_packet_info) + + contact = await ContactRepository.get_by_key(test_pubkey) + assert contact is not None + assert contact.last_path_len == 3 + assert contact.last_path == "aabbcc" + @pytest.mark.asyncio async def test_advertisement_default_path_len_treated_as_infinity( self, test_db, captured_broadcasts