Samsung TV refinements (#31248)

* use st not deviceType

* show model in flow title

* Update strings.json

* add re-auth to entity

* add re-auth to config_flow

* handle auth popup better

* use media player domain const

* fix tests

* rename not_found to not_successful

* authz not authn

* Update media_player.py

* Update config_flow.py

* Update media_player.py

* Update test_media_player.py

* finalize re-auth

* fix ssd tests

* better naming

* fix ip-address-mock serialization

* fix turn_on_action serialization

* add type of hass object

* fix acces denied test

* remove half-added typing

* async get ip address

* fix pylint
This commit is contained in:
escoand 2020-02-03 20:34:02 +01:00 committed by GitHub
parent 03642d9029
commit 4550968316
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 138 additions and 71 deletions

View file

@ -3,6 +3,7 @@ import socket
import voluptuous as vol import voluptuous as vol
from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -41,7 +42,14 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the Samsung TV integration.""" """Set up the Samsung TV integration."""
if DOMAIN in config: if DOMAIN in config:
hass.data[DOMAIN] = {}
for entry_config in config[DOMAIN]: for entry_config in config[DOMAIN]:
ip_address = await hass.async_add_executor_job(
socket.gethostbyname, entry_config[CONF_HOST]
)
hass.data[DOMAIN][ip_address] = {
CONF_ON_ACTION: entry_config.get(CONF_ON_ACTION)
}
hass.async_create_task( hass.async_create_task(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
DOMAIN, context={"source": "import"}, data=entry_config DOMAIN, context={"source": "import"}, data=entry_config
@ -54,7 +62,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, entry): async def async_setup_entry(hass, entry):
"""Set up the Samsung TV platform.""" """Set up the Samsung TV platform."""
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "media_player") hass.config_entries.async_forward_entry_setup(entry, MP_DOMAIN)
) )
return True return True

View file

@ -9,7 +9,6 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.ssdp import ( from homeassistant.components.ssdp import (
ATTR_SSDP_LOCATION, ATTR_SSDP_LOCATION,
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER, ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME, ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_UDN, ATTR_UPNP_UDN,
@ -24,20 +23,13 @@ from homeassistant.const import (
) )
# pylint:disable=unused-import # pylint:disable=unused-import
from .const import ( from .const import CONF_MANUFACTURER, CONF_MODEL, DOMAIN, LOGGER, METHODS
CONF_MANUFACTURER,
CONF_MODEL,
CONF_ON_ACTION,
DOMAIN,
LOGGER,
METHODS,
)
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str, vol.Required(CONF_NAME): str}) DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str, vol.Required(CONF_NAME): str})
RESULT_AUTH_MISSING = "auth_missing" RESULT_AUTH_MISSING = "auth_missing"
RESULT_SUCCESS = "success" RESULT_SUCCESS = "success"
RESULT_NOT_FOUND = "not_found" RESULT_NOT_SUCCESSFUL = "not_successful"
RESULT_NOT_SUPPORTED = "not_supported" RESULT_NOT_SUPPORTED = "not_supported"
@ -63,23 +55,21 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._method = None self._method = None
self._model = None self._model = None
self._name = None self._name = None
self._on_script = None
self._port = None self._port = None
self._title = None self._title = None
self._uuid = None self._id = None
def _get_entry(self): def _get_entry(self):
return self.async_create_entry( return self.async_create_entry(
title=self._title, title=self._title,
data={ data={
CONF_HOST: self._host, CONF_HOST: self._host,
CONF_ID: self._uuid, CONF_ID: self._id,
CONF_IP_ADDRESS: self._ip, CONF_IP_ADDRESS: self._ip,
CONF_MANUFACTURER: self._manufacturer, CONF_MANUFACTURER: self._manufacturer,
CONF_METHOD: self._method, CONF_METHOD: self._method,
CONF_MODEL: self._model, CONF_MODEL: self._model,
CONF_NAME: self._name, CONF_NAME: self._name,
CONF_ON_ACTION: self._on_script,
CONF_PORT: self._port, CONF_PORT: self._port,
}, },
) )
@ -94,7 +84,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"host": self._host, "host": self._host,
"method": method, "method": method,
"port": self._port, "port": self._port,
"timeout": 1, # We need this high timeout because waiting for auth popup is just an open socket
"timeout": 31,
} }
try: try:
LOGGER.debug("Try config: %s", config) LOGGER.debug("Try config: %s", config)
@ -108,15 +99,14 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
except UnhandledResponse: except UnhandledResponse:
LOGGER.debug("Working but unsupported config: %s", config) LOGGER.debug("Working but unsupported config: %s", config)
return RESULT_NOT_SUPPORTED return RESULT_NOT_SUPPORTED
except (OSError): except OSError as err:
LOGGER.debug("Failing config: %s", config) LOGGER.debug("Failing config: %s, error: %s", config, err)
LOGGER.debug("No working config found") LOGGER.debug("No working config found")
return RESULT_NOT_FOUND return RESULT_NOT_SUCCESSFUL
async def async_step_import(self, user_input=None): async def async_step_import(self, user_input=None):
"""Handle configuration by yaml file.""" """Handle configuration by yaml file."""
self._on_script = user_input.get(CONF_ON_ACTION)
self._port = user_input.get(CONF_PORT) self._port = user_input.get(CONF_PORT)
return await self.async_step_user(user_input) return await self.async_step_user(user_input)
@ -133,7 +123,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._host = user_input.get(CONF_HOST) self._host = user_input.get(CONF_HOST)
self._ip = self.context[CONF_IP_ADDRESS] = ip_address self._ip = self.context[CONF_IP_ADDRESS] = ip_address
self._title = user_input.get(CONF_NAME) self._name = user_input.get(CONF_NAME)
self._title = self._name
result = await self.hass.async_add_executor_job(self._try_connect) result = await self.hass.async_add_executor_job(self._try_connect)
@ -150,24 +141,27 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._host = host self._host = host
self._ip = self.context[CONF_IP_ADDRESS] = ip_address self._ip = self.context[CONF_IP_ADDRESS] = ip_address
self._manufacturer = user_input[ATTR_UPNP_MANUFACTURER] self._manufacturer = user_input.get(ATTR_UPNP_MANUFACTURER)
self._model = user_input[ATTR_UPNP_MODEL_NAME] self._model = user_input.get(ATTR_UPNP_MODEL_NAME)
self._name = user_input[ATTR_UPNP_FRIENDLY_NAME] self._name = f"Samsung {self._model}"
if self._name.startswith("[TV]"): self._id = user_input.get(ATTR_UPNP_UDN)
self._name = self._name[4:] self._title = self._model
self._title = f"{self._name} ({self._model})"
self._uuid = user_input[ATTR_UPNP_UDN] # probably access denied
if self._uuid.startswith("uuid:"): if self._id is None:
self._uuid = self._uuid[5:] return self.async_abort(reason=RESULT_AUTH_MISSING)
if self._id.startswith("uuid:"):
self._id = self._id[5:]
config_entry = await self.async_set_unique_id(ip_address) config_entry = await self.async_set_unique_id(ip_address)
if config_entry: if config_entry:
config_entry.data[CONF_ID] = self._uuid config_entry.data[CONF_ID] = self._id
config_entry.data[CONF_MANUFACTURER] = self._manufacturer config_entry.data[CONF_MANUFACTURER] = self._manufacturer
config_entry.data[CONF_MODEL] = self._model config_entry.data[CONF_MODEL] = self._model
self.hass.config_entries.async_update_entry(config_entry) self.hass.config_entries.async_update_entry(config_entry)
return self.async_abort(reason="already_configured") return self.async_abort(reason="already_configured")
self.context["title_placeholders"] = {"model": self._model}
return await self.async_step_confirm() return await self.async_step_confirm()
async def async_step_confirm(self, user_input=None): async def async_step_confirm(self, user_input=None):
@ -182,3 +176,19 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_show_form( return self.async_show_form(
step_id="confirm", description_placeholders={"model": self._model} step_id="confirm", description_placeholders={"model": self._model}
) )
async def async_step_reauth(self, user_input=None):
"""Handle configuration by re-auth."""
self._host = user_input[CONF_HOST]
self._id = user_input.get(CONF_ID)
self._ip = user_input[CONF_IP_ADDRESS]
self._manufacturer = user_input.get(CONF_MANUFACTURER)
self._model = user_input.get(CONF_MODEL)
self._name = user_input.get(CONF_NAME)
self._port = user_input.get(CONF_PORT)
self._title = self._model or self._name
await self.async_set_unique_id(self._ip)
self.context["title_placeholders"] = {"model": self._title}
return await self.async_step_confirm()

View file

@ -4,7 +4,7 @@ import logging
LOGGER = logging.getLogger(__package__) LOGGER = logging.getLogger(__package__)
DOMAIN = "samsungtv" DOMAIN = "samsungtv"
DEFAULT_NAME = "Samsung TV Remote" DEFAULT_NAME = "Samsung TV"
CONF_MANUFACTURER = "manufacturer" CONF_MANUFACTURER = "manufacturer"
CONF_MODEL = "model" CONF_MODEL = "model"

View file

@ -7,7 +7,7 @@
], ],
"ssdp": [ "ssdp": [
{ {
"deviceType": "urn:samsung.com:device:RemoteControlReceiver:1" "st": "urn:samsung.com:device:RemoteControlReceiver:1"
} }
], ],
"dependencies": [], "dependencies": [],

View file

@ -23,7 +23,9 @@ from homeassistant.components.media_player.const import (
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_ID, CONF_ID,
CONF_IP_ADDRESS,
CONF_METHOD, CONF_METHOD,
CONF_NAME,
CONF_PORT, CONF_PORT,
STATE_OFF, STATE_OFF,
STATE_ON, STATE_ON,
@ -59,8 +61,16 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Samsung TV from a config entry.""" """Set up the Samsung TV from a config entry."""
turn_on_action = config_entry.data.get(CONF_ON_ACTION) ip_address = config_entry.data[CONF_IP_ADDRESS]
on_script = Script(hass, turn_on_action) if turn_on_action else None on_script = None
if (
DOMAIN in hass.data
and ip_address in hass.data[DOMAIN]
and CONF_ON_ACTION in hass.data[DOMAIN][ip_address]
and hass.data[DOMAIN][ip_address][CONF_ON_ACTION]
):
turn_on_action = hass.data[DOMAIN][ip_address][CONF_ON_ACTION]
on_script = Script(hass, turn_on_action)
async_add_entities([SamsungTVDevice(config_entry, on_script)]) async_add_entities([SamsungTVDevice(config_entry, on_script)])
@ -70,12 +80,11 @@ class SamsungTVDevice(MediaPlayerDevice):
def __init__(self, config_entry, on_script): def __init__(self, config_entry, on_script):
"""Initialize the Samsung device.""" """Initialize the Samsung device."""
self._config_entry = config_entry self._config_entry = config_entry
self._name = config_entry.title
self._uuid = config_entry.data.get(CONF_ID)
self._manufacturer = config_entry.data.get(CONF_MANUFACTURER) self._manufacturer = config_entry.data.get(CONF_MANUFACTURER)
self._model = config_entry.data.get(CONF_MODEL) self._model = config_entry.data.get(CONF_MODEL)
self._name = config_entry.data.get(CONF_NAME)
self._on_script = on_script self._on_script = on_script
self._update_listener = None self._uuid = config_entry.data.get(CONF_ID)
# Assume that the TV is not muted # Assume that the TV is not muted
self._muted = False self._muted = False
# Assume that the TV is in Play mode # Assume that the TV is in Play mode
@ -88,7 +97,7 @@ class SamsungTVDevice(MediaPlayerDevice):
# Generate a configuration for the Samsung library # Generate a configuration for the Samsung library
self._config = { self._config = {
"name": "HomeAssistant", "name": "HomeAssistant",
"description": self._name, "description": "HomeAssistant",
"id": "ha.component.samsung", "id": "ha.component.samsung",
"method": config_entry.data[CONF_METHOD], "method": config_entry.data[CONF_METHOD],
"port": config_entry.data.get(CONF_PORT), "port": config_entry.data.get(CONF_PORT),
@ -124,7 +133,19 @@ class SamsungTVDevice(MediaPlayerDevice):
"""Create or return a remote control instance.""" """Create or return a remote control instance."""
if self._remote is None: if self._remote is None:
# We need to create a new instance to reconnect. # We need to create a new instance to reconnect.
self._remote = SamsungRemote(self._config.copy()) try:
self._remote = SamsungRemote(self._config.copy())
# This is only happening when the auth was switched to DENY
# A removed auth will lead to socket timeout because waiting for auth popup is just an open socket
except samsung_exceptions.AccessDenied:
self.hass.async_create_task(
self.hass.config_entries.flow.async_init(
DOMAIN,
context={"source": "reauth"},
data=self._config_entry.data,
)
)
raise
return self._remote return self._remote

View file

@ -1,10 +1,11 @@
{ {
"config": { "config": {
"flow_title": "Samsung TV: {model}",
"title": "Samsung TV", "title": "Samsung TV",
"step": { "step": {
"user": { "user": {
"title": "Samsung TV", "title": "Samsung TV",
"description": "Enter your Samsung TV information. If you never connected Home Assistant before you should see a popup on your TV asking for authentication.", "description": "Enter your Samsung TV information. If you never connected Home Assistant before you should see a popup on your TV asking for authorization.",
"data": { "data": {
"host": "Host or IP address", "host": "Host or IP address",
"name": "Name" "name": "Name"
@ -12,15 +13,15 @@
}, },
"confirm": { "confirm": {
"title": "Samsung TV", "title": "Samsung TV",
"description": "Do you want to set up Samsung TV {model}? If you never connected Home Assistant before you should see a popup on your TV asking for authentication. Manual configurations for this TV will be overwritten." "description": "Do you want to set up Samsung TV {model}? If you never connected Home Assistant before you should see a popup on your TV asking for authorization. Manual configurations for this TV will be overwritten."
} }
}, },
"abort": { "abort": {
"already_in_progress": "Samsung TV configuration is already in progress.", "already_in_progress": "Samsung TV configuration is already in progress.",
"already_configured": "This Samsung TV is already configured.", "already_configured": "This Samsung TV is already configured.",
"auth_missing": "Home Assistant is not authenticated to connect to this Samsung TV.", "auth_missing": "Home Assistant is not authorized to connect to this Samsung TV. Please check your TV's settings to authorize Home Assistant.",
"not_found": "No supported Samsung TV devices found on the network.", "not_successful": "Unable to connect to this Samsung TV device.",
"not_supported": "This Samsung TV devices is currently not supported." "not_supported": "This Samsung TV device is currently not supported."
} }
} }
} }

View file

@ -38,7 +38,7 @@ SSDP = {
], ],
"samsungtv": [ "samsungtv": [
{ {
"deviceType": "urn:samsung.com:device:RemoteControlReceiver:1" "st": "urn:samsung.com:device:RemoteControlReceiver:1"
} }
], ],
"sonos": [ "sonos": [

View file

@ -42,7 +42,7 @@ AUTODETECT_WEBSOCKET = {
"method": "websocket", "method": "websocket",
"port": None, "port": None,
"host": "fake_host", "host": "fake_host",
"timeout": 1, "timeout": 31,
} }
AUTODETECT_LEGACY = { AUTODETECT_LEGACY = {
"name": "HomeAssistant", "name": "HomeAssistant",
@ -51,7 +51,7 @@ AUTODETECT_LEGACY = {
"method": "legacy", "method": "legacy",
"port": None, "port": None,
"host": "fake_host", "host": "fake_host",
"timeout": 1, "timeout": 31,
} }
@ -87,7 +87,7 @@ async def test_user(hass, remote):
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == "fake_name" assert result["title"] == "fake_name"
assert result["data"][CONF_HOST] == "fake_host" assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] is None assert result["data"][CONF_NAME] == "fake_name"
assert result["data"][CONF_MANUFACTURER] is None assert result["data"][CONF_MANUFACTURER] is None
assert result["data"][CONF_MODEL] is None assert result["data"][CONF_MODEL] is None
assert result["data"][CONF_ID] is None assert result["data"][CONF_ID] is None
@ -123,19 +123,19 @@ async def test_user_not_supported(hass):
assert result["reason"] == "not_supported" assert result["reason"] == "not_supported"
async def test_user_not_found(hass): async def test_user_not_successful(hass):
"""Test starting a flow by user but no device found.""" """Test starting a flow by user but no connection found."""
with patch( with patch(
"homeassistant.components.samsungtv.config_flow.Remote", "homeassistant.components.samsungtv.config_flow.Remote",
side_effect=OSError("Boom"), side_effect=OSError("Boom"),
), patch("homeassistant.components.samsungtv.config_flow.socket"): ), patch("homeassistant.components.samsungtv.config_flow.socket"):
# device not found # device not connectable
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "not_found" assert result["reason"] == "not_successful"
async def test_user_already_configured(hass, remote): async def test_user_already_configured(hass, remote):
@ -170,9 +170,9 @@ async def test_ssdp(hass, remote):
result["flow_id"], user_input="whatever" result["flow_id"], user_input="whatever"
) )
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == "fake_name (fake_model)" assert result["title"] == "fake_model"
assert result["data"][CONF_HOST] == "fake_host" assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "fake_name" assert result["data"][CONF_NAME] == "Samsung fake_model"
assert result["data"][CONF_MANUFACTURER] == "fake_manufacturer" assert result["data"][CONF_MANUFACTURER] == "fake_manufacturer"
assert result["data"][CONF_MODEL] == "fake_model" assert result["data"][CONF_MODEL] == "fake_model"
assert result["data"][CONF_ID] == "fake_uuid" assert result["data"][CONF_ID] == "fake_uuid"
@ -193,9 +193,9 @@ async def test_ssdp_noprefix(hass, remote):
result["flow_id"], user_input="whatever" result["flow_id"], user_input="whatever"
) )
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == "fake2_name (fake2_model)" assert result["title"] == "fake2_model"
assert result["data"][CONF_HOST] == "fake2_host" assert result["data"][CONF_HOST] == "fake2_host"
assert result["data"][CONF_NAME] == "fake2_name" assert result["data"][CONF_NAME] == "Samsung fake2_model"
assert result["data"][CONF_MANUFACTURER] == "fake2_manufacturer" assert result["data"][CONF_MANUFACTURER] == "fake2_manufacturer"
assert result["data"][CONF_MODEL] == "fake2_model" assert result["data"][CONF_MODEL] == "fake2_model"
assert result["data"][CONF_ID] == "fake2_uuid" assert result["data"][CONF_ID] == "fake2_uuid"
@ -245,7 +245,7 @@ async def test_ssdp_not_supported(hass):
assert result["reason"] == "not_supported" assert result["reason"] == "not_supported"
async def test_ssdp_not_found(hass): async def test_ssdp_not_successful(hass):
"""Test starting a flow from discovery but no device found.""" """Test starting a flow from discovery but no device found."""
with patch( with patch(
"homeassistant.components.samsungtv.config_flow.Remote", "homeassistant.components.samsungtv.config_flow.Remote",
@ -264,7 +264,7 @@ async def test_ssdp_not_found(hass):
result["flow_id"], user_input="whatever" result["flow_id"], user_input="whatever"
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "not_found" assert result["reason"] == "not_successful"
async def test_ssdp_already_in_progress(hass, remote): async def test_ssdp_already_in_progress(hass, remote):
@ -380,7 +380,7 @@ async def test_autodetect_none(hass, remote):
DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "not_found" assert result["reason"] == "not_successful"
assert remote.call_count == 2 assert remote.call_count == 2
assert remote.call_args_list == [ assert remote.call_args_list == [
call(AUTODETECT_WEBSOCKET), call(AUTODETECT_WEBSOCKET),

View file

@ -32,7 +32,7 @@ MOCK_CONFIG = {
} }
REMOTE_CALL = { REMOTE_CALL = {
"name": "HomeAssistant", "name": "HomeAssistant",
"description": MOCK_CONFIG[SAMSUNGTV_DOMAIN][0][CONF_NAME], "description": "HomeAssistant",
"id": "ha.component.samsung", "id": "ha.component.samsung",
"method": "websocket", "method": "websocket",
"port": MOCK_CONFIG[SAMSUNGTV_DOMAIN][0][CONF_PORT], "port": MOCK_CONFIG[SAMSUNGTV_DOMAIN][0][CONF_PORT],
@ -44,11 +44,13 @@ REMOTE_CALL = {
@pytest.fixture(name="remote") @pytest.fixture(name="remote")
def remote_fixture(): def remote_fixture():
"""Patch the samsungctl Remote.""" """Patch the samsungctl Remote."""
with patch("homeassistant.components.samsungtv.socket"), patch( with patch("homeassistant.components.samsungtv.socket") as socket1, patch(
"homeassistant.components.samsungtv.config_flow.socket" "homeassistant.components.samsungtv.config_flow.socket"
), patch("homeassistant.components.samsungtv.config_flow.Remote"), patch( ) as socket2, patch("homeassistant.components.samsungtv.config_flow.Remote"), patch(
"homeassistant.components.samsungtv.media_player.SamsungRemote" "homeassistant.components.samsungtv.media_player.SamsungRemote"
) as remote: ) as remote:
socket1.gethostbyname.return_value = "FAKE_IP_ADDRESS"
socket2.gethostbyname.return_value = "FAKE_IP_ADDRESS"
yield remote yield remote

View file

@ -75,15 +75,17 @@ MOCK_CONFIG_NOTURNON = {
@pytest.fixture(name="remote") @pytest.fixture(name="remote")
def remote_fixture(): def remote_fixture():
"""Patch the samsungctl Remote.""" """Patch the samsungctl Remote."""
with patch("homeassistant.components.samsungtv.config_flow.socket"), patch( with patch("homeassistant.components.samsungtv.config_flow.Remote"), patch(
"homeassistant.components.samsungtv.config_flow.Remote" "homeassistant.components.samsungtv.config_flow.socket"
), patch( ) as socket1, patch(
"homeassistant.components.samsungtv.media_player.SamsungRemote" "homeassistant.components.samsungtv.media_player.SamsungRemote"
) as remote_class, patch( ) as remote_class, patch(
"homeassistant.components.samsungtv.socket" "homeassistant.components.samsungtv.socket"
): ) as socket2:
remote = mock.Mock() remote = mock.Mock()
remote_class.return_value = remote remote_class.return_value = remote
socket1.gethostbyname.return_value = "FAKE_IP_ADDRESS"
socket2.gethostbyname.return_value = "FAKE_IP_ADDRESS"
yield remote yield remote
@ -135,11 +137,12 @@ async def test_update_on(hass, remote, mock_now):
async def test_update_off(hass, remote, mock_now): async def test_update_off(hass, remote, mock_now):
"""Testing update tv off.""" """Testing update tv off."""
await setup_samsungtv(hass, MOCK_CONFIG)
with patch( with patch(
"homeassistant.components.samsungtv.media_player.SamsungRemote", "homeassistant.components.samsungtv.media_player.SamsungRemote",
side_effect=[OSError("Boom"), mock.DEFAULT], side_effect=[OSError("Boom"), mock.DEFAULT],
), patch("homeassistant.components.samsungtv.config_flow.socket"): ):
await setup_samsungtv(hass, MOCK_CONFIG)
next_update = mock_now + timedelta(minutes=5) next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update): with patch("homeassistant.util.dt.utcnow", return_value=next_update):
@ -150,13 +153,35 @@ async def test_update_off(hass, remote, mock_now):
assert state.state == STATE_OFF assert state.state == STATE_OFF
async def test_update_access_denied(hass, remote, mock_now):
"""Testing update tv unhandled response exception."""
await setup_samsungtv(hass, MOCK_CONFIG)
with patch(
"homeassistant.components.samsungtv.media_player.SamsungRemote",
side_effect=exceptions.AccessDenied("Boom"),
):
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
assert [
flow
for flow in hass.config_entries.flow.async_progress()
if flow["context"]["source"] == "reauth"
]
async def test_update_unhandled_response(hass, remote, mock_now): async def test_update_unhandled_response(hass, remote, mock_now):
"""Testing update tv unhandled response exception.""" """Testing update tv unhandled response exception."""
await setup_samsungtv(hass, MOCK_CONFIG)
with patch( with patch(
"homeassistant.components.samsungtv.media_player.SamsungRemote", "homeassistant.components.samsungtv.media_player.SamsungRemote",
side_effect=[exceptions.UnhandledResponse("Boom"), mock.DEFAULT], side_effect=[exceptions.UnhandledResponse("Boom"), mock.DEFAULT],
), patch("homeassistant.components.samsungtv.config_flow.socket"): ):
await setup_samsungtv(hass, MOCK_CONFIG)
next_update = mock_now + timedelta(minutes=5) next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update): with patch("homeassistant.util.dt.utcnow", return_value=next_update):