This commit is contained in:
Jack Kingsman
2026-03-06 23:12:21 -08:00
parent 9c54ea623e
commit 3edc7d9bd1
17 changed files with 234 additions and 125 deletions
+30
View File
@@ -260,6 +260,12 @@ class TestPacketFormatConversion:
assert result["route"] == "D"
assert result["path"] == "aa,bb"
def test_adds_path_for_multi_byte_direct_route(self):
data = {"timestamp": 0, "data": "024220273031CC", "snr": 1.0, "rssi": -70}
result = _format_raw_packet(data, "Node", "AA" * 32)
assert result["route"] == "D"
assert result["path"] == "2027,3031"
def test_direct_route_includes_empty_path_field(self):
data = {"timestamp": 0, "data": "0200", "snr": 1.0, "rssi": -70}
result = _format_raw_packet(data, "Node", "AA" * 32)
@@ -359,6 +365,30 @@ class TestCalculatePacketHash:
expected = hashlib.sha256(bytes([2]) + payload).hexdigest()[:16].upper()
assert result == expected
def test_multi_byte_path_uses_hop_count_for_trace_hash(self):
import hashlib
payload = b"\x99\x88"
raw = bytes([0x25, 0x42, 0x20, 0x27, 0x30, 0x31]) + payload
result = _calculate_packet_hash(raw)
expected = (
hashlib.sha256(bytes([9]) + (2).to_bytes(2, byteorder="little") + payload)
.hexdigest()[:16]
.upper()
)
assert result == expected
def test_multi_byte_path_skips_full_byte_length(self):
import hashlib
payload = b"\xde\xad\xbe\xef"
raw = bytes([0x09, 0x42, 0x20, 0x27, 0x30, 0x31]) + payload
result = _calculate_packet_hash(raw)
expected = hashlib.sha256(bytes([2]) + payload).hexdigest()[:16].upper()
assert result == expected
def test_truncated_packet_returns_zeroes(self):
# Header says TRANSPORT_FLOOD, but missing path_len at required offset.
raw = bytes([0x10, 0x01, 0x02])
+35
View File
@@ -214,6 +214,22 @@ class TestAdvertPaths:
assert data[0]["path"] == ""
assert data[0]["next_hop"] is None
@pytest.mark.asyncio
async def test_get_contact_advert_paths_with_multi_byte_hops(self, test_db, client):
repeater_key = KEY_A
await _insert_contact(repeater_key, "R1", type=2)
await ContactAdvertPathRepository.record_observation(
repeater_key, "a1b2c3d4", 1000, path_len=2
)
response = await client.get(f"/api/contacts/{repeater_key}/advert-paths")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["path_len"] == 2
assert data[0]["next_hop"] == "a1b2"
@pytest.mark.asyncio
async def test_get_contact_advert_paths_works_for_non_repeater(self, test_db, client):
await _insert_contact(KEY_A, "Alice", type=1)
@@ -326,6 +342,25 @@ class TestContactDetail:
assert repeater["name"] == "Relay1"
assert repeater["heard_count"] == 2
@pytest.mark.asyncio
async def test_detail_nearest_repeaters_resolved_for_multi_byte_hops(self, test_db, client):
await _insert_contact(KEY_A, "Alice", type=1)
repeater_key = "b1c2" + "dd" * 30
await _insert_contact(repeater_key, "RelayWide", type=2)
await ContactAdvertPathRepository.record_observation(KEY_A, "b1c2eeff", 1000, path_len=2)
await ContactAdvertPathRepository.record_observation(KEY_A, "b1c21122", 1010, path_len=2)
response = await client.get(f"/api/contacts/{KEY_A}/detail")
assert response.status_code == 200
data = response.json()
assert len(data["nearest_repeaters"]) == 1
repeater = data["nearest_repeaters"][0]
assert repeater["public_key"] == repeater_key
assert repeater["name"] == "RelayWide"
assert repeater["heard_count"] == 2
@pytest.mark.asyncio
async def test_detail_advert_frequency_computed(self, test_db, client):
"""Advert frequency is computed from path observations over time span."""
+15 -1
View File
@@ -3,7 +3,13 @@
import aiosqlite
import pytest
from app.migrations import get_version, run_migrations, set_version
from app.migrations import (
_extract_path_from_packet,
_extract_payload_for_hash,
get_version,
run_migrations,
set_version,
)
class TestMigrationSystem:
@@ -30,6 +36,14 @@ class TestMigrationSystem:
finally:
await conn.close()
def test_extract_payload_for_hash_handles_multi_byte_hops(self):
raw = bytes([0x15, 0x42, 0x20, 0x27, 0x30, 0x31]) + b"\xde\xad\xbe\xef"
assert _extract_payload_for_hash(raw) == b"\xde\xad\xbe\xef"
def test_extract_path_from_packet_handles_multi_byte_hops(self):
raw = bytes([0x15, 0x42, 0x20, 0x27, 0x30, 0x31]) + b"\xde\xad\xbe\xef"
assert _extract_path_from_packet(raw) == "20273031"
class TestMigration001:
"""Test migration 001: add last_read_at columns."""
+14
View File
@@ -274,6 +274,20 @@ class TestContactAdvertPathRepository:
assert paths[0].last_seen == 1010
assert paths[0].heard_count == 2
@pytest.mark.asyncio
async def test_record_observation_preserves_multi_byte_next_hop(self, test_db):
repeater_key = "ab" * 32
await ContactRepository.upsert({"public_key": repeater_key, "name": "R3", "type": 2})
await ContactAdvertPathRepository.record_observation(
repeater_key, "a1b2c3d4", 1000, path_len=2
)
paths = await ContactAdvertPathRepository.get_recent_for_contact(repeater_key, limit=10)
assert len(paths) == 1
assert paths[0].path_len == 2
assert paths[0].next_hop == "a1b2"
@pytest.mark.asyncio
async def test_prunes_to_most_recent_n_unique_paths(self, test_db):
repeater_key = "bb" * 32