You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
1005 lines
35 KiB
Python
1005 lines
35 KiB
Python
import unittest
|
|
import sys
|
|
|
|
# Add tests directory to path for network_test_helper
|
|
sys.path.insert(0, "../tests")
|
|
|
|
# Import network test helpers
|
|
from network_test_helper import MockNetwork, MockTime
|
|
|
|
from mocks import HotspotMockNetwork, MockMpos, MockSharedPreferences
|
|
|
|
# Inject mocks before importing WifiService
|
|
sys.modules["mpos"] = MockMpos
|
|
sys.modules["mpos.config"] = MockMpos.config
|
|
sys.modules["mpos.time"] = MockMpos.time
|
|
|
|
# Add path to wifi_service.py
|
|
sys.path.append("lib/mpos/net")
|
|
|
|
# Import WifiService
|
|
from wifi_service import WifiService
|
|
|
|
|
|
class TestWifiServiceConnect(unittest.TestCase):
|
|
"""Test WifiService.connect() method."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
MockSharedPreferences.reset_all()
|
|
WifiService.access_points = {}
|
|
WifiService.wifi_busy = False
|
|
|
|
def tearDown(self):
|
|
"""Clean up after test."""
|
|
WifiService.access_points = {}
|
|
WifiService.wifi_busy = False
|
|
|
|
def test_connect_to_saved_network(self):
|
|
"""Test connecting to a saved network."""
|
|
mock_network = MockNetwork(connected=False)
|
|
WifiService.access_points = {
|
|
"TestNetwork": {"password": "testpass123"}
|
|
}
|
|
|
|
# Configure mock scan results
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
mock_wlan._scan_results = [(b"TestNetwork", -50, 1, 3, b"", 0)]
|
|
|
|
# Mock connect to succeed immediately
|
|
def mock_connect(ssid, password):
|
|
mock_wlan._connected = True
|
|
|
|
mock_wlan.connect = mock_connect
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertTrue(result)
|
|
|
|
def test_connect_with_no_saved_networks(self):
|
|
"""Test connecting when no networks are saved."""
|
|
mock_network = MockNetwork(connected=False)
|
|
WifiService.access_points = {}
|
|
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
mock_wlan._scan_results = [(b"UnsavedNetwork", -50, 1, 3, b"", 0)]
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_connect_when_no_saved_networks_available(self):
|
|
"""Test connecting when saved networks aren't in range."""
|
|
mock_network = MockNetwork(connected=False)
|
|
WifiService.access_points = {
|
|
"SavedNetwork": {"password": "password123"}
|
|
}
|
|
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
mock_wlan._scan_results = [(b"DifferentNetwork", -50, 1, 3, b"", 0)]
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertFalse(result)
|
|
|
|
|
|
class TestWifiServiceAttemptConnecting(unittest.TestCase):
|
|
"""Test WifiService.attempt_connecting() method."""
|
|
|
|
def test_successful_connection(self):
|
|
"""Test successful WiFi connection."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_time = MockTime()
|
|
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Mock connect to succeed immediately
|
|
call_count = [0]
|
|
|
|
def mock_connect(ssid, password):
|
|
pass # Don't set connected yet
|
|
|
|
def mock_isconnected():
|
|
call_count[0] += 1
|
|
if call_count[0] >= 1:
|
|
return True
|
|
return False
|
|
|
|
mock_wlan.connect = mock_connect
|
|
mock_wlan.isconnected = mock_isconnected
|
|
|
|
result = WifiService.attempt_connecting(
|
|
"TestSSID",
|
|
"testpass",
|
|
network_module=mock_network,
|
|
time_module=mock_time
|
|
)
|
|
|
|
self.assertTrue(result)
|
|
# Should not sleep once connected immediately
|
|
self.assertEqual(len(mock_time.get_sleep_calls()), 0)
|
|
|
|
def test_connection_timeout(self):
|
|
"""Test connection timeout after 10 attempts."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_time = MockTime()
|
|
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Connection never succeeds
|
|
def mock_isconnected():
|
|
return False
|
|
|
|
mock_wlan.isconnected = mock_isconnected
|
|
|
|
result = WifiService.attempt_connecting(
|
|
"TestSSID",
|
|
"testpass",
|
|
network_module=mock_network,
|
|
time_module=mock_time
|
|
)
|
|
|
|
self.assertFalse(result)
|
|
# Should have slept 10 times
|
|
self.assertEqual(len(mock_time.get_sleep_calls()), 10)
|
|
|
|
def test_connection_aborted_when_wifi_disabled(self):
|
|
"""Test connection aborts if WiFi is disabled during attempt."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_time = MockTime()
|
|
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Never connected
|
|
def mock_isconnected():
|
|
return False
|
|
|
|
# WiFi becomes inactive on 3rd check
|
|
check_count = [0]
|
|
|
|
def mock_active(state=None):
|
|
if state is not None:
|
|
mock_wlan._active = state
|
|
return None
|
|
check_count[0] += 1
|
|
if check_count[0] >= 3:
|
|
return False
|
|
return True
|
|
|
|
mock_wlan.isconnected = mock_isconnected
|
|
mock_wlan.active = mock_active
|
|
|
|
result = WifiService.attempt_connecting(
|
|
"TestSSID",
|
|
"testpass",
|
|
network_module=mock_network,
|
|
time_module=mock_time
|
|
)
|
|
|
|
self.assertFalse(result)
|
|
# Should have checked less than 10 times (aborted early)
|
|
self.assertTrue(check_count[0] < 10)
|
|
# Should have slept only until abort
|
|
self.assertEqual(len(mock_time.get_sleep_calls()), 2)
|
|
|
|
def test_connection_error_handling(self):
|
|
"""Test handling of connection errors."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_time = MockTime()
|
|
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
def raise_error(ssid, password):
|
|
raise Exception("Connection failed")
|
|
|
|
mock_wlan.connect = raise_error
|
|
|
|
result = WifiService.attempt_connecting(
|
|
"TestSSID",
|
|
"testpass",
|
|
network_module=mock_network,
|
|
time_module=mock_time
|
|
)
|
|
|
|
self.assertFalse(result)
|
|
|
|
|
|
class TestWifiServiceAutoConnect(unittest.TestCase):
|
|
"""Test WifiService.auto_connect() method."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
MockSharedPreferences.reset_all()
|
|
WifiService.access_points = {}
|
|
WifiService.wifi_busy = False
|
|
|
|
def tearDown(self):
|
|
"""Clean up after test."""
|
|
WifiService.access_points = {}
|
|
WifiService.wifi_busy = False
|
|
MockSharedPreferences.reset_all()
|
|
|
|
def test_auto_connect_with_no_saved_networks(self):
|
|
"""Test auto_connect when no networks are saved."""
|
|
WifiService.auto_connect()
|
|
|
|
# Should exit early
|
|
self.assertEqual(len(WifiService.access_points), 0)
|
|
|
|
def test_auto_connect_when_wifi_busy(self):
|
|
"""Test auto_connect aborts when WiFi is busy."""
|
|
# Save a network
|
|
prefs = MockSharedPreferences("com.micropythonos.system.wifiservice")
|
|
editor = prefs.edit()
|
|
editor.put_dict("access_points", {"TestNet": {"password": "pass"}})
|
|
editor.commit()
|
|
|
|
# Set WiFi as busy
|
|
WifiService.wifi_busy = True
|
|
|
|
WifiService.auto_connect()
|
|
|
|
# Should still be busy (not changed)
|
|
self.assertTrue(WifiService.wifi_busy)
|
|
|
|
def test_auto_connect_desktop_mode(self):
|
|
"""Test auto_connect in desktop mode (no network module)."""
|
|
mock_time = MockTime()
|
|
|
|
# Save a network
|
|
prefs = MockSharedPreferences("com.micropythonos.system.wifiservice")
|
|
editor = prefs.edit()
|
|
editor.put_dict("access_points", {"TestNet": {"password": "pass"}})
|
|
editor.commit()
|
|
|
|
WifiService.auto_connect(network_module=None, time_module=mock_time)
|
|
|
|
# Should have "slept" to simulate connection
|
|
self.assertTrue(len(mock_time.get_sleep_calls()) > 0)
|
|
# Should clear wifi_busy flag
|
|
self.assertFalse(WifiService.wifi_busy)
|
|
|
|
|
|
class TestWifiServiceIsConnected(unittest.TestCase):
|
|
"""Test WifiService.is_connected() method."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
WifiService.wifi_busy = False
|
|
|
|
def tearDown(self):
|
|
"""Clean up after test."""
|
|
WifiService.wifi_busy = False
|
|
|
|
def test_is_connected_when_connected(self):
|
|
"""Test is_connected returns True when WiFi is connected."""
|
|
mock_network = MockNetwork(connected=True)
|
|
|
|
result = WifiService.is_connected(network_module=mock_network)
|
|
|
|
self.assertTrue(result)
|
|
|
|
def test_is_connected_when_disconnected(self):
|
|
"""Test is_connected returns False when WiFi is disconnected."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
ap_wlan.active(False)
|
|
|
|
result = WifiService.is_connected(network_module=mock_network)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_is_connected_when_wifi_busy(self):
|
|
"""Test is_connected returns False when WiFi is busy."""
|
|
mock_network = MockNetwork(connected=True)
|
|
WifiService.wifi_busy = True
|
|
|
|
result = WifiService.is_connected(network_module=mock_network)
|
|
|
|
# Should return False even though connected
|
|
self.assertFalse(result)
|
|
|
|
def test_is_connected_when_hotspot_enabled(self):
|
|
"""Test is_connected checks AP state when hotspot is enabled."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
ap_wlan.active(True)
|
|
WifiService.hotspot_enabled = True
|
|
|
|
result = WifiService.is_connected(network_module=mock_network)
|
|
|
|
self.assertTrue(result)
|
|
|
|
def test_is_connected_desktop_mode(self):
|
|
"""Test is_connected in desktop mode."""
|
|
result = WifiService.is_connected(network_module=None)
|
|
|
|
# Desktop mode always returns True
|
|
self.assertTrue(result)
|
|
|
|
|
|
class TestWifiServiceNetworkManagement(unittest.TestCase):
|
|
"""Test network save/forget functionality."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
MockSharedPreferences.reset_all()
|
|
WifiService.access_points = {}
|
|
|
|
def tearDown(self):
|
|
"""Clean up after test."""
|
|
WifiService.access_points = {}
|
|
MockSharedPreferences.reset_all()
|
|
|
|
def test_save_network(self):
|
|
"""Test saving a network."""
|
|
WifiService.save_network("MyNetwork", "mypassword123")
|
|
|
|
# Should be in class-level cache
|
|
self.assertTrue("MyNetwork" in WifiService.access_points)
|
|
self.assertEqual(WifiService.access_points["MyNetwork"]["password"], "mypassword123")
|
|
|
|
# Should be persisted
|
|
prefs = MockSharedPreferences("com.micropythonos.system.wifiservice")
|
|
saved = prefs.get_dict("access_points")
|
|
self.assertTrue("MyNetwork" in saved)
|
|
|
|
def test_save_network_updates_existing(self):
|
|
"""Test updating an existing saved network."""
|
|
WifiService.save_network("MyNetwork", "oldpassword")
|
|
WifiService.save_network("MyNetwork", "newpassword")
|
|
|
|
# Should have new password
|
|
self.assertEqual(WifiService.access_points["MyNetwork"]["password"], "newpassword")
|
|
|
|
def test_forget_network(self):
|
|
"""Test forgetting a saved network."""
|
|
WifiService.save_network("MyNetwork", "mypassword")
|
|
|
|
result = WifiService.forget_network("MyNetwork")
|
|
|
|
self.assertTrue(result)
|
|
self.assertFalse("MyNetwork" in WifiService.access_points)
|
|
|
|
def test_forget_nonexistent_network(self):
|
|
"""Test forgetting a network that doesn't exist."""
|
|
result = WifiService.forget_network("NonExistent")
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_get_saved_networks(self):
|
|
"""Test getting list of saved networks."""
|
|
WifiService.save_network("Network1", "pass1")
|
|
WifiService.save_network("Network2", "pass2")
|
|
WifiService.save_network("Network3", "pass3")
|
|
|
|
saved = WifiService.get_saved_networks()
|
|
|
|
self.assertEqual(len(saved), 3)
|
|
self.assertTrue("Network1" in saved)
|
|
self.assertTrue("Network2" in saved)
|
|
self.assertTrue("Network3" in saved)
|
|
|
|
def test_get_saved_networks_empty(self):
|
|
"""Test getting saved networks when none exist."""
|
|
saved = WifiService.get_saved_networks()
|
|
|
|
self.assertEqual(len(saved), 0)
|
|
|
|
|
|
class TestWifiServiceHotspot(unittest.TestCase):
|
|
"""Test hotspot configuration and mode switching."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
MockSharedPreferences.reset_all()
|
|
WifiService.hotspot_enabled = False
|
|
WifiService.wifi_busy = False
|
|
WifiService._needs_hotspot_restore = False
|
|
|
|
def tearDown(self):
|
|
"""Clean up after test."""
|
|
WifiService.hotspot_enabled = False
|
|
WifiService.wifi_busy = False
|
|
WifiService._needs_hotspot_restore = False
|
|
MockSharedPreferences.reset_all()
|
|
|
|
def test_enable_hotspot_applies_config(self):
|
|
"""Test enable_hotspot reads config and configures AP."""
|
|
prefs = MockSharedPreferences("com.micropythonos.settings.hotspot")
|
|
editor = prefs.edit()
|
|
editor.put_bool("enabled", True)
|
|
editor.put_string("ssid", "MyAP")
|
|
editor.put_string("password", "ap-pass")
|
|
editor.put_string("authmode", "wpa2")
|
|
editor.commit()
|
|
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
sta_wlan.active(True)
|
|
sta_wlan._connected = True
|
|
|
|
result = WifiService.enable_hotspot(network_module=mock_network)
|
|
|
|
self.assertTrue(result)
|
|
self.assertTrue(WifiService.hotspot_enabled)
|
|
self.assertTrue(ap_wlan.active())
|
|
self.assertFalse(sta_wlan.active())
|
|
self.assertEqual(ap_wlan._config.get("essid"), "MyAP")
|
|
self.assertEqual(ap_wlan._config.get("authmode"), mock_network.AUTH_WPA2_PSK)
|
|
self.assertEqual(ap_wlan._config.get("password"), "ap-pass")
|
|
|
|
def test_enable_hotspot_respects_busy_flag(self):
|
|
"""Test enable_hotspot returns False when WiFi is busy."""
|
|
WifiService.wifi_busy = True
|
|
mock_network = HotspotMockNetwork()
|
|
|
|
result = WifiService.enable_hotspot(network_module=mock_network)
|
|
|
|
self.assertFalse(result)
|
|
self.assertFalse(WifiService.hotspot_enabled)
|
|
|
|
def test_disable_hotspot_deactivates_ap(self):
|
|
"""Test disable_hotspot turns off AP and updates flag."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
ap_wlan.active(True)
|
|
WifiService.hotspot_enabled = True
|
|
|
|
WifiService.disable_hotspot(network_module=mock_network)
|
|
|
|
self.assertFalse(ap_wlan.active())
|
|
self.assertFalse(WifiService.hotspot_enabled)
|
|
|
|
def test_enable_hotspot_desktop_mode(self):
|
|
"""Test enable_hotspot in desktop mode uses simulated flag."""
|
|
result = WifiService.enable_hotspot(network_module=None)
|
|
|
|
self.assertTrue(result)
|
|
self.assertTrue(WifiService.hotspot_enabled)
|
|
|
|
def test_disable_hotspot_desktop_mode(self):
|
|
"""Test disable_hotspot in desktop mode uses simulated flag."""
|
|
WifiService.hotspot_enabled = True
|
|
|
|
WifiService.disable_hotspot(network_module=None)
|
|
|
|
self.assertFalse(WifiService.hotspot_enabled)
|
|
|
|
def test_auto_connect_with_hotspot_enabled_prefers_ap_mode(self):
|
|
"""Test auto_connect uses hotspot mode when enabled in config."""
|
|
prefs = MockSharedPreferences("com.micropythonos.settings.hotspot")
|
|
editor = prefs.edit()
|
|
editor.put_bool("enabled", True)
|
|
editor.commit()
|
|
|
|
mock_network = HotspotMockNetwork()
|
|
|
|
WifiService.auto_connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
self.assertTrue(ap_wlan.active())
|
|
self.assertTrue(WifiService.hotspot_enabled)
|
|
|
|
def test_attempt_connecting_temporarily_disables_hotspot(self):
|
|
"""Test STA connect disables hotspot and leaves it off on success."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
ap_wlan.active(True)
|
|
WifiService.hotspot_enabled = True
|
|
|
|
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
call_count = [0]
|
|
|
|
def mock_isconnected():
|
|
call_count[0] += 1
|
|
return call_count[0] >= 1
|
|
|
|
sta_wlan.isconnected = mock_isconnected
|
|
|
|
result = WifiService.attempt_connecting(
|
|
"TestSSID",
|
|
"pass",
|
|
network_module=mock_network,
|
|
time_module=MockTime(),
|
|
)
|
|
|
|
self.assertTrue(result)
|
|
self.assertFalse(WifiService.hotspot_enabled)
|
|
self.assertFalse(ap_wlan.active())
|
|
|
|
def test_attempt_connecting_restores_hotspot_on_timeout(self):
|
|
"""Test STA connect restores hotspot when connection times out."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
ap_wlan.active(True)
|
|
WifiService.hotspot_enabled = True
|
|
|
|
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
def mock_isconnected():
|
|
return False
|
|
|
|
sta_wlan.isconnected = mock_isconnected
|
|
|
|
result = WifiService.attempt_connecting(
|
|
"TestSSID",
|
|
"pass",
|
|
network_module=mock_network,
|
|
time_module=MockTime(),
|
|
)
|
|
|
|
self.assertFalse(result)
|
|
self.assertTrue(WifiService.hotspot_enabled)
|
|
self.assertTrue(ap_wlan.active())
|
|
|
|
def test_attempt_connecting_restores_hotspot_on_abort(self):
|
|
"""Test STA connect restores hotspot if WiFi is disabled mid-try."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
ap_wlan.active(True)
|
|
WifiService.hotspot_enabled = True
|
|
|
|
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
def mock_isconnected():
|
|
return False
|
|
|
|
def mock_active(state=None):
|
|
if state is not None:
|
|
sta_wlan._active = state
|
|
return None
|
|
return False
|
|
|
|
sta_wlan.isconnected = mock_isconnected
|
|
sta_wlan.active = mock_active
|
|
|
|
result = WifiService.attempt_connecting(
|
|
"TestSSID",
|
|
"pass",
|
|
network_module=mock_network,
|
|
time_module=MockTime(),
|
|
)
|
|
|
|
self.assertFalse(result)
|
|
self.assertTrue(WifiService.hotspot_enabled)
|
|
self.assertTrue(ap_wlan.active())
|
|
|
|
|
|
class TestWifiServiceTemporaryDisable(unittest.TestCase):
|
|
"""Test temporarily_disable/temporarily_enable behavior."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
WifiService.wifi_busy = False
|
|
WifiService._temp_disable_state = None
|
|
WifiService.hotspot_enabled = False
|
|
|
|
def tearDown(self):
|
|
"""Clean up after test."""
|
|
WifiService.wifi_busy = False
|
|
WifiService._temp_disable_state = None
|
|
WifiService.hotspot_enabled = False
|
|
|
|
def test_temporarily_disable_raises_when_busy(self):
|
|
"""Test temporarily_disable raises if wifi_busy is set."""
|
|
WifiService.wifi_busy = True
|
|
|
|
with self.assertRaises(RuntimeError):
|
|
WifiService.temporarily_disable(network_module=HotspotMockNetwork())
|
|
|
|
def test_temporarily_disable_disconnects_and_tracks_state(self):
|
|
"""Test temporarily_disable stores state and disconnects."""
|
|
mock_network = HotspotMockNetwork()
|
|
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
sta_wlan._connected = True
|
|
ap_wlan.active(True)
|
|
WifiService.hotspot_enabled = True
|
|
|
|
disconnect_called = [False]
|
|
|
|
def mock_disconnect(network_module=None):
|
|
disconnect_called[0] = True
|
|
|
|
original_disconnect = WifiService.disconnect
|
|
WifiService.disconnect = mock_disconnect
|
|
try:
|
|
was_connected = WifiService.temporarily_disable(network_module=mock_network)
|
|
finally:
|
|
WifiService.disconnect = original_disconnect
|
|
|
|
self.assertTrue(was_connected)
|
|
self.assertTrue(WifiService.wifi_busy)
|
|
self.assertEqual(
|
|
WifiService._temp_disable_state,
|
|
{"was_connected": True, "hotspot_was_enabled": True},
|
|
)
|
|
self.assertTrue(disconnect_called[0])
|
|
|
|
def test_temporarily_enable_restores_hotspot_and_reconnects(self):
|
|
"""Test temporarily_enable restores hotspot and triggers reconnect."""
|
|
mock_network = HotspotMockNetwork()
|
|
WifiService._temp_disable_state = {"was_connected": True, "hotspot_was_enabled": True}
|
|
WifiService.wifi_busy = True
|
|
|
|
thread_calls = []
|
|
|
|
class MockThreadModule:
|
|
@staticmethod
|
|
def start_new_thread(func, args):
|
|
thread_calls.append((func, args))
|
|
|
|
original_thread = sys.modules.get("_thread")
|
|
sys.modules["_thread"] = MockThreadModule
|
|
|
|
try:
|
|
WifiService.temporarily_enable(True, network_module=mock_network)
|
|
finally:
|
|
if original_thread is not None:
|
|
sys.modules["_thread"] = original_thread
|
|
else:
|
|
sys.modules.pop("_thread", None)
|
|
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
self.assertFalse(WifiService.wifi_busy)
|
|
self.assertIsNone(WifiService._temp_disable_state)
|
|
self.assertTrue(ap_wlan.active())
|
|
self.assertTrue(WifiService.hotspot_enabled)
|
|
self.assertEqual(thread_calls[0][0], WifiService.auto_connect)
|
|
|
|
|
|
class TestWifiServiceIPv4Info(unittest.TestCase):
|
|
"""Test IPv4 info accessors for AP/STA modes."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
WifiService.wifi_busy = False
|
|
WifiService.hotspot_enabled = False
|
|
|
|
def tearDown(self):
|
|
"""Clean up after test."""
|
|
WifiService.wifi_busy = False
|
|
WifiService.hotspot_enabled = False
|
|
|
|
def test_get_ipv4_info_from_ap_when_hotspot_enabled(self):
|
|
"""Test IPv4 getters use AP info when hotspot is enabled."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
ap_wlan.active(True)
|
|
ap_wlan.ifconfig(("192.168.4.1", "255.255.255.0", "192.168.4.1", "8.8.4.4"))
|
|
WifiService.hotspot_enabled = True
|
|
|
|
address = WifiService.get_ipv4_address(network_module=mock_network)
|
|
gateway = WifiService.get_ipv4_gateway(network_module=mock_network)
|
|
|
|
self.assertEqual(address, "192.168.4.1")
|
|
self.assertEqual(gateway, "192.168.4.1")
|
|
|
|
def test_get_ipv4_info_returns_none_when_busy(self):
|
|
"""Test IPv4 getters return None when wifi_busy is set."""
|
|
WifiService.wifi_busy = True
|
|
|
|
address = WifiService.get_ipv4_address(network_module=HotspotMockNetwork())
|
|
gateway = WifiService.get_ipv4_gateway(network_module=HotspotMockNetwork())
|
|
|
|
self.assertIsNone(address)
|
|
self.assertIsNone(gateway)
|
|
|
|
def test_get_ipv4_info_desktop_mode(self):
|
|
"""Test IPv4 getters return simulated values in desktop mode."""
|
|
address = WifiService.get_ipv4_address(network_module=None)
|
|
gateway = WifiService.get_ipv4_gateway(network_module=None)
|
|
|
|
self.assertEqual(address, "127.0.0.1")
|
|
self.assertEqual(gateway, "")
|
|
|
|
|
|
class TestWifiServiceDisconnect(unittest.TestCase):
|
|
"""Test WifiService.disconnect() method."""
|
|
|
|
def test_disconnect(self):
|
|
"""Test disconnecting from WiFi."""
|
|
mock_network = MockNetwork(connected=True)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Track calls
|
|
disconnect_called = [False]
|
|
active_false_called = [False]
|
|
|
|
def mock_disconnect():
|
|
disconnect_called[0] = True
|
|
|
|
def mock_active(state=None):
|
|
if state is False:
|
|
active_false_called[0] = True
|
|
return True if state is None else None
|
|
|
|
mock_wlan.disconnect = mock_disconnect
|
|
mock_wlan.active = mock_active
|
|
|
|
WifiService.disconnect(network_module=mock_network)
|
|
|
|
# Should have called both
|
|
self.assertTrue(disconnect_called[0])
|
|
self.assertTrue(active_false_called[0])
|
|
|
|
def test_disconnect_disables_ap(self):
|
|
"""Test disconnect also disables AP and clears hotspot flag."""
|
|
mock_network = HotspotMockNetwork()
|
|
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
|
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
ap_wlan.active(True)
|
|
sta_wlan._connected = True
|
|
|
|
WifiService.hotspot_enabled = True
|
|
|
|
WifiService.disconnect(network_module=mock_network)
|
|
|
|
self.assertFalse(ap_wlan.active())
|
|
self.assertFalse(WifiService.hotspot_enabled)
|
|
|
|
def test_disconnect_desktop_mode(self):
|
|
"""Test disconnect in desktop mode."""
|
|
# Should not raise an error
|
|
WifiService.disconnect(network_module=None)
|
|
|
|
|
|
class TestWifiServiceRSSISorting(unittest.TestCase):
|
|
"""Test RSSI-based network prioritization."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
MockSharedPreferences.reset_all()
|
|
WifiService.access_points = {}
|
|
WifiService.wifi_busy = False
|
|
|
|
def tearDown(self):
|
|
"""Clean up after tests."""
|
|
WifiService.access_points = {}
|
|
WifiService.wifi_busy = False
|
|
MockSharedPreferences.reset_all()
|
|
|
|
def test_networks_sorted_by_rssi_strongest_first(self):
|
|
"""Test that networks are sorted by RSSI with strongest first."""
|
|
# Create mock networks with different RSSI values
|
|
# Format: (ssid, bssid, channel, rssi, security, hidden)
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Unsorted networks (weak, strong, medium)
|
|
mock_wlan._scan_results = [
|
|
(b'WeakNetwork', b'\xaa\xbb\xcc\xdd\xee\xff', 6, -85, 3, False),
|
|
(b'StrongNetwork', b'\x11\x22\x33\x44\x55\x66', 1, -45, 3, False),
|
|
(b'MediumNetwork', b'\x77\x88\x99\xaa\xbb\xcc', 11, -65, 3, False),
|
|
]
|
|
|
|
# Configure all as saved networks
|
|
WifiService.access_points = {
|
|
'WeakNetwork': {'password': 'weak123'},
|
|
'StrongNetwork': {'password': 'strong123'},
|
|
'MediumNetwork': {'password': 'medium123'}
|
|
}
|
|
|
|
# Track connection attempts
|
|
connection_attempts = []
|
|
|
|
def mock_connect(ssid, password):
|
|
connection_attempts.append(ssid)
|
|
# Succeed on first attempt
|
|
mock_wlan._connected = True
|
|
|
|
mock_wlan.connect = mock_connect
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertTrue(result)
|
|
# Should try strongest first (-45 dBm)
|
|
self.assertEqual(connection_attempts[0], 'StrongNetwork')
|
|
# Should only try one (first succeeds)
|
|
self.assertEqual(len(connection_attempts), 1)
|
|
|
|
def test_multiple_networks_tried_in_rssi_order(self):
|
|
"""Test that multiple networks are tried in RSSI order when first fails."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Three networks with different signal strengths
|
|
mock_wlan._scan_results = [
|
|
(b'BadNetwork1', b'\xaa\xbb\xcc\xdd\xee\xff', 1, -40, 3, False),
|
|
(b'BadNetwork2', b'\x11\x22\x33\x44\x55\x66', 6, -50, 3, False),
|
|
(b'GoodNetwork', b'\x77\x88\x99\xaa\xbb\xcc', 11, -60, 3, False),
|
|
]
|
|
|
|
WifiService.access_points = {
|
|
'BadNetwork1': {'password': 'pass1'},
|
|
'BadNetwork2': {'password': 'pass2'},
|
|
'GoodNetwork': {'password': 'pass3'}
|
|
}
|
|
|
|
# Track attempts and make first two fail
|
|
connection_attempts = []
|
|
|
|
def mock_connect(ssid, password):
|
|
connection_attempts.append(ssid)
|
|
# Only succeed on third attempt
|
|
if len(connection_attempts) >= 3:
|
|
mock_wlan._connected = True
|
|
|
|
mock_wlan.connect = mock_connect
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertTrue(result)
|
|
# Verify order: strongest to weakest
|
|
self.assertEqual(connection_attempts[0], 'BadNetwork1') # RSSI -40
|
|
self.assertEqual(connection_attempts[1], 'BadNetwork2') # RSSI -50
|
|
self.assertEqual(connection_attempts[2], 'GoodNetwork') # RSSI -60
|
|
self.assertEqual(len(connection_attempts), 3)
|
|
|
|
def test_duplicate_ssid_strongest_tried_first(self):
|
|
"""Test that with duplicate SSIDs, strongest signal is tried first."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Real-world scenario: Multiple APs with same SSID
|
|
mock_wlan._scan_results = [
|
|
(b'MyNetwork', b'\xaa\xbb\xcc\xdd\xee\xff', 1, -70, 3, False),
|
|
(b'MyNetwork', b'\x11\x22\x33\x44\x55\x66', 6, -50, 3, False), # Strongest
|
|
(b'MyNetwork', b'\x77\x88\x99\xaa\xbb\xcc', 11, -85, 3, False),
|
|
]
|
|
|
|
WifiService.access_points = {
|
|
'MyNetwork': {'password': 'mypass123'}
|
|
}
|
|
|
|
connection_attempts = []
|
|
|
|
def mock_connect(ssid, password):
|
|
connection_attempts.append(ssid)
|
|
# Succeed on first
|
|
mock_wlan._connected = True
|
|
|
|
mock_wlan.connect = mock_connect
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertTrue(result)
|
|
# Should only try once (first is strongest and succeeds)
|
|
self.assertEqual(len(connection_attempts), 1)
|
|
self.assertEqual(connection_attempts[0], 'MyNetwork')
|
|
|
|
def test_rssi_order_with_real_scan_data(self):
|
|
"""Test with real scan data from actual ESP32 device."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Real scan output from user's example
|
|
mock_wlan._scan_results = [
|
|
(b'Channel 8', b'\xde\xec^\x8f\x00A', 11, -47, 3, False),
|
|
(b'Baptistus', b'\xd8\xec^\x8f\x00A', 11, -48, 7, False),
|
|
(b'telenet-BD74DC9', b'TgQ>t\xe7', 11, -70, 3, False),
|
|
(b'Galaxy S10+64bf', b'b\x19\xdf\xef\xb0\x8f', 11, -83, 3, False),
|
|
(b'Najeeb\xe2\x80\x99s iPhone', b"F\x07'\xb8\x0b0", 6, -84, 7, False),
|
|
(b'DIRECT-83-HP OfficeJet Pro 7740', b'\x1a`$dk\x83', 1, -87, 3, False),
|
|
(b'Channel 8', b'\xde\xec^\xe1#w', 1, -91, 3, False),
|
|
(b'Baptistus', b'\xd8\xec^\xe1#w', 1, -91, 7, False),
|
|
(b'Proximus-Home-596457', b'\xf4\x05\x95\xf9A\xf1', 1, -93, 3, False),
|
|
(b'Proximus-Home-596457', b'\xcc\x00\xf1j}\x94', 1, -93, 3, False),
|
|
(b'BASE-9104320', b'4,\xc4\xe7\x01\xb7', 1, -94, 3, False),
|
|
]
|
|
|
|
# Save several networks
|
|
WifiService.access_points = {
|
|
'Channel 8': {'password': 'pass1'},
|
|
'Baptistus': {'password': 'pass2'},
|
|
'telenet-BD74DC9': {'password': 'pass3'},
|
|
'Galaxy S10+64bf': {'password': 'pass4'},
|
|
}
|
|
|
|
# Track attempts and fail first to see ordering
|
|
connection_attempts = []
|
|
|
|
def mock_connect(ssid, password):
|
|
connection_attempts.append(ssid)
|
|
# Succeed on second attempt
|
|
if len(connection_attempts) >= 2:
|
|
mock_wlan._connected = True
|
|
|
|
mock_wlan.connect = mock_connect
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertTrue(result)
|
|
# Expected order: Channel 8 (-47), Baptistus (-48), telenet (-70), Galaxy (-83)
|
|
self.assertEqual(connection_attempts[0], 'Channel 8')
|
|
self.assertEqual(connection_attempts[1], 'Baptistus')
|
|
self.assertEqual(len(connection_attempts), 2)
|
|
|
|
def test_sorting_preserves_network_data_integrity(self):
|
|
"""Test that sorting doesn't corrupt or lose network data."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
# Networks with various attributes
|
|
mock_wlan._scan_results = [
|
|
(b'Net3', b'\xaa\xaa\xaa\xaa\xaa\xaa', 11, -90, 3, False),
|
|
(b'Net1', b'\xbb\xbb\xbb\xbb\xbb\xbb', 1, -40, 7, True), # Hidden
|
|
(b'Net2', b'\xcc\xcc\xcc\xcc\xcc\xcc', 6, -60, 2, False),
|
|
]
|
|
|
|
WifiService.access_points = {
|
|
'Net1': {'password': 'p1'},
|
|
'Net2': {'password': 'p2'},
|
|
'Net3': {'password': 'p3'}
|
|
}
|
|
|
|
# Track attempts to verify all are tried
|
|
connection_attempts = []
|
|
|
|
def mock_connect(ssid, password):
|
|
connection_attempts.append(ssid)
|
|
# Never succeed, try all
|
|
pass
|
|
|
|
mock_wlan.connect = mock_connect
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertFalse(result) # No connection succeeded
|
|
# Verify all 3 were attempted in RSSI order
|
|
self.assertEqual(len(connection_attempts), 3)
|
|
self.assertEqual(connection_attempts[0], 'Net1') # RSSI -40
|
|
self.assertEqual(connection_attempts[1], 'Net2') # RSSI -60
|
|
self.assertEqual(connection_attempts[2], 'Net3') # RSSI -90
|
|
|
|
def test_no_saved_networks_in_scan(self):
|
|
"""Test behavior when scan finds no saved networks."""
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
mock_wlan._scan_results = [
|
|
(b'UnknownNet1', b'\xaa\xbb\xcc\xdd\xee\xff', 1, -50, 3, False),
|
|
(b'UnknownNet2', b'\x11\x22\x33\x44\x55\x66', 6, -60, 3, False),
|
|
]
|
|
|
|
WifiService.access_points = {
|
|
'SavedNetwork': {'password': 'pass123'}
|
|
}
|
|
|
|
connection_attempts = []
|
|
|
|
def mock_connect(ssid, password):
|
|
connection_attempts.append(ssid)
|
|
|
|
mock_wlan.connect = mock_connect
|
|
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
|
|
self.assertFalse(result)
|
|
# No attempts should be made
|
|
self.assertEqual(len(connection_attempts), 0)
|
|
|
|
def test_rssi_logging_shows_signal_strength(self):
|
|
"""Test that RSSI value is logged during scan (for debugging)."""
|
|
# This is more of a documentation test to verify the log format
|
|
mock_network = MockNetwork(connected=False)
|
|
mock_wlan = mock_network.WLAN(mock_network.STA_IF)
|
|
|
|
mock_wlan._scan_results = [
|
|
(b'TestNet', b'\xaa\xbb\xcc\xdd\xee\xff', 1, -55, 3, False),
|
|
]
|
|
|
|
WifiService.access_points = {
|
|
'TestNet': {'password': 'pass'}
|
|
}
|
|
|
|
# The connect method now logs "Found network 'TestNet' (RSSI: -55 dBm)"
|
|
# This test just verifies it doesn't crash
|
|
result = WifiService.connect(network_module=mock_network, time_module=MockTime())
|
|
# Since mock doesn't actually connect, this will likely be False
|
|
# but the important part is the code runs without error
|
|
|
|
|