Merge pull request #36238 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2020-05-28 17:17:56 -07:00 committed by GitHub
commit 7194b74580
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 206 additions and 81 deletions

View File

@ -375,6 +375,11 @@ class AirVisualEntity(Entity):
"""Return the icon."""
return self._icon
@property
def should_poll(self) -> bool:
"""Disable polling."""
return False
@property
def unit_of_measurement(self):
"""Return the unit the value is expressed in."""

View File

@ -452,6 +452,11 @@ class ADBDevice(MediaPlayerEntity):
"""Provide the last ADB command's response as an attribute."""
return {"adb_response": self._adb_response}
@property
def media_image_hash(self):
"""Hash value for media image."""
return f"{datetime.now().timestamp()}" if self._screencap else None
@property
def name(self):
"""Return the device name."""
@ -497,11 +502,6 @@ class ADBDevice(MediaPlayerEntity):
"""Raw image data."""
return self.aftv.adb_screencap()
@property
def media_image_hash(self):
"""Hash value for media image."""
return f"{datetime.now().timestamp()}"
@adb_decorator()
def media_play(self):
"""Send play command."""

View File

@ -85,9 +85,10 @@ def setup_internal_discovery(hass: HomeAssistant) -> None:
_LOGGER.debug("Starting internal pychromecast discovery.")
listener, browser = pychromecast.start_discovery(
internal_add_callback, internal_remove_callback
internal_add_callback,
internal_remove_callback,
ChromeCastZeroconf.get_zeroconf(),
)
ChromeCastZeroconf.set_zeroconf(browser.zc)
def stop_discovery(event):
"""Stop discovery of new chromecasts."""

View File

@ -3,8 +3,8 @@
"name": "Google Cast",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==5.1.0"],
"after_dependencies": ["cloud"],
"requirements": ["pychromecast==5.3.0"],
"after_dependencies": ["cloud","zeroconf"],
"zeroconf": ["_googlecast._tcp.local."],
"codeowners": ["@emontnemery"]
}

View File

@ -14,6 +14,7 @@ from pychromecast.socket_client import (
)
import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
from homeassistant.components.media_player.const import (
MEDIA_TYPE_MOVIE,
@ -170,6 +171,7 @@ async def _async_setup_platform(
for chromecast in list(hass.data[KNOWN_CHROMECAST_INFO_KEY]):
async_cast_discovered(chromecast)
ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass))
hass.async_add_executor_job(setup_internal_discovery, hass)

View File

@ -2,7 +2,7 @@
"domain": "devolo_home_control",
"name": "devolo_home_control",
"documentation": "https://www.home-assistant.io/integrations/devolo_home_control",
"requirements": ["devolo-home-control-api==0.10.0"],
"requirements": ["devolo-home-control-api==0.11.0"],
"config_flow": true,
"codeowners": [
"@2Fake",

View File

@ -17,10 +17,6 @@ from homeassistant.const import (
)
from homeassistant.core import CoreState, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
@ -113,7 +109,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
async_add_entities(devices)
update_entities_telegram = partial(async_dispatcher_send, hass, DOMAIN)
def update_entities_telegram(telegram):
"""Update entities with latest telegram and trigger state update."""
# Make all device entities aware of new telegram
for device in devices:
device.update_data(telegram)
# Creates an asyncio.Protocol factory for reading DSMR telegrams from
# serial and calls update_entities_telegram to update entities on arrival
@ -187,17 +187,12 @@ class DSMREntity(Entity):
self._config = config
self.telegram = {}
async def async_added_to_hass(self):
"""When entity is added to hass."""
self.async_on_remove(
async_dispatcher_connect(self.hass, DOMAIN, self.update_data)
)
@callback
def update_data(self, telegram):
"""Update data."""
self.telegram = telegram
self.async_write_ha_state()
if self.hass:
self.async_write_ha_state()
def get_dsmr_object_attr(self, attribute):
"""Read attribute from last received telegram for this DSMR object."""

View File

@ -61,6 +61,10 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Initialize config entry which represents the HEOS controller."""
# For backwards compat
if entry.unique_id is None:
hass.config_entries.async_update_entry(entry, unique_id=DOMAIN)
host = entry.data[CONF_HOST]
# Setting all_progress_events=False ensures that we only receive a
# media position update upon start of playback or when media changes

View File

@ -33,12 +33,16 @@ class HeosFlowHandler(config_entries.ConfigFlow):
# Abort if other flows in progress or an entry already exists
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="already_setup")
await self.async_set_unique_id(DOMAIN)
# Show selection form
return self.async_show_form(step_id="user")
async def async_step_import(self, user_input=None):
"""Occurs when an entry is setup through config."""
host = user_input[CONF_HOST]
# raise_on_progress is False here in case ssdp discovers
# heos first which would block the import
await self.async_set_unique_id(DOMAIN, raise_on_progress=False)
return self.async_create_entry(title=format_title(host), data={CONF_HOST: host})
async def async_step_user(self, user_input=None):

View File

@ -29,11 +29,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import Event, ServiceCall, callback
from homeassistant.exceptions import (
ConfigEntryNotReady,
HomeAssistantError,
Unauthorized,
)
from homeassistant.exceptions import HomeAssistantError, Unauthorized
from homeassistant.helpers import config_validation as cv, event, template
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
@ -649,13 +645,7 @@ async def async_setup_entry(hass, entry):
tls_version=tls_version,
)
result: str = await hass.data[DATA_MQTT].async_connect()
if result == CONNECTION_FAILED:
return False
if result == CONNECTION_FAILED_RECOVERABLE:
raise ConfigEntryNotReady
await hass.data[DATA_MQTT].async_connect()
async def async_stop_mqtt(_event: Event):
"""Stop MQTT component."""
@ -835,15 +825,14 @@ class MQTT:
self._mqttc.connect, self.broker, self.port, self.keepalive
)
except OSError as err:
_LOGGER.error("Failed to connect due to exception: %s", err)
return CONNECTION_FAILED_RECOVERABLE
_LOGGER.error("Failed to connect to MQTT server due to exception: %s", err)
if result != 0:
_LOGGER.error("Failed to connect: %s", mqtt.error_string(result))
return CONNECTION_FAILED
if result is not None and result != 0:
_LOGGER.error(
"Failed to connect to MQTT server: %s", mqtt.error_string(result)
)
self._mqttc.loop_start()
return CONNECTION_SUCCESS
async def async_disconnect(self):
"""Stop the MQTT client."""
@ -898,6 +887,7 @@ class MQTT:
This method is a coroutine.
"""
_LOGGER.debug("Unsubscribing from %s", topic)
async with self._paho_lock:
result: int = None
result, _ = await self.hass.async_add_executor_job(
@ -933,6 +923,7 @@ class MQTT:
return
self.connected = True
_LOGGER.info("Connected to MQTT server (%s)", result_code)
# Group subscriptions to only re-subscribe once for each topic.
keyfunc = attrgetter("topic")
@ -999,7 +990,7 @@ class MQTT:
def _mqtt_on_disconnect(self, _mqttc, _userdata, result_code: int) -> None:
"""Disconnected callback."""
self.connected = False
_LOGGER.warning("Disconnected from MQTT (%s).", result_code)
_LOGGER.warning("Disconnected from MQTT server (%s)", result_code)
def _raise_on_error(result_code: int) -> None:

View File

@ -530,7 +530,7 @@ class MqttCover(
async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
position = float(kwargs[ATTR_TILT_POSITION])
position = kwargs[ATTR_TILT_POSITION]
# The position needs to be between min and max
level = self.find_in_range_from_percent(position)
@ -550,10 +550,7 @@ class MqttCover(
percentage_position = position
if set_position_template is not None:
position = set_position_template.async_render(**kwargs)
elif (
self._config[CONF_POSITION_OPEN] != 100
and self._config[CONF_POSITION_CLOSED] != 0
):
else:
position = self.find_in_range_from_percent(position, COVER_PAYLOAD)
mqtt.async_publish(

View File

@ -138,13 +138,17 @@ class Trigger:
self.remove_signal = remove_signal
self.type = config[CONF_TYPE]
self.subtype = config[CONF_SUBTYPE]
self.topic = config[CONF_TOPIC]
self.payload = config[CONF_PAYLOAD]
self.qos = config[CONF_QOS]
topic_changed = self.topic != config[CONF_TOPIC]
self.topic = config[CONF_TOPIC]
# Unsubscribe+subscribe if this trigger is in use
for trig in self.trigger_instances:
await trig.async_attach_trigger()
# Unsubscribe+subscribe if this trigger is in use and topic has changed
# If topic is same unsubscribe+subscribe will execute in the wrong order
# because unsubscribe is done with help of async_create_task
if topic_changed:
for trig in self.trigger_instances:
await trig.async_attach_trigger()
def detach_trigger(self):
"""Remove MQTT device trigger."""

View File

@ -297,7 +297,7 @@ class ONVIFDevice:
try:
ptz_service = self.device.create_ptz_service()
presets = await ptz_service.GetPresets(profile.token)
profile.ptz.presets = [preset.token for preset in presets]
profile.ptz.presets = [preset.token for preset in presets if preset]
except (Fault, ServerDisconnectedError):
# It's OK if Presets aren't supported
profile.ptz.presets = []

View File

@ -222,7 +222,12 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
await self.coordinator.roku.remote("home")
appl = next(
(app for app in self.coordinator.data.apps if app.name == source), None
(
app
for app in self.coordinator.data.apps
if source in (app.name, app.app_id)
),
None,
)
if appl is not None:

View File

@ -500,6 +500,8 @@ class TodoistProjectData:
events = []
for task in project_task_data:
if task["due"] is None:
continue
due_date = _parse_due_date(task["due"])
if start_date < due_date < end_date:
event = {

View File

@ -97,7 +97,7 @@ class HaServiceBrowser(ServiceBrowser):
# To avoid overwhemling the system we pre-filter here and only process
# DNSPointers for the configured record name (type)
#
if record.name != self.type or not isinstance(record, DNSPointer):
if record.name not in self.types or not isinstance(record, DNSPointer):
return
super().update_record(zc, now, record)
@ -181,6 +181,7 @@ def setup(hass, config):
if not service_info:
# Prevent the browser thread from collapsing as
# service_info can be None
_LOGGER.debug("Failed to get info for device %s", name)
return
info = info_from_service(service_info)
@ -216,11 +217,12 @@ def setup(hass, config):
)
)
for service in ZEROCONF:
HaServiceBrowser(zeroconf, service, handlers=[service_update])
types = list(ZEROCONF)
if HOMEKIT_TYPE not in ZEROCONF:
HaServiceBrowser(zeroconf, HOMEKIT_TYPE, handlers=[service_update])
types.append(HOMEKIT_TYPE)
HaServiceBrowser(zeroconf, types, handlers=[service_update])
return True

View File

@ -2,7 +2,7 @@
"domain": "zeroconf",
"name": "Zero-configuration networking (zeroconf)",
"documentation": "https://www.home-assistant.io/integrations/zeroconf",
"requirements": ["zeroconf==0.26.1"],
"requirements": ["zeroconf==0.26.3"],
"dependencies": ["api"],
"codeowners": ["@robbiet480", "@Kane610"],
"quality_scale": "internal"

View File

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

View File

@ -25,7 +25,7 @@ ruamel.yaml==0.15.100
sqlalchemy==1.3.16
voluptuous-serialize==2.3.0
voluptuous==0.11.7
zeroconf==0.26.1
zeroconf==0.26.3
pycryptodome>=3.6.6

View File

@ -469,7 +469,7 @@ deluge-client==1.7.1
denonavr==0.8.1
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.10.0
devolo-home-control-api==0.11.0
# homeassistant.components.directv
directv==0.3.0
@ -1245,7 +1245,7 @@ pycfdns==0.0.1
pychannels==1.0.0
# homeassistant.components.cast
pychromecast==5.1.0
pychromecast==5.3.0
# homeassistant.components.cmus
pycmus==0.1.1
@ -2239,7 +2239,7 @@ youtube_dl==2020.05.08
zengge==0.2
# homeassistant.components.zeroconf
zeroconf==0.26.1
zeroconf==0.26.3
# homeassistant.components.zha
zha-quirks==0.0.39

View File

@ -203,7 +203,7 @@ defusedxml==0.6.0
denonavr==0.8.1
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.10.0
devolo-home-control-api==0.11.0
# homeassistant.components.directv
directv==0.3.0
@ -527,7 +527,7 @@ pyblackbird==0.5
pybotvac==0.0.17
# homeassistant.components.cast
pychromecast==5.1.0
pychromecast==5.3.0
# homeassistant.components.coolmaster
pycoolmasternet==0.0.4
@ -900,7 +900,7 @@ xmltodict==0.12.0
ya_ma==0.3.8
# homeassistant.components.zeroconf
zeroconf==0.26.1
zeroconf==0.26.3
# homeassistant.components.zha
zha-quirks==0.0.39

View File

@ -6,7 +6,8 @@ from pyheos import HeosError
from homeassistant import data_entry_flow
from homeassistant.components import heos, ssdp
from homeassistant.components.heos.config_flow import HeosFlowHandler
from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS
from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS, DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_SSDP
from homeassistant.const import CONF_HOST
from tests.async_mock import patch
@ -55,6 +56,7 @@ async def test_create_entry_when_host_valid(hass, controller):
heos.DOMAIN, context={"source": "user"}, data=data
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["result"].unique_id == DOMAIN
assert result["title"] == "Controller (127.0.0.1)"
assert result["data"] == data
assert controller.connect.call_count == 1
@ -70,6 +72,7 @@ async def test_create_entry_when_friendly_name_valid(hass, controller):
heos.DOMAIN, context={"source": "user"}, data=data
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["result"].unique_id == DOMAIN
assert result["title"] == "Controller (127.0.0.1)"
assert result["data"] == {CONF_HOST: "127.0.0.1"}
assert controller.connect.call_count == 1
@ -79,28 +82,34 @@ async def test_create_entry_when_friendly_name_valid(hass, controller):
async def test_discovery_shows_create_form(hass, controller, discovery_data):
"""Test discovery shows form to confirm setup and subsequent abort."""
await hass.config_entries.flow.async_init(
heos.DOMAIN, context={"source": "ssdp"}, data=discovery_data
)
await hass.async_block_till_done()
assert len(hass.config_entries.flow.async_progress()) == 1
flows_in_progress = hass.config_entries.flow.async_progress()
assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN
assert len(flows_in_progress) == 1
assert hass.data[DATA_DISCOVERED_HOSTS] == {"Office (127.0.0.1)": "127.0.0.1"}
port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port
discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/"
discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom"
await hass.config_entries.flow.async_init(
heos.DOMAIN, context={"source": "ssdp"}, data=discovery_data
)
await hass.async_block_till_done()
assert len(hass.config_entries.flow.async_progress()) == 1
flows_in_progress = hass.config_entries.flow.async_progress()
assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN
assert len(flows_in_progress) == 1
assert hass.data[DATA_DISCOVERED_HOSTS] == {
"Office (127.0.0.1)": "127.0.0.1",
"Bedroom (127.0.0.2)": "127.0.0.2",
}
async def test_disovery_flow_aborts_already_setup(
async def test_discovery_flow_aborts_already_setup(
hass, controller, discovery_data, config_entry
):
"""Test discovery flow aborts when entry already setup."""
@ -110,3 +119,34 @@ async def test_disovery_flow_aborts_already_setup(
result = await flow.async_step_ssdp(discovery_data)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_setup"
async def test_discovery_sets_the_unique_id(hass, controller, discovery_data):
"""Test discovery sets the unique id."""
port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port
discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/"
discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom"
await hass.config_entries.flow.async_init(
heos.DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data
)
await hass.async_block_till_done()
flows_in_progress = hass.config_entries.flow.async_progress()
assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN
assert len(flows_in_progress) == 1
assert hass.data[DATA_DISCOVERED_HOSTS] == {"Bedroom (127.0.0.2)": "127.0.0.2"}
async def test_import_sets_the_unique_id(hass, controller):
"""Test import sets the unique id."""
with patch("homeassistant.components.heos.async_setup_entry", return_value=True):
result = await hass.config_entries.flow.async_init(
heos.DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_HOST: "127.0.0.2"},
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["result"].unique_id == DOMAIN

View File

@ -31,6 +31,7 @@ async def test_async_setup_creates_entry(hass, config):
entry = entries[0]
assert entry.title == "Controller (127.0.0.1)"
assert entry.data == {CONF_HOST: "127.0.0.1"}
assert entry.unique_id == DOMAIN
async def test_async_setup_updates_entry(hass, config_entry, config, controller):
@ -44,6 +45,7 @@ async def test_async_setup_updates_entry(hass, config_entry, config, controller)
entry = entries[0]
assert entry.title == "Controller (127.0.0.2)"
assert entry.data == {CONF_HOST: "127.0.0.2"}
assert entry.unique_id == DOMAIN
async def test_async_setup_returns_true(hass, config_entry, config):

View File

@ -14,6 +14,7 @@ from tests.common import (
assert_lists_same,
async_fire_mqtt_message,
async_get_device_automations,
async_mock_mqtt_component,
async_mock_service,
mock_device_registry,
mock_registry,
@ -456,7 +457,7 @@ async def test_if_fires_on_mqtt_message_after_update(
await hass.async_block_till_done()
assert len(calls) == 1
# Update the trigger
# Update the trigger with different topic
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2)
await hass.async_block_till_done()
@ -468,6 +469,65 @@ async def test_if_fires_on_mqtt_message_after_update(
await hass.async_block_till_done()
assert len(calls) == 2
# Update the trigger with same topic
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2)
await hass.async_block_till_done()
async_fire_mqtt_message(hass, "foobar/triggers/button1", "")
await hass.async_block_till_done()
assert len(calls) == 2
async_fire_mqtt_message(hass, "foobar/triggers/buttonOne", "")
await hass.async_block_till_done()
assert len(calls) == 3
async def test_no_resubscribe_same_topic(hass, device_reg, mqtt_mock):
"""Test subscription to topics without change."""
mock_mqtt = await async_mock_mqtt_component(hass)
config_entry = MockConfigEntry(domain=DOMAIN, data={})
config_entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, config_entry)
data1 = (
'{ "automation_type":"trigger",'
' "device":{"identifiers":["0AFFD2"]},'
' "topic": "foobar/triggers/button1",'
' "type": "button_short_press",'
' "subtype": "button_1" }'
)
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1)
await hass.async_block_till_done()
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": device_entry.id,
"discovery_id": "bla1",
"type": "button_short_press",
"subtype": "button_1",
},
"action": {
"service": "test.automation",
"data_template": {"some": ("short_press")},
},
},
]
},
)
call_count = mock_mqtt.async_subscribe.call_count
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1)
await hass.async_block_till_done()
assert mock_mqtt.async_subscribe.call_count == call_count
async def test_not_fires_on_mqtt_message_after_remove_by_mqtt(
hass, device_reg, calls, mqtt_mock

View File

@ -18,7 +18,6 @@ from homeassistant.const import (
TEMP_CELSIUS,
)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
@ -678,23 +677,24 @@ async def test_setup_embedded_with_embedded(hass):
assert _start.call_count == 1
async def test_setup_fails_if_no_connect_broker(hass):
async def test_setup_logs_error_if_no_connect_broker(hass, caplog):
"""Test for setup failure if connection to broker is missing."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})
with patch("paho.mqtt.client.Client") as mock_client:
mock_client().connect = lambda *args: 1
assert not await mqtt.async_setup_entry(hass, entry)
assert await mqtt.async_setup_entry(hass, entry)
assert "Failed to connect to MQTT server:" in caplog.text
async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass):
async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass, caplog):
"""Test for setup failure if connection to broker is missing."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})
with patch("paho.mqtt.client.Client") as mock_client:
mock_client().connect = MagicMock(side_effect=OSError("Connection error"))
with pytest.raises(ConfigEntryNotReady):
await mqtt.async_setup_entry(hass, entry)
assert await mqtt.async_setup_entry(hass, entry)
assert "Failed to connect to MQTT server due to exception:" in caplog.text
async def test_setup_uses_certificate_on_certificate_set_to_auto(hass, mock_mqtt):

View File

@ -332,7 +332,7 @@ async def test_services(
remote_mock.assert_called_once_with("home")
with patch("homeassistant.components.roku.Roku.launch") as remote_mock:
with patch("homeassistant.components.roku.Roku.launch") as launch_mock:
await hass.services.async_call(
MP_DOMAIN,
SERVICE_SELECT_SOURCE,
@ -340,7 +340,17 @@ async def test_services(
blocking=True,
)
remote_mock.assert_called_once_with("12")
launch_mock.assert_called_once_with("12")
with patch("homeassistant.components.roku.Roku.launch") as launch_mock:
await hass.services.async_call(
MP_DOMAIN,
SERVICE_SELECT_SOURCE,
{ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: 12},
blocking=True,
)
launch_mock.assert_called_once_with("12")
async def test_tv_services(

View File

@ -29,9 +29,10 @@ def mock_zeroconf():
yield mock_zc.return_value
def service_update_mock(zeroconf, service, handlers):
def service_update_mock(zeroconf, services, handlers):
"""Call service update handler."""
handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added)
for service in services:
handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added)
def get_service_info_mock(service_type, name):
@ -76,7 +77,7 @@ async def test_setup(hass, mock_zeroconf):
mock_zeroconf.get_service_info.side_effect = get_service_info_mock
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
assert len(mock_service_browser.mock_calls) == len(zc_gen.ZEROCONF)
assert len(mock_service_browser.mock_calls) == 1
expected_flow_calls = 0
for matching_components in zc_gen.ZEROCONF.values():
expected_flow_calls += len(matching_components)