* Schedule periodic coordinator updates as background tasks. Currently, the coordinator's periodic refreshes delay startup because they are not scheduled as background tasks. We will wait if the startup takes long enough for the first planned refresh. Another coordinator's scheduled refresh will be fired on busy systems, further delaying the startup. This chain of events results in the startup taking a long time and hitting the safety timeout because too many coordinators are refreshing. This case can also happen with scheduled entity refreshes, but it's less common. A future PR will address that case. * periodic_tasks * periodic_tasks * periodic_tasks * merge * merge * merge * merge * merge * fix test that call the sync api from async * one more place * cannot chain * async_run_periodic_hass_job * sun and pattern time changes from automations also block startup * Revert "sun and pattern time changes from automations also block startup" This reverts commit6de2defa05
. * make sure polling is cancelled when config entry is unloaded * Revert "Revert "sun and pattern time changes from automations also block startup"" This reverts commite8f12aad55
. * remove DisabledError from homewizard test as it relies on a race * fix race * direct coverage
347 lines
12 KiB
Python
347 lines
12 KiB
Python
"""The tests for the hassio sensors."""
|
|
from datetime import timedelta
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
import pytest
|
|
|
|
from homeassistant import config_entries
|
|
from homeassistant.components.hassio import (
|
|
DOMAIN,
|
|
HASSIO_UPDATE_INTERVAL,
|
|
HassioAPIError,
|
|
)
|
|
from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY
|
|
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 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, request):
|
|
"""Mock all setup requests."""
|
|
_install_default_mocks(aioclient_mock)
|
|
_install_test_addon_stats_mock(aioclient_mock)
|
|
|
|
|
|
def _install_test_addon_stats_mock(aioclient_mock: AiohttpClientMocker):
|
|
"""Install mock to provide valid stats for the test addon."""
|
|
aioclient_mock.get(
|
|
"http://127.0.0.1/addons/test/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,
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
def _install_test_addon_stats_failure_mock(aioclient_mock: AiohttpClientMocker):
|
|
"""Install mocks to raise an exception when fetching stats for the test addon."""
|
|
aioclient_mock.get(
|
|
"http://127.0.0.1/addons/test/stats",
|
|
exc=HassioAPIError,
|
|
)
|
|
|
|
|
|
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.get("http://127.0.0.1/supervisor/ping", 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/store",
|
|
json={
|
|
"result": "ok",
|
|
"data": {"addons": [], "repositories": []},
|
|
},
|
|
)
|
|
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/addons/test/changelog", text="")
|
|
aioclient_mock.get(
|
|
"http://127.0.0.1/addons/test/info",
|
|
json={"result": "ok", "data": {"auto_update": True}},
|
|
)
|
|
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
|
|
aioclient_mock.get(
|
|
"http://127.0.0.1/addons/test2/info",
|
|
json={"result": "ok", "data": {"auto_update": False}},
|
|
)
|
|
aioclient_mock.get(
|
|
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
|
|
)
|
|
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
|
|
aioclient_mock.get(
|
|
"http://127.0.0.1/resolution/info",
|
|
json={
|
|
"result": "ok",
|
|
"data": {
|
|
"unsupported": [],
|
|
"unhealthy": [],
|
|
"suggestions": [],
|
|
"issues": [],
|
|
"checks": [],
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
@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(
|
|
("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,
|
|
) -> 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)
|
|
_install_test_addon_stats_failure_mock(aioclient_mock)
|
|
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert "Could not fetch stats" not in caplog.text
|
|
|
|
aioclient_mock.clear_requests()
|
|
_install_default_mocks(aioclient_mock)
|
|
_install_test_addon_stats_mock(aioclient_mock)
|
|
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
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()
|
|
assert config_entry.state is config_entries.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()
|
|
|
|
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()
|
|
# 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)
|
|
_install_test_addon_stats_failure_mock(aioclient_mock)
|
|
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(entity_id)
|
|
assert state.state == STATE_UNAVAILABLE
|
|
assert "Could not fetch stats" in caplog.text
|