mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-06 01:42:11 +02:00
Step 2
This commit is contained in:
@@ -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])
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user