fix: web UI and src.common bugs (wifi wrong-password, plugin icon, starlark toggle, API caching, scroll/logo/font helpers) (#646)

- wifi: keep the "wrong_password:" prefix through the restore/AP fallback so
  the UI's incorrect-password prompt fires again.
- /plugins/installed returns the manifest's icon (string only).
- /starlark/apps/<id>/toggle coerces `enabled` and delegates to
  _toggle_starlark_app (disk before memory, no KeyError, "false" is false).
- /api/v3/ JSON GETs are sent Cache-Control: no-store; non-JSON keeps 5s.
- ScrollHelper.set_scrolling_image converts non-RGB input (alpha onto black);
  create/set_scrolling_image reset last_update_time like reset_scroll.
- LogoHelper backs off a failed download per path for
  MISSING_LOGO_RECHECK_SECONDS; cleared on invalidate/clear_cache.
- refresh_placeholder_timestamp saves atomically.
- FontManager.clear_cache / _clear_plugin_font_cache bump cache_generation.
- Odds manager: per-game logs to DEBUG; JSON decode error caught before
  RequestException (same cooldown).
- element_style mangled continuations; startup validator skips null plugin
  blocks and reuses the controller's discovery.
- src/common/README lists frame_timing, json_body, render_gate.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-28 08:26:05 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent b518c51679
commit 7eb7a58d0c
27 changed files with 764 additions and 64 deletions
+78
View File
@@ -0,0 +1,78 @@
"""A rejected passphrase must reach the web UI as "wrong_password:".
_connect_nmcli prefixes the message with "wrong_password:" and the api_v3
wifi route turns that into ``error_type == 'wrong_password'``, which the
setup pages key their "incorrect password" prompt off. _connect_validated
used to replace the message on every failure branch (restore the old
network, or bring the setup AP up), so the prefix never survived.
"""
import sys
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.wifi_manager import WiFiManager # noqa: E402
def _manager(connected_ssid=None):
manager = WiFiManager.__new__(WiFiManager) # no __init__: no real host access
manager.has_nmcli = True
manager._wifi_interface = "wlan0"
manager.get_wifi_status = MagicMock(return_value=SimpleNamespace(
connected=bool(connected_ssid), ssid=connected_ssid, ip_address=None))
manager._is_ap_mode_active = MagicMock(return_value=False)
manager._ensure_wifi_radio_enabled = MagicMock(return_value=True)
manager._show_led_message = MagicMock()
manager.disconnect_from_network = MagicMock(return_value=(True, "ok"))
manager._wait_for_device_idle = MagicMock(return_value=True)
manager.enable_ap_mode = MagicMock(return_value=(True, "AP up"))
return manager
def _run(manager, nmcli_result):
manager._connect_nmcli = MagicMock(return_value=nmcli_result)
nmcli_out = SimpleNamespace(returncode=0, stdout="GENERAL.CONNECTION:OldNet\n")
with patch("src.wifi_manager.subprocess.run", return_value=nmcli_out), \
patch("src.wifi_manager.time.sleep"):
return manager._connect_validated("HomeNet", "hunter22")
WRONG = (False, "wrong_password: Secrets were required, but not provided")
class TestWrongPasswordSurvivesRecovery:
def test_when_the_setup_ap_comes_back_up(self):
ok, message = _run(_manager(), WRONG)
assert ok is False
assert message.startswith("wrong_password:")
def test_when_the_setup_ap_fails_too(self):
manager = _manager()
manager.enable_ap_mode.return_value = (False, "hostapd missing")
ok, message = _run(manager, WRONG)
assert ok is False
assert message.startswith("wrong_password:")
assert "hostapd missing" in message
@pytest.mark.parametrize("restored", [True, False])
def test_when_the_previous_network_is_restored_or_not(self, restored):
manager = _manager(connected_ssid="OldNet")
manager._restore_original_connection = MagicMock(return_value=restored)
ok, message = _run(manager, WRONG)
assert ok is False
assert message.startswith("wrong_password:")
class TestOtherFailuresAreUnchanged:
def test_a_generic_failure_gets_no_prefix(self):
ok, message = _run(_manager(), (False, "Connection timed out"))
assert ok is False
assert message == "Connection failed. AP mode enabled."
def test_success_passes_through(self):
assert _run(_manager(), (True, "Connected to HomeNet")) == (True, "Connected to HomeNet")