fix(wifi): public check_internet_connectivity(); absolute systemctl path; stricter mode assertion

wifi_manager.py:
- Add public check_internet_connectivity() wrapping the private method so the
  daemon does not reach into the private API

wifi_monitor_daemon.py:
- Call wifi_manager.check_internet_connectivity() instead of the private
  _check_internet_connectivity()
- Use /usr/bin/systemctl (absolute path) instead of bare "systemctl"
- Wrap NM restart in try/except with check=True; only reset
  _consecutive_internet_failures on success — on CalledProcessError or other
  exception, log the error and leave the counter unchanged so the next cycle
  retries

test/test_wifi_manager_ap.py:
- Replace loose `assert "ap" in add_calls[0]` (list-membership check that
  could be satisfied by any element equal to "ap") with an explicit key/value
  check: locate "802-11-wireless.mode" in the command list and assert the next
  element is exactly "ap"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-05-03 08:22:13 -04:00
parent baebe4f5f7
commit 44316d3bae
3 changed files with 26 additions and 6 deletions

View File

@@ -132,7 +132,7 @@ class WiFiMonitorDaemon:
# AP-enable trigger clean and avoid false-positive AP enables from
# transient packet loss on otherwise working WiFi.
if updated_status.connected and not updated_status.ap_mode_active:
if not self.wifi_manager._check_internet_connectivity():
if not self.wifi_manager.check_internet_connectivity():
self._consecutive_internet_failures += 1
logger.warning(
f"Internet unreachable despite nmcli connection "
@@ -140,10 +140,18 @@ class WiFiMonitorDaemon:
)
if self._consecutive_internet_failures >= self._nm_restart_threshold:
logger.warning("Restarting NetworkManager to recover internet connectivity")
import subprocess as _sp
_sp.run(["sudo", "systemctl", "restart", "NetworkManager"],
capture_output=True, timeout=20)
try:
subprocess.run(
["/usr/bin/systemctl", "restart", "NetworkManager"],
capture_output=True, timeout=20, check=True
)
self._consecutive_internet_failures = 0
except subprocess.CalledProcessError as e:
logger.error(f"NetworkManager restart failed (rc={e.returncode}); "
"keeping failure counter unchanged")
except Exception as e:
logger.error(f"NetworkManager restart error: {e}; "
"keeping failure counter unchanged")
else:
self._consecutive_internet_failures = 0
else:

View File

@@ -948,6 +948,10 @@ class WiFiManager:
logger.debug("Internet connectivity check failed (both ping and HTTP)")
return False
def check_internet_connectivity(self, timeout: int = 5) -> bool:
"""Public wrapper around _check_internet_connectivity for use by the daemon."""
return self._check_internet_connectivity(timeout=timeout)
def _has_ap_clients(self) -> bool:
"""
Return True if at least one client is associated with the AP.

View File

@@ -129,7 +129,15 @@ def test_nmcli_ap_profile_has_no_security_params(manager: WiFiManager) -> None:
assert "psk" not in add_str, "AP profile must not include a PSK/password"
assert "wpa" not in add_str.lower(), "AP profile must not reference WPA"
assert "802-11-wireless.mode" in add_str, "AP profile must declare wireless mode"
assert "ap" in add_calls[0], "Wireless mode value must be 'ap'"
# Verify the value for 802-11-wireless.mode is exactly "ap" — check the element
# that immediately follows the key in the command list, not a loose substring match.
cmd = add_calls[0]
try:
mode_idx = cmd.index("802-11-wireless.mode")
assert cmd[mode_idx + 1] == "ap", \
f"802-11-wireless.mode value must be exactly 'ap', got {cmd[mode_idx + 1]!r}"
except ValueError:
pytest.fail("802-11-wireless.mode not found as a list element in nmcli command")
# ---------------------------------------------------------------------------