hass-core/tests/components/esphome/test_dashboard.py
J. Nick Koston 4f213f6df3
Fix first ESPHome device update entity not offering install feature (#106993)
In the case where the user gets their first ESPHome device such as a RATGDO,
they will usually add the device first in HA, and than find the dashboard.

The install function will be missing because we do not know if the dashboard
supports updating devices until the first device is added. We now set the
supported features when we learn the version when the first device is added
2024-01-03 19:58:04 -05:00

209 lines
7.9 KiB
Python

"""Test ESPHome dashboard features."""
import asyncio
from unittest.mock import patch
from aioesphomeapi import DeviceInfo, InvalidAuthAPIError
from homeassistant.components.esphome import CONF_NOISE_PSK, dashboard
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from . import VALID_NOISE_PSK
from tests.common import MockConfigEntry
async def test_dashboard_storage(
hass: HomeAssistant, init_integration, mock_dashboard, hass_storage
) -> None:
"""Test dashboard storage."""
assert hass_storage[dashboard.STORAGE_KEY]["data"] == {
"info": {"addon_slug": "mock-slug", "host": "mock-host", "port": 1234}
}
await dashboard.async_set_dashboard_info(hass, "test-slug", "new-host", 6052)
assert hass_storage[dashboard.STORAGE_KEY]["data"] == {
"info": {"addon_slug": "test-slug", "host": "new-host", "port": 6052}
}
async def test_restore_dashboard_storage(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, hass_storage
) -> MockConfigEntry:
"""Restore dashboard url and slug from storage."""
hass_storage[dashboard.STORAGE_KEY] = {
"version": dashboard.STORAGE_VERSION,
"minor_version": dashboard.STORAGE_VERSION,
"key": dashboard.STORAGE_KEY,
"data": {"info": {"addon_slug": "test-slug", "host": "new-host", "port": 6052}},
}
with patch.object(
dashboard, "async_get_or_create_dashboard_manager"
) as mock_get_or_create:
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_get_or_create.call_count == 1
async def test_restore_dashboard_storage_end_to_end(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, hass_storage
) -> MockConfigEntry:
"""Restore dashboard url and slug from storage."""
hass_storage[dashboard.STORAGE_KEY] = {
"version": dashboard.STORAGE_VERSION,
"minor_version": dashboard.STORAGE_VERSION,
"key": dashboard.STORAGE_KEY,
"data": {"info": {"addon_slug": "test-slug", "host": "new-host", "port": 6052}},
}
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI"
) as mock_dashboard_api:
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state == ConfigEntryState.LOADED
assert mock_dashboard_api.mock_calls[0][1][0] == "http://new-host:6052"
async def test_setup_dashboard_fails(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, hass_storage
) -> MockConfigEntry:
"""Test that nothing is stored on failed dashboard setup when there was no dashboard before."""
with patch.object(
dashboard.ESPHomeDashboardAPI, "get_devices", side_effect=asyncio.TimeoutError
) as mock_get_devices:
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await dashboard.async_set_dashboard_info(hass, "test-slug", "test-host", 6052)
assert mock_config_entry.state == ConfigEntryState.LOADED
assert mock_get_devices.call_count == 1
# The dashboard addon might recover later so we still
# allow it to be set up.
assert dashboard.STORAGE_KEY in hass_storage
async def test_setup_dashboard_fails_when_already_setup(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, hass_storage
) -> MockConfigEntry:
"""Test failed dashboard setup still reloads entries if one existed before."""
with patch.object(dashboard.ESPHomeDashboardAPI, "get_devices") as mock_get_devices:
await dashboard.async_set_dashboard_info(
hass, "test-slug", "working-host", 6052
)
await hass.async_block_till_done()
assert mock_get_devices.call_count == 1
assert dashboard.STORAGE_KEY in hass_storage
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
with patch.object(
dashboard.ESPHomeDashboardAPI, "get_devices", side_effect=asyncio.TimeoutError
) as mock_get_devices, patch(
"homeassistant.components.esphome.async_setup_entry", return_value=True
) as mock_setup:
await dashboard.async_set_dashboard_info(hass, "test-slug", "test-host", 6052)
await hass.async_block_till_done()
assert mock_config_entry.state == ConfigEntryState.LOADED
assert mock_get_devices.call_count == 1
# We still setup, and reload, but we do not do the reauths
assert dashboard.STORAGE_KEY in hass_storage
assert len(mock_setup.mock_calls) == 1
async def test_new_info_reload_config_entries(
hass: HomeAssistant, init_integration, mock_dashboard
) -> None:
"""Test config entries are reloaded when new info is set."""
assert init_integration.state == ConfigEntryState.LOADED
with patch("homeassistant.components.esphome.async_setup_entry") as mock_setup:
await dashboard.async_set_dashboard_info(hass, "test-slug", "test-host", 6052)
assert len(mock_setup.mock_calls) == 1
assert mock_setup.mock_calls[0][1][1] == init_integration
# Test it's a no-op when the same info is set
with patch("homeassistant.components.esphome.async_setup_entry") as mock_setup:
await dashboard.async_set_dashboard_info(hass, "test-slug", "test-host", 6052)
assert len(mock_setup.mock_calls) == 0
async def test_new_dashboard_fix_reauth(
hass: HomeAssistant, mock_client, mock_config_entry, mock_dashboard
) -> None:
"""Test config entries waiting for reauth are triggered."""
mock_client.device_info.side_effect = (
InvalidAuthAPIError,
DeviceInfo(uses_password=False, name="test"),
)
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key:
result = await hass.config_entries.flow.async_init(
"esphome",
context={
"source": SOURCE_REAUTH,
"entry_id": mock_config_entry.entry_id,
"unique_id": mock_config_entry.unique_id,
},
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert len(mock_get_encryption_key.mock_calls) == 0
mock_dashboard["configured"].append(
{
"name": "test",
"configuration": "test.yaml",
}
)
await dashboard.async_get_dashboard(hass).async_refresh()
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key, patch(
"homeassistant.components.esphome.async_setup_entry", return_value=True
) as mock_setup:
await dashboard.async_set_dashboard_info(hass, "test-slug", "test-host", 6052)
await hass.async_block_till_done()
assert len(mock_get_encryption_key.mock_calls) == 1
assert len(mock_setup.mock_calls) == 1
assert mock_config_entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK
async def test_dashboard_supports_update(hass: HomeAssistant, mock_dashboard) -> None:
"""Test dashboard supports update."""
dash = dashboard.async_get_dashboard(hass)
# No data
assert not dash.supports_update
await dash.async_refresh()
assert dash.supports_update is None
# supported version
mock_dashboard["configured"].append(
{
"name": "test",
"configuration": "test.yaml",
"current_version": "2023.2.0-dev",
}
)
await dash.async_refresh()
assert dash.supports_update is True
# unsupported version
dash.supports_update = None
mock_dashboard["configured"][0]["current_version"] = "2023.1.0"
await dash.async_refresh()
assert dash.supports_update is False