mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 14:11:54 -07:00
feat(meshcore): add data model, connection config, MeshcoreClient skeleton
Implements utils/meshcore.py with all dataclasses (MeshcoreMessage, MeshcoreNode, MeshcoreContact, MeshcoreTelemetry, MeshcoreTraceroute), connection configs (SerialConfig, TCPConfig, BLEConfig), ConnectionState enum, serial port discovery, and the MeshcoreClient singleton skeleton. Adds tests/test_meshcore_client.py covering all dataclasses, availability check, and state enum (8/8 tests passing). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Tests for MeshcoreClient dataclasses and state machine."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestAvailability:
|
||||
def test_returns_bool(self):
|
||||
from utils.meshcore import is_meshcore_available
|
||||
|
||||
assert isinstance(is_meshcore_available(), bool)
|
||||
|
||||
def test_false_when_not_installed(self):
|
||||
with patch.dict("sys.modules", {"meshcore": None}):
|
||||
import importlib
|
||||
|
||||
import utils.meshcore as m
|
||||
|
||||
importlib.reload(m)
|
||||
assert m.is_meshcore_available() is False
|
||||
|
||||
|
||||
class TestMeshcoreMessage:
|
||||
def _make(self, **kw):
|
||||
from utils.meshcore import MeshcoreMessage
|
||||
|
||||
defaults = {
|
||||
"id": "abc123",
|
||||
"sender_id": "NODE001",
|
||||
"recipient_id": "BROADCAST",
|
||||
"text": "hello mesh",
|
||||
"timestamp": datetime(2026, 5, 10, 12, 0, 0, tzinfo=timezone.utc),
|
||||
"hop_count": 2,
|
||||
"snr": -8.5,
|
||||
"is_direct": False,
|
||||
}
|
||||
defaults.update(kw)
|
||||
return MeshcoreMessage(**defaults)
|
||||
|
||||
def test_to_dict_keys(self):
|
||||
d = self._make().to_dict()
|
||||
for key in ("id", "sender_id", "recipient_id", "text", "timestamp", "hop_count", "snr", "is_direct", "pending"):
|
||||
assert key in d, f"missing key: {key}"
|
||||
|
||||
def test_pending_defaults_false(self):
|
||||
assert self._make().to_dict()["pending"] is False
|
||||
|
||||
def test_none_snr_allowed(self):
|
||||
d = self._make(snr=None).to_dict()
|
||||
assert d["snr"] is None
|
||||
|
||||
|
||||
class TestMeshcoreNode:
|
||||
def test_to_dict_includes_is_repeater(self):
|
||||
from utils.meshcore import MeshcoreNode
|
||||
|
||||
node = MeshcoreNode(
|
||||
node_id="RPT1",
|
||||
name="Roof-Repeater",
|
||||
is_repeater=True,
|
||||
lat=51.5,
|
||||
lon=-0.1,
|
||||
battery_pct=87,
|
||||
last_seen=datetime.now(timezone.utc),
|
||||
snr=-5.0,
|
||||
hops_away=1,
|
||||
)
|
||||
d = node.to_dict()
|
||||
assert d["is_repeater"] is True
|
||||
assert d["node_id"] == "RPT1"
|
||||
|
||||
|
||||
class TestMeshcoreTelemetry:
|
||||
def test_to_dict_timestamp_is_iso(self):
|
||||
from utils.meshcore import MeshcoreTelemetry
|
||||
|
||||
t = MeshcoreTelemetry(
|
||||
node_id="N1",
|
||||
timestamp=datetime(2026, 5, 10, tzinfo=timezone.utc),
|
||||
battery_pct=72,
|
||||
voltage=3.7,
|
||||
temperature=22.1,
|
||||
humidity=55.0,
|
||||
uptime_secs=3600,
|
||||
)
|
||||
d = t.to_dict()
|
||||
assert "2026-05-10" in d["timestamp"]
|
||||
|
||||
|
||||
class TestConnectionState:
|
||||
def test_state_enum_values(self):
|
||||
from utils.meshcore import ConnectionState
|
||||
|
||||
assert ConnectionState.DISCONNECTED
|
||||
assert ConnectionState.CONNECTING
|
||||
assert ConnectionState.CONNECTED
|
||||
assert ConnectionState.ERROR
|
||||
Reference in New Issue
Block a user