mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 18:27:51 +00:00
ESPHome quality improvements round 2 (#143613)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
39f3aa7e78
commit
3aa1c60fe3
@ -109,7 +109,7 @@ def _mired_to_kelvin(mired_temperature: float) -> int:
|
||||
def _color_mode_to_ha(mode: int) -> str:
|
||||
"""Convert an esphome color mode to a HA color mode constant.
|
||||
|
||||
Chose the color mode that best matches the feature-set.
|
||||
Choose the color mode that best matches the feature-set.
|
||||
"""
|
||||
candidates = []
|
||||
for ha_mode, cap_lists in _COLOR_MODE_MAPPING.items():
|
||||
|
@ -96,7 +96,7 @@ class EsphomeMediaPlayer(
|
||||
|
||||
@property
|
||||
@esphome_float_state_property
|
||||
def volume_level(self) -> float | None:
|
||||
def volume_level(self) -> float:
|
||||
"""Volume level of the media player (0..1)."""
|
||||
return self._state.volume
|
||||
|
||||
|
@ -36,7 +36,7 @@ class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity):
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def is_on(self) -> bool | None:
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if the switch is on."""
|
||||
return self._state.state
|
||||
|
||||
|
@ -109,7 +109,6 @@ class ESPHomeDashboardUpdateEntity(
|
||||
_attr_has_entity_name = True
|
||||
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||
_attr_title = "ESPHome"
|
||||
_attr_name = "Firmware"
|
||||
_attr_release_url = "https://esphome.io/changelog/"
|
||||
_attr_entity_registry_enabled_default = False
|
||||
|
||||
@ -242,7 +241,7 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def installed_version(self) -> str | None:
|
||||
def installed_version(self) -> str:
|
||||
"""Return the installed version."""
|
||||
return self._state.current_version
|
||||
|
||||
@ -260,19 +259,19 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def release_summary(self) -> str | None:
|
||||
def release_summary(self) -> str:
|
||||
"""Return the release summary."""
|
||||
return self._state.release_summary
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def release_url(self) -> str | None:
|
||||
def release_url(self) -> str:
|
||||
"""Return the release URL."""
|
||||
return self._state.release_url
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def title(self) -> str | None:
|
||||
def title(self) -> str:
|
||||
"""Return the title of the update."""
|
||||
return self._state.title
|
||||
|
||||
|
@ -65,7 +65,7 @@ class EsphomeValve(EsphomeEntity[ValveInfo, ValveState], ValveEntity):
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def current_valve_position(self) -> int | None:
|
||||
def current_valve_position(self) -> int:
|
||||
"""Return current position of valve. 0 is closed, 100 is open."""
|
||||
return round(self._state.position * 100.0)
|
||||
|
||||
|
34
tests/components/esphome/common.py
Normal file
34
tests/components/esphome/common.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""ESPHome test common code."""
|
||||
|
||||
from homeassistant.components import assist_satellite
|
||||
from homeassistant.components.assist_satellite import AssistSatelliteEntity
|
||||
|
||||
# pylint: disable-next=hass-component-root-import
|
||||
from homeassistant.components.esphome import DOMAIN
|
||||
from homeassistant.components.esphome.assist_satellite import EsphomeAssistSatellite
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
|
||||
def get_satellite_entity(
|
||||
hass: HomeAssistant, mac_address: str
|
||||
) -> EsphomeAssistSatellite | None:
|
||||
"""Get the satellite entity for a device."""
|
||||
ent_reg = er.async_get(hass)
|
||||
satellite_entity_id = ent_reg.async_get_entity_id(
|
||||
Platform.ASSIST_SATELLITE, DOMAIN, f"{mac_address}-assist_satellite"
|
||||
)
|
||||
if satellite_entity_id is None:
|
||||
return None
|
||||
assert satellite_entity_id.endswith("_assist_satellite")
|
||||
|
||||
component: EntityComponent[AssistSatelliteEntity] = hass.data[
|
||||
assist_satellite.DOMAIN
|
||||
]
|
||||
if (entity := component.get_entity(satellite_entity_id)) is not None:
|
||||
assert isinstance(entity, EsphomeAssistSatellite)
|
||||
return entity
|
||||
|
||||
return None
|
@ -34,59 +34,28 @@ from homeassistant.components import (
|
||||
from homeassistant.components.assist_pipeline import PipelineEvent, PipelineEventType
|
||||
from homeassistant.components.assist_satellite import (
|
||||
AssistSatelliteConfiguration,
|
||||
AssistSatelliteEntity,
|
||||
AssistSatelliteEntityFeature,
|
||||
AssistSatelliteWakeWord,
|
||||
)
|
||||
|
||||
# pylint: disable-next=hass-component-root-import
|
||||
from homeassistant.components.assist_satellite.entity import AssistSatelliteState
|
||||
from homeassistant.components.esphome import DOMAIN
|
||||
from homeassistant.components.esphome.assist_satellite import (
|
||||
EsphomeAssistSatellite,
|
||||
VoiceAssistantUDPServer,
|
||||
)
|
||||
from homeassistant.components.esphome.assist_satellite import VoiceAssistantUDPServer
|
||||
from homeassistant.components.select import (
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
)
|
||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
intent as intent_helper,
|
||||
)
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers import device_registry as dr, intent as intent_helper
|
||||
from homeassistant.helpers.network import get_url
|
||||
|
||||
from .common import get_satellite_entity
|
||||
from .conftest import MockESPHomeDevice
|
||||
|
||||
from tests.components.tts.common import MockResultStream
|
||||
|
||||
|
||||
def get_satellite_entity(
|
||||
hass: HomeAssistant, mac_address: str
|
||||
) -> EsphomeAssistSatellite | None:
|
||||
"""Get the satellite entity for a device."""
|
||||
ent_reg = er.async_get(hass)
|
||||
satellite_entity_id = ent_reg.async_get_entity_id(
|
||||
Platform.ASSIST_SATELLITE, DOMAIN, f"{mac_address}-assist_satellite"
|
||||
)
|
||||
if satellite_entity_id is None:
|
||||
return None
|
||||
assert satellite_entity_id.endswith("_assist_satellite")
|
||||
|
||||
component: EntityComponent[AssistSatelliteEntity] = hass.data[
|
||||
assist_satellite.DOMAIN
|
||||
]
|
||||
if (entity := component.get_entity(satellite_entity_id)) is not None:
|
||||
assert isinstance(entity, EsphomeAssistSatellite)
|
||||
return entity
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_wav() -> bytes:
|
||||
"""Return test WAV audio."""
|
||||
@ -1143,32 +1112,6 @@ async def test_tts_minimal_format_from_media_player(
|
||||
}
|
||||
|
||||
|
||||
async def test_announce_supported_features(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test that the announce supported feature is not set by default."""
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
user_service=[],
|
||||
states=[],
|
||||
device_info={
|
||||
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||
assert satellite is not None
|
||||
|
||||
assert not (satellite.supported_features & AssistSatelliteEntityFeature.ANNOUNCE)
|
||||
|
||||
|
||||
async def test_announce_message(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
@ -1236,7 +1179,7 @@ async def test_announce_message(
|
||||
assist_satellite.DOMAIN,
|
||||
"announce",
|
||||
{
|
||||
"entity_id": satellite.entity_id,
|
||||
ATTR_ENTITY_ID: satellite.entity_id,
|
||||
"message": "test-text",
|
||||
"preannounce": False,
|
||||
},
|
||||
@ -1326,7 +1269,7 @@ async def test_announce_media_id(
|
||||
assist_satellite.DOMAIN,
|
||||
"announce",
|
||||
{
|
||||
"entity_id": satellite.entity_id,
|
||||
ATTR_ENTITY_ID: satellite.entity_id,
|
||||
"media_id": "https://www.home-assistant.io/resolved.mp3",
|
||||
"preannounce": False,
|
||||
},
|
||||
@ -1413,7 +1356,7 @@ async def test_announce_message_with_preannounce(
|
||||
assist_satellite.DOMAIN,
|
||||
"announce",
|
||||
{
|
||||
"entity_id": satellite.entity_id,
|
||||
ATTR_ENTITY_ID: satellite.entity_id,
|
||||
"message": "test-text",
|
||||
"preannounce_media_id": "test-preannounce",
|
||||
},
|
||||
@ -1423,7 +1366,7 @@ async def test_announce_message_with_preannounce(
|
||||
assert satellite.state == AssistSatelliteState.IDLE
|
||||
|
||||
|
||||
async def test_start_conversation_supported_features(
|
||||
async def test_non_default_supported_features(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
@ -1431,7 +1374,7 @@ async def test_start_conversation_supported_features(
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test that the start conversation supported feature is not set by default."""
|
||||
"""Test that the start conversation and announce are not set by default."""
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
@ -1449,6 +1392,7 @@ async def test_start_conversation_supported_features(
|
||||
assert not (
|
||||
satellite.supported_features & AssistSatelliteEntityFeature.START_CONVERSATION
|
||||
)
|
||||
assert not (satellite.supported_features & AssistSatelliteEntityFeature.ANNOUNCE)
|
||||
|
||||
|
||||
async def test_start_conversation_message(
|
||||
@ -1537,7 +1481,7 @@ async def test_start_conversation_message(
|
||||
assist_satellite.DOMAIN,
|
||||
"start_conversation",
|
||||
{
|
||||
"entity_id": satellite.entity_id,
|
||||
ATTR_ENTITY_ID: satellite.entity_id,
|
||||
"start_message": "test-text",
|
||||
"preannounce": False,
|
||||
},
|
||||
@ -1646,7 +1590,7 @@ async def test_start_conversation_media_id(
|
||||
assist_satellite.DOMAIN,
|
||||
"start_conversation",
|
||||
{
|
||||
"entity_id": satellite.entity_id,
|
||||
ATTR_ENTITY_ID: satellite.entity_id,
|
||||
"start_media_id": "https://www.home-assistant.io/resolved.mp3",
|
||||
"preannounce": False,
|
||||
},
|
||||
@ -1752,7 +1696,7 @@ async def test_start_conversation_message_with_preannounce(
|
||||
assist_satellite.DOMAIN,
|
||||
"start_conversation",
|
||||
{
|
||||
"entity_id": satellite.entity_id,
|
||||
ATTR_ENTITY_ID: satellite.entity_id,
|
||||
"start_message": "test-text",
|
||||
"preannounce_media_id": "test-preannounce",
|
||||
},
|
||||
@ -1982,7 +1926,7 @@ async def test_wake_word_select(
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{"entity_id": "select.test_wake_word", "option": "Okay Nabu"},
|
||||
{ATTR_ENTITY_ID: "select.test_wake_word", "option": "Okay Nabu"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@ -1997,122 +1941,3 @@ async def test_wake_word_select(
|
||||
|
||||
# Satellite config should have been updated
|
||||
assert satellite.async_get_configuration().active_wake_words == ["okay_nabu"]
|
||||
|
||||
|
||||
async def test_wake_word_select_no_wake_words(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test wake word select is unavailable when there are no available wake word."""
|
||||
device_config = AssistSatelliteConfiguration(
|
||||
available_wake_words=[],
|
||||
active_wake_words=[],
|
||||
max_active_wake_words=1,
|
||||
)
|
||||
mock_client.get_voice_assistant_configuration.return_value = device_config
|
||||
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
user_service=[],
|
||||
states=[],
|
||||
device_info={
|
||||
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||
| VoiceAssistantFeature.ANNOUNCE
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||
assert satellite is not None
|
||||
assert not satellite.async_get_configuration().available_wake_words
|
||||
|
||||
# Select should be unavailable
|
||||
state = hass.states.get("select.test_wake_word")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_wake_word_select_zero_max_wake_words(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test wake word select is unavailable max wake words is zero."""
|
||||
device_config = AssistSatelliteConfiguration(
|
||||
available_wake_words=[
|
||||
AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
|
||||
],
|
||||
active_wake_words=[],
|
||||
max_active_wake_words=0,
|
||||
)
|
||||
mock_client.get_voice_assistant_configuration.return_value = device_config
|
||||
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
user_service=[],
|
||||
states=[],
|
||||
device_info={
|
||||
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||
| VoiceAssistantFeature.ANNOUNCE
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||
assert satellite is not None
|
||||
assert satellite.async_get_configuration().max_active_wake_words == 0
|
||||
|
||||
# Select should be unavailable
|
||||
state = hass.states.get("select.test_wake_word")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_wake_word_select_no_active_wake_words(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test wake word select uses first available wake word if none are active."""
|
||||
device_config = AssistSatelliteConfiguration(
|
||||
available_wake_words=[
|
||||
AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
|
||||
AssistSatelliteWakeWord("hey_jarvis", "Hey Jarvis", ["en"]),
|
||||
],
|
||||
active_wake_words=[],
|
||||
max_active_wake_words=1,
|
||||
)
|
||||
mock_client.get_voice_assistant_configuration.return_value = device_config
|
||||
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
user_service=[],
|
||||
states=[],
|
||||
device_info={
|
||||
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||
| VoiceAssistantFeature.ANNOUNCE
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||
assert satellite is not None
|
||||
assert not satellite.async_get_configuration().active_wake_words
|
||||
|
||||
# First available wake word should be selected
|
||||
state = hass.states.get("select.test_wake_word")
|
||||
assert state is not None
|
||||
assert state.state == "Okay Nabu"
|
||||
|
@ -72,18 +72,16 @@ async def test_user_connection_works(
|
||||
) -> None:
|
||||
"""Test we can finish a config flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=None,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
@ -119,7 +117,7 @@ async def test_user_connection_updates_host(hass: HomeAssistant) -> None:
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=None,
|
||||
)
|
||||
@ -127,10 +125,9 @@ async def test_user_connection_updates_host(hass: HomeAssistant) -> None:
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured_updates"
|
||||
@ -157,7 +154,7 @@ async def test_user_sets_unique_id(hass: HomeAssistant) -> None:
|
||||
type="mock_type",
|
||||
)
|
||||
discovery_result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
|
||||
assert discovery_result["type"] is FlowResultType.FORM
|
||||
@ -180,7 +177,7 @@ async def test_user_sets_unique_id(hass: HomeAssistant) -> None:
|
||||
}
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=None,
|
||||
)
|
||||
@ -211,7 +208,7 @@ async def test_user_resolve_error(hass: HomeAssistant, mock_client: APIClient) -
|
||||
) as exc:
|
||||
mock_client.device_info.side_effect = exc
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -258,7 +255,7 @@ async def test_user_causes_zeroconf_to_abort(hass: HomeAssistant) -> None:
|
||||
type="mock_type",
|
||||
)
|
||||
discovery_result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
|
||||
assert discovery_result["type"] is FlowResultType.FORM
|
||||
@ -268,7 +265,7 @@ async def test_user_causes_zeroconf_to_abort(hass: HomeAssistant) -> None:
|
||||
}
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=None,
|
||||
)
|
||||
@ -301,7 +298,7 @@ async def test_user_connection_error(
|
||||
mock_client.device_info.side_effect = APIConnectionError
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -342,7 +339,7 @@ async def test_user_with_password(
|
||||
mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -374,7 +371,7 @@ async def test_user_invalid_password(
|
||||
mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -432,7 +429,7 @@ async def test_user_dashboard_has_wrong_key(
|
||||
return_value=WRONG_NOISE_PSK,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -487,7 +484,7 @@ async def test_user_discovers_name_and_gets_key_from_dashboard(
|
||||
return_value=VALID_NOISE_PSK,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -539,7 +536,7 @@ async def test_user_discovers_name_and_gets_key_from_dashboard_fails(
|
||||
side_effect=dashboard_exception,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -589,12 +586,12 @@ async def test_user_discovers_name_and_dashboard_is_unavailable(
|
||||
)
|
||||
|
||||
with patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.get_devices",
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
|
||||
side_effect=TimeoutError,
|
||||
):
|
||||
await dashboard.async_get_dashboard(hass).async_refresh()
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -627,7 +624,7 @@ async def test_login_connection_error(
|
||||
mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -680,7 +677,7 @@ async def test_discovery_initiation(hass: HomeAssistant) -> None:
|
||||
type="mock_type",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
assert get_flow_context(hass, flow) == {
|
||||
"source": config_entries.SOURCE_ZEROCONF,
|
||||
@ -714,7 +711,7 @@ async def test_discovery_no_mac(hass: HomeAssistant) -> None:
|
||||
type="mock_type",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
assert flow["type"] is FlowResultType.ABORT
|
||||
assert flow["reason"] == "mdns_missing_mac"
|
||||
@ -741,7 +738,7 @@ async def test_discovery_already_configured(hass: HomeAssistant) -> None:
|
||||
type="mock_type",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
@ -767,14 +764,14 @@ async def test_discovery_duplicate_data(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
|
||||
DOMAIN, data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
assert result["description_placeholders"] == {"name": "test"}
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
|
||||
DOMAIN, data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_in_progress"
|
||||
@ -801,7 +798,7 @@ async def test_discovery_updates_unique_id(hass: HomeAssistant) -> None:
|
||||
type="mock_type",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
@ -821,7 +818,7 @@ async def test_user_requires_psk(hass: HomeAssistant, mock_client: APIClient) ->
|
||||
mock_client.device_info.side_effect = RequiresEncryptionAPIError
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -867,7 +864,7 @@ async def test_encryption_key_valid_psk(
|
||||
mock_client.device_info.side_effect = RequiresEncryptionAPIError
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -903,7 +900,7 @@ async def test_encryption_key_invalid_psk(
|
||||
mock_client.device_info.side_effect = RequiresEncryptionAPIError
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -1301,7 +1298,7 @@ async def test_discovery_dhcp_updates_host(
|
||||
macaddress="1122334455aa",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
@ -1337,7 +1334,7 @@ async def test_discovery_dhcp_does_not_update_host_wrong_mac(
|
||||
macaddress="1122334455aa",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
@ -1372,7 +1369,7 @@ async def test_discovery_dhcp_does_not_update_host_wrong_mac_bad_key(
|
||||
macaddress="1122334455aa",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
@ -1407,7 +1404,7 @@ async def test_discovery_dhcp_does_not_update_host_missing_mac_bad_key(
|
||||
macaddress="1122334455aa",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
@ -1441,7 +1438,7 @@ async def test_discovery_dhcp_no_changes(
|
||||
macaddress="000000000000",
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
@ -1454,7 +1451,7 @@ async def test_discovery_dhcp_no_changes(
|
||||
async def test_discovery_hassio(hass: HomeAssistant) -> None:
|
||||
"""Test dashboard discovery."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
data=HassioServiceInfo(
|
||||
config={
|
||||
"host": "mock-esphome",
|
||||
@ -1494,7 +1491,7 @@ async def test_zeroconf_encryption_key_via_dashboard(
|
||||
type="mock_type",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
|
||||
assert flow["type"] is FlowResultType.FORM
|
||||
@ -1561,7 +1558,7 @@ async def test_zeroconf_encryption_key_via_dashboard_with_api_encryption_prop(
|
||||
type="mock_type",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
|
||||
assert flow["type"] is FlowResultType.FORM
|
||||
@ -1625,7 +1622,7 @@ async def test_zeroconf_no_encryption_key_via_dashboard(
|
||||
type="mock_type",
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
|
||||
)
|
||||
|
||||
assert flow["type"] is FlowResultType.FORM
|
||||
@ -1767,7 +1764,7 @@ async def test_user_discovers_name_no_dashboard(
|
||||
]
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -1805,7 +1802,7 @@ async def mqtt_discovery_test_abort(
|
||||
timestamp=None,
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_MQTT}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_MQTT}, data=service_info
|
||||
)
|
||||
assert flow["type"] is FlowResultType.ABORT
|
||||
assert flow["reason"] == reason
|
||||
@ -1849,7 +1846,7 @@ async def test_discovery_mqtt_initiation(hass: HomeAssistant) -> None:
|
||||
timestamp=None,
|
||||
)
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
"esphome", context={"source": config_entries.SOURCE_MQTT}, data=service_info
|
||||
DOMAIN, context={"source": config_entries.SOURCE_MQTT}, data=service_info
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
@ -1886,7 +1883,7 @@ async def test_user_flow_name_conflict_migrate(
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
@ -1936,11 +1933,10 @@ async def test_user_flow_name_conflict_overwrite(
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"esphome",
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "name_conflict"
|
||||
|
@ -6,12 +6,7 @@ from unittest.mock import patch
|
||||
from aioesphomeapi import DeviceInfo, InvalidAuthAPIError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.esphome import (
|
||||
CONF_NOISE_PSK,
|
||||
DOMAIN,
|
||||
coordinator,
|
||||
dashboard,
|
||||
)
|
||||
from homeassistant.components.esphome import CONF_NOISE_PSK, DOMAIN, dashboard
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
@ -117,8 +112,9 @@ async def test_setup_dashboard_fails(
|
||||
hass_storage: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test that nothing is stored on failed dashboard setup when there was no dashboard before."""
|
||||
with patch.object(
|
||||
coordinator.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
|
||||
with patch(
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
|
||||
side_effect=TimeoutError,
|
||||
) as mock_get_devices:
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
@ -136,8 +132,8 @@ async def test_setup_dashboard_fails_when_already_setup(
|
||||
hass_storage: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test failed dashboard setup still reloads entries if one existed before."""
|
||||
with patch.object(
|
||||
coordinator.ESPHomeDashboardAPI, "get_devices"
|
||||
with patch(
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices"
|
||||
) as mock_get_devices:
|
||||
await dashboard.async_set_dashboard_info(
|
||||
hass, "test-slug", "working-host", 6052
|
||||
@ -151,8 +147,9 @@ async def test_setup_dashboard_fails_when_already_setup(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
coordinator.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
|
||||
patch(
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
|
||||
side_effect=TimeoutError,
|
||||
) as mock_get_devices,
|
||||
patch(
|
||||
"homeassistant.components.esphome.async_setup_entry", return_value=True
|
||||
|
@ -221,9 +221,6 @@ async def test_entities_removed_after_reload(
|
||||
unique_id="my_binary_sensor",
|
||||
),
|
||||
]
|
||||
states = [
|
||||
BinarySensorState(key=1, state=True, missing_state=False),
|
||||
]
|
||||
mock_device.client.list_entities_services = AsyncMock(
|
||||
return_value=(entity_info, user_service)
|
||||
)
|
||||
|
@ -7,6 +7,8 @@ from aioesphomeapi import (
|
||||
SensorState,
|
||||
)
|
||||
|
||||
from homeassistant.components.esphome import DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
@ -62,15 +64,15 @@ async def test_migrate_entity_unique_id_downgrade_upgrade(
|
||||
) -> None:
|
||||
"""Test unique id migration prefers the original entity on downgrade upgrade."""
|
||||
entity_registry.async_get_or_create(
|
||||
"sensor",
|
||||
"esphome",
|
||||
SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
"my_sensor",
|
||||
suggested_object_id="old_sensor",
|
||||
disabled_by=None,
|
||||
)
|
||||
entity_registry.async_get_or_create(
|
||||
"sensor",
|
||||
"esphome",
|
||||
SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
"11:22:33:44:55:AA-sensor-mysensor",
|
||||
suggested_object_id="new_sensor",
|
||||
disabled_by=None,
|
||||
@ -103,7 +105,7 @@ async def test_migrate_entity_unique_id_downgrade_upgrade(
|
||||
# entity that was only created on downgrade and they keep
|
||||
# the original one.
|
||||
assert (
|
||||
entity_registry.async_get_entity_id("sensor", "esphome", "my_sensor")
|
||||
entity_registry.async_get_entity_id(SENSOR_DOMAIN, DOMAIN, "my_sensor")
|
||||
is not None
|
||||
)
|
||||
# Note that ESPHome includes the EntityInfo type in the unique id
|
||||
|
@ -9,9 +9,9 @@ from homeassistant.core import HomeAssistant
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_zeroconf")
|
||||
async def test_delete_entry(hass: HomeAssistant, mock_client) -> None:
|
||||
"""Test we can delete an entry with error."""
|
||||
@pytest.mark.usefixtures("mock_client", "mock_zeroconf")
|
||||
async def test_delete_entry(hass: HomeAssistant) -> None:
|
||||
"""Test we can delete an entry without error."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: "test.local", CONF_PORT: 6053, CONF_PASSWORD: ""},
|
||||
|
@ -38,6 +38,8 @@ from homeassistant.components.light import (
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
LIGHT_COLOR_CAPABILITY_UNKNOWN = 1 << 8 # 256
|
||||
|
||||
|
||||
async def test_light_on_off(
|
||||
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
|
||||
@ -391,7 +393,9 @@ async def test_light_brightness_on_off_with_unknown_color_mode(
|
||||
min_mireds=153,
|
||||
max_mireds=400,
|
||||
supported_color_modes=[
|
||||
LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | 1 << 8
|
||||
LightColorCapability.ON_OFF
|
||||
| LightColorCapability.BRIGHTNESS
|
||||
| LIGHT_COLOR_CAPABILITY_UNKNOWN
|
||||
],
|
||||
)
|
||||
]
|
||||
@ -420,7 +424,7 @@ async def test_light_brightness_on_off_with_unknown_color_mode(
|
||||
state=True,
|
||||
color_mode=LightColorCapability.ON_OFF
|
||||
| LightColorCapability.BRIGHTNESS
|
||||
| 1 << 8,
|
||||
| LIGHT_COLOR_CAPABILITY_UNKNOWN,
|
||||
)
|
||||
]
|
||||
)
|
||||
@ -439,7 +443,7 @@ async def test_light_brightness_on_off_with_unknown_color_mode(
|
||||
state=True,
|
||||
color_mode=LightColorCapability.ON_OFF
|
||||
| LightColorCapability.BRIGHTNESS
|
||||
| 1 << 8,
|
||||
| LIGHT_COLOR_CAPABILITY_UNKNOWN,
|
||||
brightness=pytest.approx(0.4980392156862745),
|
||||
)
|
||||
]
|
||||
|
@ -34,6 +34,7 @@ from homeassistant.components.esphome.const import (
|
||||
STABLE_BLE_VERSION_STR,
|
||||
)
|
||||
from homeassistant.components.esphome.manager import DEVICE_CONFLICT_ISSUE_FORMAT
|
||||
from homeassistant.components.tag import DOMAIN as TAG_DOMAIN
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
@ -192,7 +193,7 @@ async def test_esphome_device_service_calls_allowed(
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test a device with service calls are allowed."""
|
||||
await async_setup_component(hass, "tag", {})
|
||||
await async_setup_component(hass, TAG_DOMAIN, {})
|
||||
entity_info = []
|
||||
states = []
|
||||
user_service = []
|
||||
|
@ -1,9 +1,22 @@
|
||||
"""Test ESPHome selects."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from unittest.mock import call
|
||||
|
||||
from aioesphomeapi import APIClient, SelectInfo, SelectState
|
||||
from aioesphomeapi import (
|
||||
APIClient,
|
||||
EntityInfo,
|
||||
EntityState,
|
||||
SelectInfo,
|
||||
SelectState,
|
||||
UserService,
|
||||
VoiceAssistantFeature,
|
||||
)
|
||||
|
||||
from homeassistant.components.assist_satellite import (
|
||||
AssistSatelliteConfiguration,
|
||||
AssistSatelliteWakeWord,
|
||||
)
|
||||
from homeassistant.components.select import (
|
||||
ATTR_OPTION,
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
@ -12,6 +25,9 @@ from homeassistant.components.select import (
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .common import get_satellite_entity
|
||||
from .conftest import MockESPHomeDevice
|
||||
|
||||
|
||||
async def test_pipeline_selector(
|
||||
hass: HomeAssistant,
|
||||
@ -80,3 +96,122 @@ async def test_select_generic_entity(
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.select_command.assert_has_calls([call(1, "b")])
|
||||
|
||||
|
||||
async def test_wake_word_select_no_wake_words(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test wake word select is unavailable when there are no available wake word."""
|
||||
device_config = AssistSatelliteConfiguration(
|
||||
available_wake_words=[],
|
||||
active_wake_words=[],
|
||||
max_active_wake_words=1,
|
||||
)
|
||||
mock_client.get_voice_assistant_configuration.return_value = device_config
|
||||
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
user_service=[],
|
||||
states=[],
|
||||
device_info={
|
||||
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||
| VoiceAssistantFeature.ANNOUNCE
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||
assert satellite is not None
|
||||
assert not satellite.async_get_configuration().available_wake_words
|
||||
|
||||
# Select should be unavailable
|
||||
state = hass.states.get("select.test_wake_word")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_wake_word_select_zero_max_wake_words(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test wake word select is unavailable max wake words is zero."""
|
||||
device_config = AssistSatelliteConfiguration(
|
||||
available_wake_words=[
|
||||
AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
|
||||
],
|
||||
active_wake_words=[],
|
||||
max_active_wake_words=0,
|
||||
)
|
||||
mock_client.get_voice_assistant_configuration.return_value = device_config
|
||||
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
user_service=[],
|
||||
states=[],
|
||||
device_info={
|
||||
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||
| VoiceAssistantFeature.ANNOUNCE
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||
assert satellite is not None
|
||||
assert satellite.async_get_configuration().max_active_wake_words == 0
|
||||
|
||||
# Select should be unavailable
|
||||
state = hass.states.get("select.test_wake_word")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_wake_word_select_no_active_wake_words(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: Callable[
|
||||
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||
Awaitable[MockESPHomeDevice],
|
||||
],
|
||||
) -> None:
|
||||
"""Test wake word select uses first available wake word if none are active."""
|
||||
device_config = AssistSatelliteConfiguration(
|
||||
available_wake_words=[
|
||||
AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
|
||||
AssistSatelliteWakeWord("hey_jarvis", "Hey Jarvis", ["en"]),
|
||||
],
|
||||
active_wake_words=[],
|
||||
max_active_wake_words=1,
|
||||
)
|
||||
mock_client.get_voice_assistant_configuration.return_value = device_config
|
||||
|
||||
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=[],
|
||||
user_service=[],
|
||||
states=[],
|
||||
device_info={
|
||||
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||
| VoiceAssistantFeature.ANNOUNCE
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||
assert satellite is not None
|
||||
assert not satellite.async_get_configuration().active_wake_words
|
||||
|
||||
# First available wake word should be selected
|
||||
state = hass.states.get("select.test_wake_word")
|
||||
assert state is not None
|
||||
assert state.state == "Okay Nabu"
|
||||
|
@ -119,10 +119,12 @@ async def test_update_entity(
|
||||
# Compile failed, don't try to upload
|
||||
with (
|
||||
patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=False
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
|
||||
return_value=False,
|
||||
) as mock_compile,
|
||||
patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
|
||||
return_value=True,
|
||||
) as mock_upload,
|
||||
pytest.raises(
|
||||
HomeAssistantError,
|
||||
@ -130,9 +132,9 @@ async def test_update_entity(
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"update",
|
||||
"install",
|
||||
{"entity_id": "update.test_firmware"},
|
||||
UPDATE_DOMAIN,
|
||||
SERVICE_INSTALL,
|
||||
{ATTR_ENTITY_ID: "update.test_firmware"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@ -144,10 +146,12 @@ async def test_update_entity(
|
||||
# Compile success, upload fails
|
||||
with (
|
||||
patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
|
||||
return_value=True,
|
||||
) as mock_compile,
|
||||
patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
|
||||
return_value=False,
|
||||
) as mock_upload,
|
||||
pytest.raises(
|
||||
HomeAssistantError,
|
||||
@ -155,9 +159,9 @@ async def test_update_entity(
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"update",
|
||||
"install",
|
||||
{"entity_id": "update.test_firmware"},
|
||||
UPDATE_DOMAIN,
|
||||
SERVICE_INSTALL,
|
||||
{ATTR_ENTITY_ID: "update.test_firmware"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@ -170,16 +174,18 @@ async def test_update_entity(
|
||||
# Everything works
|
||||
with (
|
||||
patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
|
||||
return_value=True,
|
||||
) as mock_compile,
|
||||
patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
|
||||
return_value=True,
|
||||
) as mock_upload,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"update",
|
||||
"install",
|
||||
{"entity_id": "update.test_firmware"},
|
||||
UPDATE_DOMAIN,
|
||||
SERVICE_INSTALL,
|
||||
{ATTR_ENTITY_ID: "update.test_firmware"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@ -286,7 +292,7 @@ async def test_update_entity_dashboard_not_available_startup(
|
||||
"""Test ESPHome update entity when dashboard is not available at startup."""
|
||||
with (
|
||||
patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.get_devices",
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
|
||||
side_effect=TimeoutError,
|
||||
),
|
||||
):
|
||||
@ -334,7 +340,7 @@ async def test_update_entity_dashboard_discovered_after_startup_but_update_faile
|
||||
) -> None:
|
||||
"""Test ESPHome update entity when dashboard is discovered after startup and the first update fails."""
|
||||
with patch(
|
||||
"esphome_dashboard_api.ESPHomeDashboardAPI.get_devices",
|
||||
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
|
||||
side_effect=TimeoutError,
|
||||
):
|
||||
await async_get_dashboard(hass).async_refresh()
|
||||
|
Loading…
x
Reference in New Issue
Block a user