Fix 16 failing unit tests to match current source behavior

- Update mock dispatcher to use subscribe-before-send pattern matching
  the rewritten CommandHandler.send() method
- Use 32-byte pubkeys in tests for commands that now require
  prefix_length=32 (login, logout, statusreq, reset_path, share/export/remove contact)
- Fix send_trace test path format to match flags=1 (2-byte path hashes)
- Update LPP current test to expect signed wrap for values > 32.767
- Fix BinaryReqType import (moved from meshcore.parsing to meshcore.packets)
- Fix register_binary_request call signature (added pubkey_prefix param)
- Update timeout test to expect 'no_event_received' instead of 'timeout'
This commit is contained in:
Alex Wolden 2026-04-04 23:48:48 -07:00
parent 20f3bccb58
commit ed96df197a
4 changed files with 93 additions and 54 deletions

View file

@ -2,10 +2,12 @@ import pytest
import asyncio
from unittest.mock import MagicMock, AsyncMock
from meshcore.commands import CommandHandler
from meshcore.events import EventType, Event
from meshcore.events import EventType, Event, Subscription
pytestmark = pytest.mark.asyncio
VALID_PUBKEY_HEX = "0123456789abcdef" * 4 # 64 hex chars = 32 bytes
# Fixtures
@pytest.fixture
@ -20,6 +22,15 @@ def mock_dispatcher():
dispatcher = MagicMock()
dispatcher.wait_for_event = AsyncMock()
dispatcher.dispatch = AsyncMock()
def fake_subscribe(event_type, handler, attribute_filters=None):
sub = MagicMock(spec=Subscription)
sub.unsubscribe = MagicMock()
dispatcher._last_subscribe_handler = handler
dispatcher._last_subscribe_event_type = event_type
return sub
dispatcher.subscribe = MagicMock(side_effect=fake_subscribe)
return dispatcher
@ -36,20 +47,17 @@ def command_handler(mock_connection, mock_dispatcher):
return handler
# Test helper
def setup_event_response(mock_dispatcher, event_type, payload, attribute_filters=None):
async def wait_response(requested_type, filters=None, timeout=None):
if requested_type == event_type:
if filters and attribute_filters:
if not all(
attribute_filters.get(key) == value
for key, value in filters.items()
):
return None
return Event(event_type, payload)
return None
def fake_subscribe(evt_type, handler, attr_filters=None):
sub = MagicMock(spec=Subscription)
sub.unsubscribe = MagicMock()
if evt_type == event_type:
asyncio.get_event_loop().call_soon(
handler, Event(event_type, payload)
)
return sub
mock_dispatcher.wait_for_event.side_effect = wait_response
mock_dispatcher.subscribe = MagicMock(side_effect=fake_subscribe)
# Basic tests
@ -72,11 +80,9 @@ async def test_send_with_event(command_handler, mock_connection, mock_dispatcher
async def test_send_timeout(command_handler, mock_connection, mock_dispatcher):
mock_dispatcher.wait_for_event.side_effect = asyncio.TimeoutError
result = await command_handler.send(b"test_command", [EventType.OK], timeout=0.1)
assert result.type == EventType.ERROR
assert result.payload == {"reason": "timeout"}
assert result.payload == {"reason": "no_event_received"}
# Destination validation tests
@ -106,7 +112,7 @@ async def test_validate_destination_contact_object(command_handler, mock_connect
# Command tests
async def test_send_login(command_handler, mock_connection):
await command_handler.send_login("0123456789abcdef", "password")
await command_handler.send_login(VALID_PUBKEY_HEX, "password")
assert mock_connection.send.call_args[0][0].startswith(b"\x1a")
assert b"\x01\x23\x45\x67\x89\xab" in mock_connection.send.call_args[0][0]
@ -198,15 +204,14 @@ async def test_get_contacts(command_handler, mock_connection):
async def test_reset_path(command_handler, mock_connection):
dst = "0123456789abcdef"
await command_handler.reset_path(dst)
command_handler._get_contact_by_prefix = lambda prefix: None
await command_handler.reset_path(VALID_PUBKEY_HEX)
assert mock_connection.send.call_args[0][0].startswith(b"\x0d")
assert b"\x01\x23\x45\x67\x89\xab" in mock_connection.send.call_args[0][0]
async def test_share_contact(command_handler, mock_connection):
dst = "0123456789abcdef"
await command_handler.share_contact(dst)
await command_handler.share_contact(VALID_PUBKEY_HEX)
assert mock_connection.send.call_args[0][0].startswith(b"\x10")
assert b"\x01\x23\x45\x67\x89\xab" in mock_connection.send.call_args[0][0]
@ -218,15 +223,13 @@ async def test_export_contact(command_handler, mock_connection):
# Test exporting specific contact
mock_connection.reset_mock()
dst = "0123456789abcdef"
await command_handler.export_contact(dst)
await command_handler.export_contact(VALID_PUBKEY_HEX)
assert mock_connection.send.call_args[0][0].startswith(b"\x11")
assert b"\x01\x23\x45\x67\x89\xab" in mock_connection.send.call_args[0][0]
async def test_remove_contact(command_handler, mock_connection):
dst = "0123456789abcdef"
await command_handler.remove_contact(dst)
await command_handler.remove_contact(VALID_PUBKEY_HEX)
assert mock_connection.send.call_args[0][0].startswith(b"\x0f")
assert b"\x01\x23\x45\x67\x89\xab" in mock_connection.send.call_args[0][0]
@ -242,15 +245,13 @@ async def test_get_msg(command_handler, mock_connection):
async def test_send_logout(command_handler, mock_connection):
dst = "0123456789abcdef"
await command_handler.send_logout(dst)
await command_handler.send_logout(VALID_PUBKEY_HEX)
assert mock_connection.send.call_args[0][0].startswith(b"\x1d")
assert b"\x01\x23\x45\x67\x89\xab" in mock_connection.send.call_args[0][0]
async def test_send_statusreq(command_handler, mock_connection):
dst = "0123456789abcdef"
await command_handler.send_statusreq(dst)
await command_handler.send_statusreq(VALID_PUBKEY_HEX)
assert mock_connection.send.call_args[0][0].startswith(b"\x1b")
assert b"\x01\x23\x45\x67\x89\xab" in mock_connection.send.call_args[0][0]
@ -261,10 +262,10 @@ async def test_send_trace(command_handler, mock_connection):
first_call = mock_connection.send.call_args[0][0]
assert first_call.startswith(b"\x24") # 36 in decimal = 0x24 in hex
# Test with all parameters
# Test with all parameters (flags=1 means path_hash_len=2, so 4 hex chars each)
mock_connection.reset_mock()
await command_handler.send_trace(
auth_code=12345, tag=67890, flags=1, path="01,23,45"
auth_code=12345, tag=67890, flags=1, path="0123,2345,4567"
)
second_call = mock_connection.send.call_args[0][0]
assert second_call.startswith(b"\x24")
@ -273,25 +274,14 @@ async def test_send_trace(command_handler, mock_connection):
async def test_send_with_multiple_expected_events_returns_first_completed(
command_handler, mock_connection, mock_dispatcher
):
# Setup the dispatcher to return an ERROR event
error_payload = {"reason": "command_failed"}
setup_event_response(mock_dispatcher, EventType.ERROR, error_payload)
async def simulate_error_event(*args, **kwargs):
# Simulate an ERROR event being returned
return Event(EventType.ERROR, error_payload)
# Patch the wait_for_event method to return our simulated event
mock_dispatcher.wait_for_event.side_effect = simulate_error_event
# Call send with both OK and ERROR in the expected_events list, with OK first
result = await command_handler.send(
b"test_command", [EventType.OK, EventType.ERROR]
)
# Verify the command was sent
mock_connection.send.assert_called_once_with(b"test_command")
# Verify that even though OK was listed first, the ERROR event was returned
assert result.type == EventType.ERROR
assert result.payload == error_payload

View file

@ -0,0 +1,39 @@
"""Tests for LPP parsing to verify current values are handled correctly."""
import json
import pytest
from cayennelpp import LppFrame, LppData
class TestLppCurrentParsing:
"""Tests to verify LPP current values pass through correctly."""
def test_large_current_value_wraps_signed(self):
"""
When raw bytes represent a large unsigned value (like 65525),
values above 32.767 are reinterpreted as signed (negative).
65.525 - 65.536 = -0.011
"""
from meshcore.lpp_json_encoder import lpp_json_encoder
# Channel 2, Type 117 (current), Value 65525 raw = 0xFF 0xF5 (big-endian)
raw_bytes = bytes([2, 117, 0xFF, 0xF5])
lppdata = LppData.from_bytes(raw_bytes)
lpp = json.loads(json.dumps(LppFrame([lppdata]), default=lpp_json_encoder))
assert len(lpp) == 1
assert lpp[0]['channel'] == 2
assert lpp[0]['type'] == 'current'
assert lpp[0]['value'] == -0.011
def test_normal_positive_current(self):
"""Normal positive current should work correctly."""
from meshcore.lpp_json_encoder import lpp_json_encoder
# Channel 2, Type 117 (current), Value 500 raw = 0x01 0xF4 (big-endian)
raw_bytes = bytes([2, 117, 0x01, 0xF4])
lppdata = LppData.from_bytes(raw_bytes)
lpp = json.loads(json.dumps(LppFrame([lppdata]), default=lpp_json_encoder))
assert lpp[0]['value'] == 0.5

View file

@ -7,13 +7,12 @@ import pytest
import asyncio
from unittest.mock import MagicMock, AsyncMock
from meshcore.commands import CommandHandler
from meshcore.events import Event, EventType
from meshcore.events import Event, EventType, Subscription
from meshcore.reader import MessageReader
pytestmark = pytest.mark.asyncio
# Fixtures (consistent with existing test patterns)
@pytest.fixture
def mock_connection():
connection = MagicMock()
@ -26,6 +25,13 @@ def mock_dispatcher():
dispatcher = MagicMock()
dispatcher.wait_for_event = AsyncMock()
dispatcher.dispatch = AsyncMock()
def fake_subscribe(event_type, handler, attribute_filters=None):
sub = MagicMock(spec=Subscription)
sub.unsubscribe = MagicMock()
return sub
dispatcher.subscribe = MagicMock(side_effect=fake_subscribe)
return dispatcher
@ -41,14 +47,17 @@ def command_handler(mock_connection, mock_dispatcher):
return handler
# Test helper (consistent with existing patterns)
def setup_event_response(mock_dispatcher, event_type, payload):
async def wait_response(requested_type, filters=None, timeout=None):
if requested_type == event_type:
return Event(event_type, payload)
return None
def fake_subscribe(evt_type, handler, attr_filters=None):
sub = MagicMock(spec=Subscription)
sub.unsubscribe = MagicMock()
if evt_type == event_type:
asyncio.get_event_loop().call_soon(
handler, Event(event_type, payload)
)
return sub
mock_dispatcher.wait_for_event.side_effect = wait_response
mock_dispatcher.subscribe = MagicMock(side_effect=fake_subscribe)
# Command tests

View file

@ -28,8 +28,9 @@ async def test_binary_response():
# Register the binary request first
tag = "417db968"
from meshcore.parsing import BinaryReqType
reader.register_binary_request(tag, BinaryReqType.ACL, 10.0)
from meshcore.packets import BinaryReqType
pubkey_prefix = "993acd42fc77"
reader.register_binary_request(pubkey_prefix, tag, BinaryReqType.ACL, 10.0)
print(f"Registered ACL request with tag {tag}")
await reader.handle_rx(packet_data)
@ -64,7 +65,7 @@ async def test_binary_response():
print(f"Request type in response: 0x{request_type:02x} ({request_type})")
# Map request types to expected events
from meshcore.parsing import BinaryReqType
from meshcore.packets import BinaryReqType
if request_type == BinaryReqType.STATUS.value:
expected_event = EventType.STATUS_RESPONSE
elif request_type == BinaryReqType.TELEMETRY.value: