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 import asyncio
from unittest.mock import MagicMock, AsyncMock from unittest.mock import MagicMock, AsyncMock
from meshcore.commands import CommandHandler from meshcore.commands import CommandHandler
from meshcore.events import EventType, Event from meshcore.events import EventType, Event, Subscription
pytestmark = pytest.mark.asyncio pytestmark = pytest.mark.asyncio
VALID_PUBKEY_HEX = "0123456789abcdef" * 4 # 64 hex chars = 32 bytes
# Fixtures # Fixtures
@pytest.fixture @pytest.fixture
@@ -20,6 +22,15 @@ def mock_dispatcher():
dispatcher = MagicMock() dispatcher = MagicMock()
dispatcher.wait_for_event = AsyncMock() dispatcher.wait_for_event = AsyncMock()
dispatcher.dispatch = 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 return dispatcher
@@ -36,20 +47,17 @@ def command_handler(mock_connection, mock_dispatcher):
return handler return handler
# Test helper
def setup_event_response(mock_dispatcher, event_type, payload, attribute_filters=None): def setup_event_response(mock_dispatcher, event_type, payload, attribute_filters=None):
async def wait_response(requested_type, filters=None, timeout=None): def fake_subscribe(evt_type, handler, attr_filters=None):
if requested_type == event_type: sub = MagicMock(spec=Subscription)
if filters and attribute_filters: sub.unsubscribe = MagicMock()
if not all( if evt_type == event_type:
attribute_filters.get(key) == value asyncio.get_event_loop().call_soon(
for key, value in filters.items() handler, Event(event_type, payload)
): )
return None return sub
return Event(event_type, payload)
return None
mock_dispatcher.wait_for_event.side_effect = wait_response mock_dispatcher.subscribe = MagicMock(side_effect=fake_subscribe)
# Basic tests # 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): 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) result = await command_handler.send(b"test_command", [EventType.OK], timeout=0.1)
assert result.type == EventType.ERROR assert result.type == EventType.ERROR
assert result.payload == {"reason": "timeout"} assert result.payload == {"reason": "no_event_received"}
# Destination validation tests # Destination validation tests
@@ -106,7 +112,7 @@ async def test_validate_destination_contact_object(command_handler, mock_connect
# Command tests # Command tests
async def test_send_login(command_handler, mock_connection): 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 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] 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): async def test_reset_path(command_handler, mock_connection):
dst = "0123456789abcdef" command_handler._get_contact_by_prefix = lambda prefix: None
await command_handler.reset_path(dst) await command_handler.reset_path(VALID_PUBKEY_HEX)
assert mock_connection.send.call_args[0][0].startswith(b"\x0d") 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] 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): async def test_share_contact(command_handler, mock_connection):
dst = "0123456789abcdef" await command_handler.share_contact(VALID_PUBKEY_HEX)
await command_handler.share_contact(dst)
assert mock_connection.send.call_args[0][0].startswith(b"\x10") 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] 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 # Test exporting specific contact
mock_connection.reset_mock() mock_connection.reset_mock()
dst = "0123456789abcdef" await command_handler.export_contact(VALID_PUBKEY_HEX)
await command_handler.export_contact(dst)
assert mock_connection.send.call_args[0][0].startswith(b"\x11") 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] 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): async def test_remove_contact(command_handler, mock_connection):
dst = "0123456789abcdef" await command_handler.remove_contact(VALID_PUBKEY_HEX)
await command_handler.remove_contact(dst)
assert mock_connection.send.call_args[0][0].startswith(b"\x0f") 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] 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): async def test_send_logout(command_handler, mock_connection):
dst = "0123456789abcdef" await command_handler.send_logout(VALID_PUBKEY_HEX)
await command_handler.send_logout(dst)
assert mock_connection.send.call_args[0][0].startswith(b"\x1d") 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] 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): async def test_send_statusreq(command_handler, mock_connection):
dst = "0123456789abcdef" await command_handler.send_statusreq(VALID_PUBKEY_HEX)
await command_handler.send_statusreq(dst)
assert mock_connection.send.call_args[0][0].startswith(b"\x1b") 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] 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] first_call = mock_connection.send.call_args[0][0]
assert first_call.startswith(b"\x24") # 36 in decimal = 0x24 in hex 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() mock_connection.reset_mock()
await command_handler.send_trace( 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] second_call = mock_connection.send.call_args[0][0]
assert second_call.startswith(b"\x24") 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( async def test_send_with_multiple_expected_events_returns_first_completed(
command_handler, mock_connection, mock_dispatcher command_handler, mock_connection, mock_dispatcher
): ):
# Setup the dispatcher to return an ERROR event
error_payload = {"reason": "command_failed"} 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( result = await command_handler.send(
b"test_command", [EventType.OK, EventType.ERROR] b"test_command", [EventType.OK, EventType.ERROR]
) )
# Verify the command was sent
mock_connection.send.assert_called_once_with(b"test_command") 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.type == EventType.ERROR
assert result.payload == error_payload 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 import asyncio
from unittest.mock import MagicMock, AsyncMock from unittest.mock import MagicMock, AsyncMock
from meshcore.commands import CommandHandler from meshcore.commands import CommandHandler
from meshcore.events import Event, EventType from meshcore.events import Event, EventType, Subscription
from meshcore.reader import MessageReader from meshcore.reader import MessageReader
pytestmark = pytest.mark.asyncio pytestmark = pytest.mark.asyncio
# Fixtures (consistent with existing test patterns)
@pytest.fixture @pytest.fixture
def mock_connection(): def mock_connection():
connection = MagicMock() connection = MagicMock()
@@ -26,6 +25,13 @@ def mock_dispatcher():
dispatcher = MagicMock() dispatcher = MagicMock()
dispatcher.wait_for_event = AsyncMock() dispatcher.wait_for_event = AsyncMock()
dispatcher.dispatch = 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 return dispatcher
@@ -41,14 +47,17 @@ def command_handler(mock_connection, mock_dispatcher):
return handler return handler
# Test helper (consistent with existing patterns)
def setup_event_response(mock_dispatcher, event_type, payload): def setup_event_response(mock_dispatcher, event_type, payload):
async def wait_response(requested_type, filters=None, timeout=None): def fake_subscribe(evt_type, handler, attr_filters=None):
if requested_type == event_type: sub = MagicMock(spec=Subscription)
return Event(event_type, payload) sub.unsubscribe = MagicMock()
return None 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 # Command tests

View File

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