Skip to content

Commit 4de19f5

Browse files
committed
fix: add tests
1 parent 4d8430d commit 4de19f5

File tree

3 files changed

+559
-0
lines changed

3 files changed

+559
-0
lines changed

meshtastic/tests/test_main.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import platform
77
import re
88
import sys
9+
import tempfile
910
from unittest.mock import mock_open, MagicMock, patch
1011

1112
import pytest
@@ -2900,3 +2901,68 @@ def test_main_set_ham_empty_string(capsys):
29002901
out, _ = capsys.readouterr()
29012902
assert "ERROR: Ham radio callsign cannot be empty or contain only whitespace characters" in out
29022903
assert excinfo.value.code == 1
2904+
2905+
2906+
# OTA-related tests
2907+
@pytest.mark.unit
2908+
@pytest.mark.usefixtures("reset_mt_config")
2909+
def test_main_ota_update_file_not_found(capsys):
2910+
"""Test --ota-update with non-existent file"""
2911+
sys.argv = [
2912+
"",
2913+
"--ota-update",
2914+
"/nonexistent/firmware.bin",
2915+
"--host",
2916+
"192.168.1.100",
2917+
]
2918+
mt_config.args = sys.argv
2919+
2920+
with pytest.raises(SystemExit) as pytest_wrapped_e:
2921+
main()
2922+
2923+
assert pytest_wrapped_e.type == SystemExit
2924+
assert pytest_wrapped_e.value.code == 1
2925+
2926+
2927+
@pytest.mark.unit
2928+
@pytest.mark.usefixtures("reset_mt_config")
2929+
@patch("meshtastic.ota.ESP32WiFiOTA")
2930+
@patch("meshtastic.__main__.meshtastic.util.our_exit")
2931+
def test_main_ota_update_retries(mock_our_exit, mock_ota_class, capsys):
2932+
"""Test --ota-update retries on failure"""
2933+
# Create a temporary firmware file
2934+
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
2935+
f.write(b"fake firmware data")
2936+
firmware_file = f.name
2937+
2938+
try:
2939+
sys.argv = ["", "--ota-update", firmware_file, "--host", "192.168.1.100"]
2940+
mt_config.args = sys.argv
2941+
2942+
# Mock the OTA class to fail all 5 retries
2943+
mock_ota = MagicMock()
2944+
mock_ota_class.return_value = mock_ota
2945+
mock_ota.hash_bytes.return_value = b"\x00" * 32
2946+
mock_ota.hash_hex.return_value = "a" * 64
2947+
mock_ota.update.side_effect = Exception("Connection failed")
2948+
2949+
# Mock isinstance to return True
2950+
with patch("meshtastic.__main__.isinstance", return_value=True):
2951+
with patch("meshtastic.tcp_interface.TCPInterface") as mock_tcp:
2952+
mock_iface = MagicMock()
2953+
mock_iface.hostname = "192.168.1.100"
2954+
mock_iface.localNode = MagicMock(autospec=Node)
2955+
mock_tcp.return_value = mock_iface
2956+
2957+
with patch("time.sleep"):
2958+
main()
2959+
2960+
# Should have exhausted all retries and called our_exit
2961+
# Note: our_exit might be called twice - once for TCP check, once for failure
2962+
assert mock_our_exit.call_count >= 1
2963+
# Check the last call was for OTA failure
2964+
last_call_args = mock_our_exit.call_args[0][0]
2965+
assert "OTA update failed" in last_call_args
2966+
2967+
finally:
2968+
os.unlink(firmware_file)

meshtastic/tests/test_node.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,41 @@ def test_setOwner_valid_names(caplog):
15501550
assert re.search(r'p.set_owner.short_name:VN:', caplog.text, re.MULTILINE)
15511551

15521552

1553+
@pytest.mark.unit
1554+
def test_start_ota_local_node():
1555+
"""Test startOTA on local node"""
1556+
iface = MagicMock(autospec=MeshInterface)
1557+
anode = Node(iface, 1234567890, noProto=True)
1558+
# Set up as local node
1559+
iface.localNode = anode
1560+
1561+
amesg = admin_pb2.AdminMessage()
1562+
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
1563+
with patch.object(anode, "_sendAdmin") as mock_send_admin:
1564+
test_hash = b"\x01\x02\x03" * 8 # 24 bytes hash
1565+
anode.startOTA(ota_mode=admin_pb2.OTAMode.OTA_WIFI, ota_file_hash=test_hash)
1566+
1567+
# Verify the OTA request was set correctly
1568+
assert amesg.ota_request.reboot_ota_mode == admin_pb2.OTAMode.OTA_WIFI
1569+
assert amesg.ota_request.ota_hash == test_hash
1570+
mock_send_admin.assert_called_once_with(amesg)
1571+
1572+
1573+
@pytest.mark.unit
1574+
def test_start_ota_remote_node_raises_error():
1575+
"""Test startOTA on remote node raises ValueError"""
1576+
iface = MagicMock(autospec=MeshInterface)
1577+
local_node = Node(iface, 1234567890, noProto=True)
1578+
remote_node = Node(iface, 9876543210, noProto=True)
1579+
iface.localNode = local_node
1580+
1581+
test_hash = b"\x01\x02\x03" * 8
1582+
with pytest.raises(ValueError, match="startOTA only possible in local node"):
1583+
remote_node.startOTA(
1584+
ota_mode=admin_pb2.OTAMode.OTA_WIFI, ota_file_hash=test_hash
1585+
)
1586+
1587+
15531588
# TODO
15541589
# @pytest.mark.unitslow
15551590
# def test_waitForConfig():

0 commit comments

Comments
 (0)