Merge pull request #49302 from home-assistant/rc

pull/49313/head 2021.4.5
Franck Nijhof 2021-04-16 16:04:26 +02:00 committed by GitHub
commit 77f14b63f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 245 additions and 56 deletions

View File

@ -15,14 +15,14 @@ from .const import DOMAIN
PLATFORMS = ["sensor"] PLATFORMS = ["sensor"]
async def async_setup(hass: HomeAssistant, config: dict): async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up the Coronavirus component.""" """Set up the Coronavirus component."""
# Make sure coordinator is initialized. # Make sure coordinator is initialized.
await get_coordinator(hass) await get_coordinator(hass)
return True return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Coronavirus from a config entry.""" """Set up Coronavirus from a config entry."""
if isinstance(entry.data["country"], int): if isinstance(entry.data["country"], int):
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
@ -44,6 +44,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
if not entry.unique_id: if not entry.unique_id:
hass.config_entries.async_update_entry(entry, unique_id=entry.data["country"]) hass.config_entries.async_update_entry(entry, unique_id=entry.data["country"])
coordinator = await get_coordinator(hass)
if not coordinator.last_update_success:
await coordinator.async_config_entry_first_refresh()
for platform in PLATFORMS: for platform in PLATFORMS:
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform) hass.config_entries.async_forward_entry_setup(entry, platform)
@ -52,9 +56,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = all( return all(
await asyncio.gather( await asyncio.gather(
*[ *[
hass.config_entries.async_forward_entry_unload(entry, platform) hass.config_entries.async_forward_entry_unload(entry, platform)
@ -63,10 +67,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
) )
) )
return unload_ok
async def get_coordinator(
async def get_coordinator(hass): hass: HomeAssistant,
) -> update_coordinator.DataUpdateCoordinator:
"""Get the data update coordinator.""" """Get the data update coordinator."""
if DOMAIN in hass.data: if DOMAIN in hass.data:
return hass.data[DOMAIN] return hass.data[DOMAIN]

View File

@ -1,4 +1,8 @@
"""Config flow for Coronavirus integration.""" """Config flow for Coronavirus integration."""
from __future__ import annotations
from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
@ -15,13 +19,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_options = None _options = None
async def async_step_user(self, user_input=None): async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Handle the initial step.""" """Handle the initial step."""
errors = {} errors = {}
if self._options is None: if self._options is None:
self._options = {OPTION_WORLDWIDE: "Worldwide"}
coordinator = await get_coordinator(self.hass) coordinator = await get_coordinator(self.hass)
if not coordinator.last_update_success:
return self.async_abort(reason="cannot_connect")
self._options = {OPTION_WORLDWIDE: "Worldwide"}
for case in sorted( for case in sorted(
coordinator.data.values(), key=lambda case: case.country coordinator.data.values(), key=lambda case: case.country
): ):

View File

@ -7,6 +7,7 @@
} }
}, },
"abort": { "abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" "already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
} }
} }

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Service is already configured" "already_configured": "Service is already configured",
"cannot_connect": "Failed to connect"
}, },
"step": { "step": {
"user": { "user": {

View File

@ -3,7 +3,7 @@
"name": "DHCP Discovery", "name": "DHCP Discovery",
"documentation": "https://www.home-assistant.io/integrations/dhcp", "documentation": "https://www.home-assistant.io/integrations/dhcp",
"requirements": [ "requirements": [
"scapy==2.4.4", "aiodiscover==1.3.3" "scapy==2.4.4", "aiodiscover==1.3.4"
], ],
"codeowners": [ "codeowners": [
"@bdraco" "@bdraco"

View File

@ -507,5 +507,5 @@ def state_needs_accessory_mode(state):
or state.domain == MEDIA_PLAYER_DOMAIN or state.domain == MEDIA_PLAYER_DOMAIN
and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV
or state.domain == REMOTE_DOMAIN or state.domain == REMOTE_DOMAIN
and state.attributes.get(ATTR_SUPPORTED_FEATURES) & SUPPORT_ACTIVITY and state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & SUPPORT_ACTIVITY
) )

View File

@ -751,3 +751,20 @@ class Light(LightEntity):
"Light is deprecated, modify %s to extend LightEntity", "Light is deprecated, modify %s to extend LightEntity",
cls.__name__, cls.__name__,
) )
def legacy_supported_features(
supported_features: int, supported_color_modes: list[str] | None
) -> int:
"""Calculate supported features with backwards compatibility."""
# Backwards compatibility for supported_color_modes added in 2021.4
if supported_color_modes is None:
return supported_features
if any(mode in supported_color_modes for mode in COLOR_MODES_COLOR):
supported_features |= SUPPORT_COLOR
if any(mode in supported_color_modes for mode in COLOR_MODES_BRIGHTNESS):
supported_features |= SUPPORT_BRIGHTNESS
if COLOR_MODE_COLOR_TEMP in supported_color_modes:
supported_features |= SUPPORT_COLOR_TEMP
return supported_features

View File

@ -35,6 +35,7 @@ from homeassistant.components.light import (
SUPPORT_WHITE_VALUE, SUPPORT_WHITE_VALUE,
VALID_COLOR_MODES, VALID_COLOR_MODES,
LightEntity, LightEntity,
legacy_supported_features,
valid_supported_color_modes, valid_supported_color_modes,
) )
from homeassistant.const import ( from homeassistant.const import (
@ -458,7 +459,9 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
return self._supported_features return legacy_supported_features(
self._supported_features, self._config.get(CONF_SUPPORTED_COLOR_MODES)
)
def _set_flash_and_transition(self, message, **kwargs): def _set_flash_and_transition(self, message, **kwargs):
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:

View File

@ -7,6 +7,11 @@ from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_t
from homeassistant.const import ( from homeassistant.const import (
ATTR_SERVICE_DATA, ATTR_SERVICE_DATA,
EVENT_CALL_SERVICE, EVENT_CALL_SERVICE,
EVENT_HOMEASSISTANT_CLOSE,
EVENT_HOMEASSISTANT_FINAL_WRITE,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP,
EVENT_STATE_CHANGED, EVENT_STATE_CHANGED,
EVENT_TIME_CHANGED, EVENT_TIME_CHANGED,
MATCH_ALL, MATCH_ALL,
@ -37,6 +42,14 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
BLOCKED_EVENTS = [
EVENT_HOMEASSISTANT_CLOSE,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_FINAL_WRITE,
]
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the MQTT eventstream component.""" """Set up the MQTT eventstream component."""
@ -45,16 +58,15 @@ async def async_setup(hass, config):
pub_topic = conf.get(CONF_PUBLISH_TOPIC) pub_topic = conf.get(CONF_PUBLISH_TOPIC)
sub_topic = conf.get(CONF_SUBSCRIBE_TOPIC) sub_topic = conf.get(CONF_SUBSCRIBE_TOPIC)
ignore_event = conf.get(CONF_IGNORE_EVENT) ignore_event = conf.get(CONF_IGNORE_EVENT)
ignore_event.append(EVENT_TIME_CHANGED)
@callback @callback
def _event_publisher(event): def _event_publisher(event):
"""Handle events by publishing them on the MQTT queue.""" """Handle events by publishing them on the MQTT queue."""
if event.origin != EventOrigin.local: if event.origin != EventOrigin.local:
return return
if event.event_type == EVENT_TIME_CHANGED:
return
# User-defined events to ignore # Events to ignore
if event.event_type in ignore_event: if event.event_type in ignore_event:
return return
@ -84,6 +96,10 @@ async def async_setup(hass, config):
event_type = event.get("event_type") event_type = event.get("event_type")
event_data = event.get("event_data") event_data = event.get("event_data")
# Don't fire HOMEASSISTANT_* events on this instance
if event_type in BLOCKED_EVENTS:
return
# Special case handling for event STATE_CHANGED # Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects # We will try to convert state dicts back to State objects
# Copied over from the _handle_api_post_events_event method # Copied over from the _handle_api_post_events_event method

View File

@ -1,6 +1,8 @@
"""Support for MySensors sensors.""" """Support for MySensors sensors."""
from typing import Callable from typing import Callable
from awesomeversion import AwesomeVersion
from homeassistant.components import mysensors from homeassistant.components import mysensors
from homeassistant.components.mysensors import on_unload from homeassistant.components.mysensors import on_unload
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY
@ -115,7 +117,7 @@ class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity):
"""Return the unit of measurement of this entity.""" """Return the unit of measurement of this entity."""
set_req = self.gateway.const.SetReq set_req = self.gateway.const.SetReq
if ( if (
float(self.gateway.protocol_version) >= 1.5 AwesomeVersion(self.gateway.protocol_version) >= AwesomeVersion("1.5")
and set_req.V_UNIT_PREFIX in self._values and set_req.V_UNIT_PREFIX in self._values
): ):
return self._values[set_req.V_UNIT_PREFIX] return self._values[set_req.V_UNIT_PREFIX]

View File

@ -2,7 +2,7 @@
"domain": "spotify", "domain": "spotify",
"name": "Spotify", "name": "Spotify",
"documentation": "https://www.home-assistant.io/integrations/spotify", "documentation": "https://www.home-assistant.io/integrations/spotify",
"requirements": ["spotipy==2.17.1"], "requirements": ["spotipy==2.18.0"],
"zeroconf": ["_spotify-connect._tcp.local."], "zeroconf": ["_spotify-connect._tcp.local."],
"dependencies": ["http"], "dependencies": ["http"],
"codeowners": ["@frenck"], "codeowners": ["@frenck"],

View File

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
MAJOR_VERSION = 2021 MAJOR_VERSION = 2021
MINOR_VERSION = 4 MINOR_VERSION = 4
PATCH_VERSION = "4" PATCH_VERSION = "5"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 8, 0) REQUIRED_PYTHON_VER = (3, 8, 0)

View File

@ -1144,10 +1144,7 @@ class Script:
self._log("Already running", level=LOGSEVERITY[self._max_exceeded]) self._log("Already running", level=LOGSEVERITY[self._max_exceeded])
script_execution_set("failed_single") script_execution_set("failed_single")
return return
if self.script_mode == SCRIPT_MODE_RESTART: if self.script_mode != SCRIPT_MODE_RESTART and self.runs == self.max_runs:
self._log("Restarting")
await self.async_stop(update_state=False)
elif len(self._runs) == self.max_runs:
if self._max_exceeded != "SILENT": if self._max_exceeded != "SILENT":
self._log( self._log(
"Maximum number of runs exceeded", "Maximum number of runs exceeded",
@ -1186,6 +1183,14 @@ class Script:
self._hass, self, cast(dict, variables), context, self._log_exceptions self._hass, self, cast(dict, variables), context, self._log_exceptions
) )
self._runs.append(run) self._runs.append(run)
if self.script_mode == SCRIPT_MODE_RESTART:
# When script mode is SCRIPT_MODE_RESTART, first add the new run and then
# stop any other runs. If we stop other runs first, self.is_running will
# return false after the other script runs were stopped until our task
# resumes running.
self._log("Restarting")
await self.async_stop(update_state=False, spare=run)
if started_action: if started_action:
self._hass.async_run_job(started_action) self._hass.async_run_job(started_action)
self.last_triggered = utcnow() self.last_triggered = utcnow()
@ -1198,17 +1203,21 @@ class Script:
self._changed() self._changed()
raise raise
async def _async_stop(self, update_state): async def _async_stop(self, update_state, spare=None):
aws = [asyncio.create_task(run.async_stop()) for run in self._runs] aws = [
asyncio.create_task(run.async_stop()) for run in self._runs if run != spare
]
if not aws: if not aws:
return return
await asyncio.wait(aws) await asyncio.wait(aws)
if update_state: if update_state:
self._changed() self._changed()
async def async_stop(self, update_state: bool = True) -> None: async def async_stop(
self, update_state: bool = True, spare: _ScriptRun | None = None
) -> None:
"""Stop running script.""" """Stop running script."""
await asyncio.shield(self._async_stop(update_state)) await asyncio.shield(self._async_stop(update_state, spare))
async def _async_get_condition(self, config): async def _async_get_condition(self, config):
if isinstance(config, template.Template): if isinstance(config, template.Template):

View File

@ -1,6 +1,6 @@
PyJWT==1.7.1 PyJWT==1.7.1
PyNaCl==1.3.0 PyNaCl==1.3.0
aiodiscover==1.3.3 aiodiscover==1.3.4
aiohttp==3.7.4.post0 aiohttp==3.7.4.post0
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
astral==1.10.1 astral==1.10.1

View File

@ -22,6 +22,7 @@ BASE_PLATFORMS = {
"air_quality", "air_quality",
"alarm_control_panel", "alarm_control_panel",
"binary_sensor", "binary_sensor",
"camera",
"climate", "climate",
"cover", "cover",
"device_tracker", "device_tracker",

View File

@ -147,7 +147,7 @@ aioazuredevops==1.3.5
aiobotocore==0.11.1 aiobotocore==0.11.1
# homeassistant.components.dhcp # homeassistant.components.dhcp
aiodiscover==1.3.3 aiodiscover==1.3.4
# homeassistant.components.dnsip # homeassistant.components.dnsip
# homeassistant.components.minecraft_server # homeassistant.components.minecraft_server
@ -2117,7 +2117,7 @@ spiderpy==1.4.2
spotcrime==1.0.4 spotcrime==1.0.4
# homeassistant.components.spotify # homeassistant.components.spotify
spotipy==2.17.1 spotipy==2.18.0
# homeassistant.components.recorder # homeassistant.components.recorder
# homeassistant.components.sql # homeassistant.components.sql

View File

@ -84,7 +84,7 @@ aioazuredevops==1.3.5
aiobotocore==0.11.1 aiobotocore==0.11.1
# homeassistant.components.dhcp # homeassistant.components.dhcp
aiodiscover==1.3.3 aiodiscover==1.3.4
# homeassistant.components.dnsip # homeassistant.components.dnsip
# homeassistant.components.minecraft_server # homeassistant.components.minecraft_server
@ -1104,7 +1104,7 @@ speedtest-cli==2.1.3
spiderpy==1.4.2 spiderpy==1.4.2
# homeassistant.components.spotify # homeassistant.components.spotify
spotipy==2.17.1 spotipy==2.18.0
# homeassistant.components.recorder # homeassistant.components.recorder
# homeassistant.components.sql # homeassistant.components.sql

View File

@ -156,8 +156,8 @@ async def test_motion_light(hass):
# Turn on motion # Turn on motion
hass.states.async_set("binary_sensor.kitchen", "on") hass.states.async_set("binary_sensor.kitchen", "on")
# Can't block till done because delay is active # Can't block till done because delay is active
# So wait 5 event loop iterations to process script # So wait 10 event loop iterations to process script
for _ in range(5): for _ in range(10):
await asyncio.sleep(0) await asyncio.sleep(0)
assert len(turn_on_calls) == 1 assert len(turn_on_calls) == 1
@ -165,7 +165,7 @@ async def test_motion_light(hass):
# Test light doesn't turn off if motion stays # Test light doesn't turn off if motion stays
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200))
for _ in range(5): for _ in range(10):
await asyncio.sleep(0) await asyncio.sleep(0)
assert len(turn_off_calls) == 0 assert len(turn_off_calls) == 0
@ -173,7 +173,7 @@ async def test_motion_light(hass):
# Test light turns off off 120s after last motion # Test light turns off off 120s after last motion
hass.states.async_set("binary_sensor.kitchen", "off") hass.states.async_set("binary_sensor.kitchen", "off")
for _ in range(5): for _ in range(10):
await asyncio.sleep(0) await asyncio.sleep(0)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120))
@ -184,7 +184,7 @@ async def test_motion_light(hass):
# Test restarting the script # Test restarting the script
hass.states.async_set("binary_sensor.kitchen", "on") hass.states.async_set("binary_sensor.kitchen", "on")
for _ in range(5): for _ in range(10):
await asyncio.sleep(0) await asyncio.sleep(0)
assert len(turn_on_calls) == 2 assert len(turn_on_calls) == 2
@ -192,7 +192,7 @@ async def test_motion_light(hass):
hass.states.async_set("binary_sensor.kitchen", "off") hass.states.async_set("binary_sensor.kitchen", "off")
for _ in range(5): for _ in range(10):
await asyncio.sleep(0) await asyncio.sleep(0)
hass.states.async_set("binary_sensor.kitchen", "on") hass.states.async_set("binary_sensor.kitchen", "on")

View File

@ -1,9 +1,14 @@
"""Test the Coronavirus config flow.""" """Test the Coronavirus config flow."""
from unittest.mock import MagicMock, patch
from aiohttp import ClientError
from homeassistant import config_entries, setup from homeassistant import config_entries, setup
from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE
from homeassistant.core import HomeAssistant
async def test_form(hass): async def test_form(hass: HomeAssistant) -> None:
"""Test we get the form.""" """Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {}) await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -24,3 +29,22 @@ async def test_form(hass):
} }
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4 assert len(hass.states.async_all()) == 4
@patch(
"coronavirus.get_cases",
side_effect=ClientError,
)
async def test_abort_on_connection_error(
mock_get_cases: MagicMock, hass: HomeAssistant
) -> None:
"""Test we abort on connection error."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert "type" in result
assert result["type"] == "abort"
assert "reason" in result
assert result["reason"] == "cannot_connect"

View File

@ -1,12 +1,18 @@
"""Test init of Coronavirus integration.""" """Test init of Coronavirus integration."""
from unittest.mock import MagicMock, patch
from aiohttp import ClientError
from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE
from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, mock_registry from tests.common import MockConfigEntry, mock_registry
async def test_migration(hass): async def test_migration(hass: HomeAssistant) -> None:
"""Test that we can migrate coronavirus to stable unique ID.""" """Test that we can migrate coronavirus to stable unique ID."""
nl_entry = MockConfigEntry(domain=DOMAIN, title="Netherlands", data={"country": 34}) nl_entry = MockConfigEntry(domain=DOMAIN, title="Netherlands", data={"country": 34})
nl_entry.add_to_hass(hass) nl_entry.add_to_hass(hass)
@ -47,3 +53,20 @@ async def test_migration(hass):
assert nl_entry.unique_id == "Netherlands" assert nl_entry.unique_id == "Netherlands"
assert worldwide_entry.unique_id == OPTION_WORLDWIDE assert worldwide_entry.unique_id == OPTION_WORLDWIDE
@patch(
"coronavirus.get_cases",
side_effect=ClientError,
)
async def test_config_entry_not_ready(
mock_get_cases: MagicMock, hass: HomeAssistant
) -> None:
"""Test the configuration entry not ready."""
entry = MockConfigEntry(domain=DOMAIN, title="Netherlands", data={"country": 34})
entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
assert entry.state == ENTRY_STATE_SETUP_RETRY

View File

@ -145,6 +145,8 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
hass.states.async_set("camera.one", "on") hass.states.async_set("camera.one", "on")
hass.states.async_set("camera.existing", "on") hass.states.async_set("camera.existing", "on")
hass.states.async_set("media_player.two", "on", {"device_class": "tv"}) hass.states.async_set("media_player.two", "on", {"device_class": "tv"})
hass.states.async_set("remote.standard", "on")
hass.states.async_set("remote.activity", "on", {"supported_features": 4})
bridge_mode_entry = MockConfigEntry( bridge_mode_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -178,7 +180,7 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{"include_domains": ["camera", "media_player", "light"]}, {"include_domains": ["camera", "media_player", "light", "remote"]},
) )
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "pairing" assert result2["step_id"] == "pairing"
@ -205,7 +207,7 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
"filter": { "filter": {
"exclude_domains": [], "exclude_domains": [],
"exclude_entities": [], "exclude_entities": [],
"include_domains": ["media_player", "light"], "include_domains": ["media_player", "light", "remote"],
"include_entities": [], "include_entities": [],
}, },
"exclude_accessory_mode": True, "exclude_accessory_mode": True,
@ -222,7 +224,8 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
# 3 - new bridge # 3 - new bridge
# 4 - camera.one in accessory mode # 4 - camera.one in accessory mode
# 5 - media_player.two in accessory mode # 5 - media_player.two in accessory mode
assert len(mock_setup_entry.mock_calls) == 5 # 6 - remote.activity in accessory mode
assert len(mock_setup_entry.mock_calls) == 6
async def test_import(hass): async def test_import(hass):

View File

@ -234,10 +234,10 @@ async def test_rgb_light(hass, mqtt_mock):
state = hass.states.get("light.test") state = hass.states.get("light.test")
expected_features = ( expected_features = (
light.SUPPORT_TRANSITION light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR | light.SUPPORT_COLOR
| light.SUPPORT_FLASH | light.SUPPORT_FLASH
| light.SUPPORT_BRIGHTNESS | light.SUPPORT_TRANSITION
) )
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
@ -261,7 +261,8 @@ async def test_no_color_brightness_color_temp_white_val_if_no_topics(hass, mqtt_
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("rgb_color") is None assert state.attributes.get("rgb_color") is None
assert state.attributes.get("brightness") is None assert state.attributes.get("brightness") is None
assert state.attributes.get("color_temp") is None assert state.attributes.get("color_temp") is None
@ -310,7 +311,16 @@ async def test_controlling_state_via_topic(hass, mqtt_mock):
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 191 expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
| light.SUPPORT_WHITE_VALUE
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("rgb_color") is None assert state.attributes.get("rgb_color") is None
assert state.attributes.get("brightness") is None assert state.attributes.get("brightness") is None
assert state.attributes.get("color_temp") is None assert state.attributes.get("color_temp") is None
@ -429,7 +439,15 @@ async def test_controlling_state_via_topic2(hass, mqtt_mock, caplog):
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44 expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("brightness") is None assert state.attributes.get("brightness") is None
assert state.attributes.get("color_mode") is None assert state.attributes.get("color_mode") is None
assert state.attributes.get("color_temp") is None assert state.attributes.get("color_temp") is None
@ -610,7 +628,16 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
assert state.attributes.get("effect") == "random" assert state.attributes.get("effect") == "random"
assert state.attributes.get("color_temp") == 100 assert state.attributes.get("color_temp") == 100
assert state.attributes.get("white_value") == 50 assert state.attributes.get("white_value") == 50
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 191 expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
| light.SUPPORT_WHITE_VALUE
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "light.test") await common.async_turn_on(hass, "light.test")
@ -738,7 +765,15 @@ async def test_sending_mqtt_commands_and_optimistic2(hass, mqtt_mock):
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_ON assert state.state == STATE_ON
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44 expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("brightness") == 95 assert state.attributes.get("brightness") == 95
assert state.attributes.get("color_mode") == "rgb" assert state.attributes.get("color_mode") == "rgb"
assert state.attributes.get("color_temp") is None assert state.attributes.get("color_temp") is None
@ -1313,7 +1348,10 @@ async def test_effect(hass, mqtt_mock):
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44 expected_features = (
light.SUPPORT_EFFECT | light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
await common.async_turn_on(hass, "light.test") await common.async_turn_on(hass, "light.test")
@ -1373,7 +1411,8 @@ async def test_flash_short_and_long(hass, mqtt_mock):
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
await common.async_turn_on(hass, "light.test", flash="short") await common.async_turn_on(hass, "light.test", flash="short")
@ -1431,8 +1470,8 @@ async def test_transition(hass, mqtt_mock):
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
await common.async_turn_on(hass, "light.test", transition=15) await common.async_turn_on(hass, "light.test", transition=15)
mqtt_mock.async_publish.assert_called_once_with( mqtt_mock.async_publish.assert_called_once_with(
@ -1523,7 +1562,15 @@ async def test_invalid_values(hass, mqtt_mock):
state = hass.states.get("light.test") state = hass.states.get("light.test")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 187 expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
| light.SUPPORT_WHITE_VALUE
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("rgb_color") is None assert state.attributes.get("rgb_color") is None
assert state.attributes.get("brightness") is None assert state.attributes.get("brightness") is None
assert state.attributes.get("white_value") is None assert state.attributes.get("white_value") is None

View File

@ -3,7 +3,7 @@ import json
from unittest.mock import ANY, patch from unittest.mock import ANY, patch
import homeassistant.components.mqtt_eventstream as eventstream import homeassistant.components.mqtt_eventstream as eventstream
from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
from homeassistant.core import State, callback from homeassistant.core import State, callback
from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.json import JSONEncoder
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -114,6 +114,7 @@ async def test_time_event_does_not_send_message(hass, mqtt_mock):
mqtt_mock.async_publish.reset_mock() mqtt_mock.async_publish.reset_mock()
async_fire_time_changed(hass, dt_util.utcnow()) async_fire_time_changed(hass, dt_util.utcnow())
await hass.async_block_till_done()
assert not mqtt_mock.async_publish.called assert not mqtt_mock.async_publish.called
@ -140,6 +141,33 @@ async def test_receiving_remote_event_fires_hass_event(hass, mqtt_mock):
assert len(calls) == 1 assert len(calls) == 1
await hass.async_block_till_done()
async def test_receiving_blocked_event_fires_hass_event(hass, mqtt_mock):
"""Test the receiving of blocked event does not fire."""
sub_topic = "foo"
assert await add_eventstream(hass, sub_topic=sub_topic)
await hass.async_block_till_done()
calls = []
@callback
def listener(_):
calls.append(1)
hass.bus.async_listen(MATCH_ALL, listener)
await hass.async_block_till_done()
for event in eventstream.BLOCKED_EVENTS:
payload = json.dumps({"event_type": event, "event_data": {}}, cls=JSONEncoder)
async_fire_mqtt_message(hass, sub_topic, payload)
await hass.async_block_till_done()
assert len(calls) == 0
await hass.async_block_till_done()
async def test_ignored_event_doesnt_send_over_stream(hass, mqtt_mock): async def test_ignored_event_doesnt_send_over_stream(hass, mqtt_mock):
"""Test the ignoring of sending events if defined.""" """Test the ignoring of sending events if defined."""
@ -159,6 +187,7 @@ async def test_ignored_event_doesnt_send_over_stream(hass, mqtt_mock):
# Set a state of an entity # Set a state of an entity
mock_state_change_event(hass, State(e_id, "on")) mock_state_change_event(hass, State(e_id, "on"))
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done()
assert not mqtt_mock.async_publish.called assert not mqtt_mock.async_publish.called