core/tests/components/esphome/test_manager.py

335 lines
10 KiB
Python

"""Test ESPHome manager."""
from collections.abc import Awaitable, Callable
from unittest.mock import AsyncMock
from aioesphomeapi import APIClient, DeviceInfo, EntityInfo, EntityState, UserService
import pytest
from homeassistant import config_entries
from homeassistant.components import dhcp
from homeassistant.components.esphome.const import (
CONF_DEVICE_NAME,
DOMAIN,
STABLE_BLE_VERSION_STR,
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import issue_registry as ir
from .conftest import MockESPHomeDevice
from tests.common import MockConfigEntry
async def test_esphome_device_with_old_bluetooth(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test a device with old bluetooth creates an issue."""
entity_info = []
states = []
user_service = []
await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
device_info={"bluetooth_proxy_feature_flags": 1, "esphome_version": "2023.3.0"},
)
await hass.async_block_till_done()
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
"esphome", "ble_firmware_outdated-11:22:33:44:55:aa"
)
assert (
issue.learn_more_url
== f"https://esphome.io/changelog/{STABLE_BLE_VERSION_STR}.html"
)
async def test_esphome_device_with_password(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test a device with legacy password creates an issue."""
entity_info = []
states = []
user_service = []
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "test.local",
CONF_PORT: 6053,
CONF_PASSWORD: "has",
},
)
entry.add_to_hass(hass)
await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
device_info={"bluetooth_proxy_feature_flags": 0, "esphome_version": "2023.3.0"},
entry=entry,
)
await hass.async_block_till_done()
issue_registry = ir.async_get(hass)
assert (
issue_registry.async_get_issue(
"esphome", "api_password_deprecated-11:22:33:44:55:aa"
)
is not None
)
async def test_esphome_device_with_current_bluetooth(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test a device with recent bluetooth does not create an issue."""
entity_info = []
states = []
user_service = []
await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
device_info={
"bluetooth_proxy_feature_flags": 1,
"esphome_version": STABLE_BLE_VERSION_STR,
},
)
await hass.async_block_till_done()
issue_registry = ir.async_get(hass)
assert (
issue_registry.async_get_issue(
"esphome", "ble_firmware_outdated-11:22:33:44:55:aa"
)
is None
)
async def test_unique_id_updated_to_mac(
hass: HomeAssistant, mock_client, mock_zeroconf: None
) -> None:
"""Test we update config entry unique ID to MAC address."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: "test.local", CONF_PORT: 6053, CONF_PASSWORD: ""},
unique_id="mock-config-name",
)
entry.add_to_hass(hass)
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(
mac_address="1122334455aa",
)
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.unique_id == "11:22:33:44:55:aa"
async def test_unique_id_not_updated_if_name_same_and_already_mac(
hass: HomeAssistant, mock_client: APIClient, mock_zeroconf: None
) -> None:
"""Test we never update the entry unique ID event if the name is the same."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "test.local",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_DEVICE_NAME: "test",
},
unique_id="11:22:33:44:55:aa",
)
entry.add_to_hass(hass)
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(mac_address="1122334455ab", name="test")
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# Mac should never update
assert entry.unique_id == "11:22:33:44:55:aa"
async def test_unique_id_updated_if_name_unset_and_already_mac(
hass: HomeAssistant, mock_client: APIClient, mock_zeroconf: None
) -> None:
"""Test we never update config entry unique ID even if the name is unset."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: "test.local", CONF_PORT: 6053, CONF_PASSWORD: ""},
unique_id="11:22:33:44:55:aa",
)
entry.add_to_hass(hass)
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(mac_address="1122334455ab", name="test")
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# Mac should never update
assert entry.unique_id == "11:22:33:44:55:aa"
async def test_unique_id_not_updated_if_name_different_and_already_mac(
hass: HomeAssistant, mock_client: APIClient, mock_zeroconf: None
) -> None:
"""Test we do not update config entry unique ID if the name is different."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "test.local",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_DEVICE_NAME: "test",
},
unique_id="11:22:33:44:55:aa",
)
entry.add_to_hass(hass)
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(mac_address="1122334455ab", name="different")
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# Mac should not be updated because name is different
assert entry.unique_id == "11:22:33:44:55:aa"
# Name should not be updated either
assert entry.data[CONF_DEVICE_NAME] == "test"
async def test_name_updated_only_if_mac_matches(
hass: HomeAssistant, mock_client: APIClient, mock_zeroconf: None
) -> None:
"""Test we update config entry name only if the mac matches."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "test.local",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_DEVICE_NAME: "old",
},
unique_id="11:22:33:44:55:aa",
)
entry.add_to_hass(hass)
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(mac_address="1122334455aa", name="new")
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.unique_id == "11:22:33:44:55:aa"
assert entry.data[CONF_DEVICE_NAME] == "new"
async def test_name_updated_only_if_mac_was_unset(
hass: HomeAssistant, mock_client: APIClient, mock_zeroconf: None
) -> None:
"""Test we update config entry name if the old unique id was not a mac."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "test.local",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_DEVICE_NAME: "old",
},
unique_id="notamac",
)
entry.add_to_hass(hass)
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(mac_address="1122334455aa", name="new")
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.unique_id == "11:22:33:44:55:aa"
assert entry.data[CONF_DEVICE_NAME] == "new"
async def test_connection_aborted_wrong_device(
hass: HomeAssistant,
mock_client: APIClient,
mock_zeroconf: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test we abort the connection if the unique id is a mac and neither name or mac match."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "192.168.43.183",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_DEVICE_NAME: "test",
},
unique_id="11:22:33:44:55:aa",
)
entry.add_to_hass(hass)
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(mac_address="1122334455ab", name="different")
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert (
"Unexpected device found at 192.168.43.183; expected `test` "
"with mac address `11:22:33:44:55:aa`, found `different` "
"with mac address `11:22:33:44:55:ab`" in caplog.text
)
caplog.clear()
# Make sure discovery triggers a reconnect to the correct device
service_info = dhcp.DhcpServiceInfo(
ip="192.168.43.184",
hostname="test",
macaddress="1122334455aa",
)
new_info = AsyncMock(
return_value=DeviceInfo(mac_address="1122334455aa", name="test")
)
mock_client.device_info = new_info
result = await hass.config_entries.flow.async_init(
"esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"
assert entry.data[CONF_HOST] == "192.168.43.184"
await hass.async_block_till_done()
assert len(new_info.mock_calls) == 1
assert "Unexpected device found at" not in caplog.text