|
6 | 6 | import platform |
7 | 7 | import re |
8 | 8 | import sys |
| 9 | +import tempfile |
9 | 10 | from unittest.mock import mock_open, MagicMock, patch |
10 | 11 |
|
11 | 12 | import pytest |
@@ -2900,3 +2901,68 @@ def test_main_set_ham_empty_string(capsys): |
2900 | 2901 | out, _ = capsys.readouterr() |
2901 | 2902 | assert "ERROR: Ham radio callsign cannot be empty or contain only whitespace characters" in out |
2902 | 2903 | 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) |
0 commit comments