mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-05-07 05:44:53 +02:00
15023b8d4a
This commit adds the complete Interface component for MeshCore device communication: Device abstraction (interface/device.py): - BaseMeshCoreDevice abstract class - MeshCoreDevice for real hardware (placeholder for meshcore_py) - DeviceConfig for connection settings - EventType enumeration for all MeshCore events - Event handler registration and dispatching Mock device (interface/mock_device.py): - MockMeshCoreDevice for testing without hardware - Configurable event generation - Simulated network with multiple mock nodes - Support for injecting custom events RECEIVER mode (interface/receiver.py): - Subscribes to device events - Publishes events to MQTT broker - Signal handling for graceful shutdown SENDER mode (interface/sender.py): - Subscribes to MQTT command topics - Dispatches commands to MeshCore device - Handles send_msg, send_channel_msg, send_advert, etc. CLI (interface/cli.py): - Click commands for running interface - Convenience commands for receiver/sender modes - Environment variable support for all options Tests: - Device abstraction tests - Mock device tests - Receiver and sender mode tests
207 lines
6.1 KiB
Python
207 lines
6.1 KiB
Python
"""Tests for mock device implementation."""
|
|
|
|
import pytest
|
|
import time
|
|
import threading
|
|
|
|
from meshcore_hub.interface.device import EventType
|
|
from meshcore_hub.interface.mock_device import (
|
|
MockDeviceConfig,
|
|
MockMeshCoreDevice,
|
|
MockNodeConfig,
|
|
generate_random_public_key,
|
|
)
|
|
|
|
|
|
class TestGenerateRandomPublicKey:
|
|
"""Tests for public key generation."""
|
|
|
|
def test_generates_64_char_hex(self) -> None:
|
|
"""Test that public key is 64 hex characters."""
|
|
key = generate_random_public_key()
|
|
|
|
assert len(key) == 64
|
|
assert all(c in "0123456789abcdef" for c in key)
|
|
|
|
def test_generates_unique_keys(self) -> None:
|
|
"""Test that generated keys are unique."""
|
|
keys = [generate_random_public_key() for _ in range(100)]
|
|
|
|
assert len(set(keys)) == 100
|
|
|
|
|
|
class TestMockDeviceConfig:
|
|
"""Tests for MockDeviceConfig."""
|
|
|
|
def test_default_values(self) -> None:
|
|
"""Test default configuration values."""
|
|
config = MockDeviceConfig()
|
|
|
|
assert config.public_key is None
|
|
assert config.name == "MockNode"
|
|
assert config.enable_auto_events is True
|
|
assert config.advertisement_interval == 30.0
|
|
assert config.message_interval == 10.0
|
|
|
|
def test_custom_values(self) -> None:
|
|
"""Test custom configuration values."""
|
|
config = MockDeviceConfig(
|
|
public_key="a" * 64,
|
|
name="CustomNode",
|
|
enable_auto_events=False,
|
|
)
|
|
|
|
assert config.public_key == "a" * 64
|
|
assert config.name == "CustomNode"
|
|
assert config.enable_auto_events is False
|
|
|
|
|
|
class TestMockMeshCoreDevice:
|
|
"""Tests for MockMeshCoreDevice."""
|
|
|
|
def test_connection(self, mock_device) -> None:
|
|
"""Test device connection."""
|
|
assert not mock_device.is_connected
|
|
|
|
result = mock_device.connect()
|
|
|
|
assert result is True
|
|
assert mock_device.is_connected
|
|
|
|
def test_public_key(self, mock_device) -> None:
|
|
"""Test public key assignment."""
|
|
assert mock_device.public_key == "a" * 64
|
|
|
|
def test_disconnect(self, mock_device) -> None:
|
|
"""Test device disconnection."""
|
|
mock_device.connect()
|
|
assert mock_device.is_connected
|
|
|
|
mock_device.disconnect()
|
|
|
|
assert not mock_device.is_connected
|
|
|
|
def test_send_message(self, mock_device) -> None:
|
|
"""Test sending a message."""
|
|
mock_device.connect()
|
|
|
|
result = mock_device.send_message(
|
|
destination="b" * 64,
|
|
text="Hello!",
|
|
)
|
|
|
|
assert result is True
|
|
|
|
def test_send_message_not_connected(self, mock_device) -> None:
|
|
"""Test sending message when not connected."""
|
|
result = mock_device.send_message(
|
|
destination="b" * 64,
|
|
text="Hello!",
|
|
)
|
|
|
|
assert result is False
|
|
|
|
def test_send_channel_message(self, mock_device) -> None:
|
|
"""Test sending a channel message."""
|
|
mock_device.connect()
|
|
|
|
result = mock_device.send_channel_message(
|
|
channel_idx=4,
|
|
text="Channel message",
|
|
)
|
|
|
|
assert result is True
|
|
|
|
def test_send_advertisement(self, mock_device) -> None:
|
|
"""Test sending an advertisement."""
|
|
mock_device.connect()
|
|
|
|
result = mock_device.send_advertisement(flood=True)
|
|
|
|
assert result is True
|
|
|
|
def test_request_status(self, mock_device) -> None:
|
|
"""Test requesting status."""
|
|
mock_device.connect()
|
|
|
|
result = mock_device.request_status()
|
|
|
|
assert result is True
|
|
|
|
def test_request_telemetry(self, mock_device) -> None:
|
|
"""Test requesting telemetry."""
|
|
mock_device.connect()
|
|
|
|
result = mock_device.request_telemetry(target="c" * 64)
|
|
|
|
assert result is True
|
|
|
|
def test_event_handler_registration(self, mock_device) -> None:
|
|
"""Test event handler registration."""
|
|
events_received = []
|
|
|
|
def handler(event_type, payload):
|
|
events_received.append((event_type, payload))
|
|
|
|
mock_device.register_handler(EventType.ADVERTISEMENT, handler)
|
|
mock_device.connect()
|
|
|
|
# Inject an event
|
|
mock_device.inject_event(
|
|
EventType.ADVERTISEMENT,
|
|
{"public_key": "d" * 64, "name": "TestNode"},
|
|
)
|
|
|
|
# Give time for event processing
|
|
time.sleep(0.1)
|
|
|
|
assert len(events_received) >= 1
|
|
event_type, payload = events_received[-1]
|
|
assert event_type == EventType.ADVERTISEMENT
|
|
assert payload["name"] == "TestNode"
|
|
|
|
def test_event_handler_unregistration(self, mock_device) -> None:
|
|
"""Test event handler unregistration."""
|
|
events_received = []
|
|
|
|
def handler(event_type, payload):
|
|
events_received.append((event_type, payload))
|
|
|
|
mock_device.register_handler(EventType.ADVERTISEMENT, handler)
|
|
mock_device.unregister_handler(EventType.ADVERTISEMENT, handler)
|
|
|
|
mock_device.connect()
|
|
mock_device.inject_event(
|
|
EventType.ADVERTISEMENT,
|
|
{"public_key": "d" * 64, "name": "TestNode"},
|
|
)
|
|
|
|
time.sleep(0.1)
|
|
|
|
# Should only have the status event from connect(), not the advertisement
|
|
advert_events = [e for e in events_received if e[0] == EventType.ADVERTISEMENT]
|
|
assert len(advert_events) == 0
|
|
|
|
def test_default_nodes_created(self, device_config) -> None:
|
|
"""Test that default nodes are created when none provided."""
|
|
device = MockMeshCoreDevice(device_config)
|
|
|
|
assert len(device.mock_config.nodes) > 0
|
|
assert any(n.adv_type == "chat" for n in device.mock_config.nodes)
|
|
assert any(n.adv_type == "repeater" for n in device.mock_config.nodes)
|
|
|
|
def test_custom_nodes(self, device_config) -> None:
|
|
"""Test custom node configuration."""
|
|
custom_nodes = [
|
|
MockNodeConfig(
|
|
public_key="e" * 64,
|
|
name="CustomAlice",
|
|
adv_type="chat",
|
|
),
|
|
]
|
|
config = MockDeviceConfig(nodes=custom_nodes)
|
|
device = MockMeshCoreDevice(device_config, config)
|
|
|
|
assert len(device.mock_config.nodes) == 1
|
|
assert device.mock_config.nodes[0].name == "CustomAlice"
|