"""The tests for the hassio sensors.""" from datetime import timedelta import os from unittest.mock import AsyncMock, patch from aiohasupervisor import SupervisorError from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant import config_entries from homeassistant.components.hassio import DOMAIN, HASSIO_UPDATE_INTERVAL from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY from homeassistant.config_entries import ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from .common import MOCK_REPOSITORIES, MOCK_STORE_ADDONS from tests.common import MockConfigEntry, async_fire_time_changed from tests.test_util.aiohttp import AiohttpClientMocker MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"} @pytest.fixture(autouse=True) def mock_all( aioclient_mock: AiohttpClientMocker, addon_installed: AsyncMock, store_info: AsyncMock, addon_stats: AsyncMock, addon_changelog: AsyncMock, resolution_info: AsyncMock, ) -> None: """Mock all setup requests.""" _install_default_mocks(aioclient_mock) def _install_default_mocks(aioclient_mock: AiohttpClientMocker): """Install default mocks.""" aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) aioclient_mock.get( "http://127.0.0.1/info", json={ "result": "ok", "data": { "supervisor": "222", "homeassistant": "0.110.0", "hassos": "1.2.3", }, }, ) aioclient_mock.get( "http://127.0.0.1/host/info", json={ "result": "ok", "data": { "agent_version": "1.0.0", "chassis": "vm", "operating_system": "Debian GNU/Linux 10 (buster)", "kernel": "4.19.0-6-amd64", }, }, ) aioclient_mock.get( "http://127.0.0.1/core/info", json={"result": "ok", "data": {"version_latest": "1.0.0", "version": "1.0.0"}}, ) aioclient_mock.get( "http://127.0.0.1/os/info", json={"result": "ok", "data": {"version_latest": "1.0.0", "version": "1.0.0"}}, ) aioclient_mock.get( "http://127.0.0.1/supervisor/info", json={ "result": "ok", "data": { "result": "ok", "version": "1.0.0", "version_latest": "1.0.0", "auto_update": True, "addons": [ { "name": "test", "state": "started", "slug": "test", "installed": True, "update_available": False, "version": "2.0.0", "version_latest": "2.0.1", "repository": "core", "url": "https://github.com/home-assistant/addons/test", "icon": False, }, { "name": "test2", "state": "stopped", "slug": "test2", "installed": True, "update_available": False, "version": "3.1.0", "version_latest": "3.2.0", "repository": "core", "url": "https://github.com", "icon": False, }, ], }, }, ) aioclient_mock.get( "http://127.0.0.1/core/stats", json={ "result": "ok", "data": { "cpu_percent": 0.99, "memory_usage": 182611968, "memory_limit": 3977146368, "memory_percent": 4.59, "network_rx": 362570232, "network_tx": 82374138, "blk_read": 46010945536, "blk_write": 15051526144, }, }, ) aioclient_mock.get( "http://127.0.0.1/supervisor/stats", json={ "result": "ok", "data": { "cpu_percent": 0.99, "memory_usage": 182611968, "memory_limit": 3977146368, "memory_percent": 4.59, "network_rx": 362570232, "network_tx": 82374138, "blk_read": 46010945536, "blk_write": 15051526144, }, }, ) aioclient_mock.get( "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} ) aioclient_mock.get( "http://127.0.0.1/network/info", json={ "result": "ok", "data": { "host_internet": True, "supervisor_internet": True, }, }, ) @pytest.mark.parametrize( ("store_addons", "store_repositories"), [(MOCK_STORE_ADDONS, MOCK_REPOSITORIES)] ) @pytest.mark.parametrize( ("entity_id", "expected"), [ ("sensor.home_assistant_operating_system_version", "1.0.0"), ("sensor.home_assistant_operating_system_newest_version", "1.0.0"), ("sensor.home_assistant_host_os_agent_version", "1.0.0"), ("sensor.home_assistant_core_cpu_percent", "0.99"), ("sensor.home_assistant_supervisor_cpu_percent", "0.99"), ("sensor.test_version", "2.0.0"), ("sensor.test_newest_version", "2.0.1"), ("sensor.test2_version", "3.1.0"), ("sensor.test2_newest_version", "3.2.0"), ("sensor.test_cpu_percent", "0.99"), ("sensor.test2_cpu_percent", "unavailable"), ("sensor.test_memory_percent", "4.59"), ("sensor.test2_memory_percent", "unavailable"), ], ) async def test_sensor( hass: HomeAssistant, entity_id, expected, aioclient_mock: AiohttpClientMocker, entity_registry: er.EntityRegistry, caplog: pytest.LogCaptureFixture, ) -> None: """Test hassio OS and addons sensor.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() # Verify that the entity is disabled by default. assert hass.states.get(entity_id) is None # Enable the entity. entity_registry.async_update_entity(entity_id, disabled_by=None) await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() # There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer async_fire_time_changed( hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY) ) await hass.async_block_till_done() # Verify that the entity have the expected state. state = hass.states.get(entity_id) assert state.state == expected @pytest.mark.parametrize( ("store_addons", "store_repositories"), [(MOCK_STORE_ADDONS, MOCK_REPOSITORIES)] ) @pytest.mark.parametrize( ("entity_id", "expected"), [ ("sensor.test_cpu_percent", "0.99"), ("sensor.test_memory_percent", "4.59"), ], ) @patch.dict(os.environ, MOCK_ENVIRON) async def test_stats_addon_sensor( hass: HomeAssistant, entity_id, expected, aioclient_mock: AiohttpClientMocker, entity_registry: er.EntityRegistry, caplog: pytest.LogCaptureFixture, freezer: FrozenDateTimeFactory, addon_stats: AsyncMock, ) -> None: """Test stats addons sensor.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() # Verify that the entity is disabled by default. assert hass.states.get(entity_id) is None aioclient_mock.clear_requests() _install_default_mocks(aioclient_mock) addon_stats.side_effect = SupervisorError freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) assert "Could not fetch stats" not in caplog.text aioclient_mock.clear_requests() _install_default_mocks(aioclient_mock) addon_stats.side_effect = None freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) assert "Could not fetch stats" not in caplog.text # Enable the entity and wait for the reload to complete. entity_registry.async_update_entity(entity_id, disabled_by=None) freezer.tick(config_entries.RELOAD_AFTER_UPDATE_DELAY) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) assert config_entry.state is ConfigEntryState.LOADED # Verify the entity is still enabled assert entity_registry.async_get(entity_id).disabled_by is None # The config entry just reloaded, so we need to wait for the next update freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(entity_id) is not None freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) # Verify that the entity have the expected state. state = hass.states.get(entity_id) assert state.state == expected aioclient_mock.clear_requests() _install_default_mocks(aioclient_mock) addon_stats.side_effect = SupervisorError freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(entity_id) assert state.state == STATE_UNAVAILABLE assert "Could not fetch stats" in caplog.text