"""Streamlit AppTest — no live server (uses embedded script runner).""" from __future__ import annotations from datetime import UTC, datetime from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch import pytest from streamlit.testing.v1 import AppTest from shelly_manager.core.config import AppConfig from shelly_manager.core.models import ShellyDevice ROOT = Path(__file__).resolve().parents[1] DASHBOARD_APP = ROOT / "src" / "shelly_manager" / "ui" / "Dashboard.py" MASS_CONFIG_PAGE = ROOT / "src" / "shelly_manager" / "ui" / "pages" / "3_Mass_Config.py" DEVICE_PAGE = ROOT / "src" / "shelly_manager" / "ui" / "pages" / "2_Device.py" @pytest.fixture def mock_manager() -> MagicMock: mgr = MagicMock() mgr.storage.list_devices = AsyncMock(return_value=[]) mgr.storage.get_device = AsyncMock(return_value=None) mgr.storage.get_kv = AsyncMock(return_value=None) mgr.storage.set_kv = AsyncMock(return_value=None) mgr.storage.list_snapshots = AsyncMock(return_value=[]) mgr.storage.recent_snapshot_labels_for_devices = AsyncMock(return_value={}) mgr.discover_all = AsyncMock(return_value=[]) mgr.refresh_all = AsyncMock(return_value=[]) mgr.refresh_device = AsyncMock(return_value=None) mgr.mass_apply_operation = AsyncMock(return_value=[]) mgr.apply_mass_tags = AsyncMock(return_value=0) mgr.apply_device_config = AsyncMock(return_value=[("sys", "ok", False)]) mgr.reboot_device = AsyncMock(return_value=None) mgr.reboot_devices_by_ids = AsyncMock(return_value=[]) mgr.check_firmware_update = AsyncMock(return_value={}) mgr.check_firmware_updates_for_ids = AsyncMock(return_value=[]) mgr.apply_firmware_update = AsyncMock(return_value=None) mgr.mass_apply_section_patch = AsyncMock(return_value=[]) return mgr def test_dashboard_renders_title_and_metrics(mock_manager: MagicMock) -> None: with patch("shelly_manager.ui.Dashboard.get_manager", return_value=mock_manager): at = AppTest.from_file(str(DASHBOARD_APP), default_timeout=15) at.run(timeout=15) titles = [n.value for n in at.title] assert any("Shelly Manager" in (t or "") for t in titles) assert len(at.metric) >= 1 labels = [m.label for m in at.metric] assert "Matching (after filters)" in labels md_blob = "\n".join((m.value or "") for m in at.markdown) assert "Total devices" in md_blob def test_mass_config_page_renders(mock_manager: MagicMock) -> None: with patch("shelly_manager.ui.state.get_manager", return_value=mock_manager): at = AppTest.from_file(str(MASS_CONFIG_PAGE), default_timeout=20) at.run(timeout=20) assert not at.exception titles = [n.value for n in at.title] assert any("Mass configuration" in (t or "") for t in titles) def test_device_detail_page_renders(mock_manager: MagicMock) -> None: d = ShellyDevice( id="AABBCCDDEEFF", name="Kitchen", mac="AA:BB:CC:DD:EE:FF", ip="192.168.1.10", generation=2, model="Plus1", firmware="1.0", online=True, last_seen=datetime.now(UTC), capabilities=[], status={}, settings={"sys": {"device": {"name": "Test"}}}, tags=[], ) mock_manager.storage.list_devices = AsyncMock(return_value=[d]) mock_manager.storage.get_device = AsyncMock(return_value=d) with ( patch("shelly_manager.ui.state.get_manager", return_value=mock_manager), patch("shelly_manager.ui.state.get_config", return_value=AppConfig()), ): at = AppTest.from_file(str(DEVICE_PAGE), default_timeout=20) at.run(timeout=20) assert not at.exception titles = [n.value for n in at.title] assert any("Device" in (t or "") for t in titles)