core/tests/components/samsungtv/test_config_flow.py

1319 lines
47 KiB
Python

"""Tests for Samsung TV config flow."""
import socket
from unittest.mock import Mock, PropertyMock, call, patch
from samsungctl.exceptions import AccessDenied, UnhandledResponse
from samsungtvws.exceptions import ConnectionFailure, HttpApiError
from websocket import WebSocketException, WebSocketProtocolException
from homeassistant import config_entries
from homeassistant.components import dhcp, ssdp, zeroconf
from homeassistant.components.samsungtv.const import (
CONF_MANUFACTURER,
CONF_MODEL,
DEFAULT_MANUFACTURER,
DOMAIN,
LEGACY_PORT,
METHOD_LEGACY,
METHOD_WEBSOCKET,
RESULT_AUTH_MISSING,
RESULT_CANNOT_CONNECT,
RESULT_NOT_SUPPORTED,
RESULT_UNKNOWN_HOST,
TIMEOUT_REQUEST,
TIMEOUT_WEBSOCKET,
)
from homeassistant.components.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_UDN,
)
from homeassistant.const import (
CONF_HOST,
CONF_ID,
CONF_IP_ADDRESS,
CONF_MAC,
CONF_METHOD,
CONF_NAME,
CONF_PORT,
CONF_TOKEN,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
from tests.components.samsungtv.conftest import (
RESULT_ALREADY_CONFIGURED,
RESULT_ALREADY_IN_PROGRESS,
)
MOCK_IMPORT_DATA = {
CONF_HOST: "fake_host",
CONF_NAME: "fake",
CONF_PORT: 55000,
}
MOCK_IMPORT_DATA_WITHOUT_NAME = {
CONF_HOST: "fake_host",
}
MOCK_IMPORT_WSDATA = {
CONF_HOST: "fake_host",
CONF_NAME: "fake",
CONF_PORT: 8002,
}
MOCK_USER_DATA = {CONF_HOST: "fake_host", CONF_NAME: "fake_name"}
MOCK_SSDP_DATA = ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="https://fake_host:12345/test",
upnp={
ATTR_UPNP_FRIENDLY_NAME: "[TV] fake_name",
ATTR_UPNP_MANUFACTURER: "Samsung fake_manufacturer",
ATTR_UPNP_MODEL_NAME: "fake_model",
ATTR_UPNP_UDN: "uuid:0d1cef00-00dc-1000-9c80-4844f7b172de",
},
)
MOCK_SSDP_DATA_NOPREFIX = ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://fake2_host:12345/test",
upnp={
ATTR_UPNP_FRIENDLY_NAME: "fake2_name",
ATTR_UPNP_MANUFACTURER: "Samsung fake2_manufacturer",
ATTR_UPNP_MODEL_NAME: "fake2_model",
ATTR_UPNP_UDN: "uuid:0d1cef00-00dc-1000-9c80-4844f7b172df",
},
)
MOCK_SSDP_DATA_WRONGMODEL = ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://fake2_host:12345/test",
upnp={
ATTR_UPNP_FRIENDLY_NAME: "fake2_name",
ATTR_UPNP_MANUFACTURER: "fake2_manufacturer",
ATTR_UPNP_MODEL_NAME: "HW-Qfake",
ATTR_UPNP_UDN: "uuid:0d1cef00-00dc-1000-9c80-4844f7b172df",
},
)
MOCK_DHCP_DATA = dhcp.DhcpServiceInfo(
ip="fake_host", macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname"
)
EXISTING_IP = "192.168.40.221"
MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo(
host="fake_host",
hostname="mock_hostname",
name="mock_name",
port=1234,
properties={
"deviceid": "aa:bb:cc:dd:ee:ff",
"manufacturer": "fake_manufacturer",
"model": "fake_model",
"serialNumber": "fake_serial",
},
type="mock_type",
)
MOCK_OLD_ENTRY = {
CONF_HOST: "fake_host",
CONF_ID: "0d1cef00-00dc-1000-9c80-4844f7b172de_old",
CONF_IP_ADDRESS: EXISTING_IP,
CONF_METHOD: "legacy",
CONF_PORT: None,
}
MOCK_LEGACY_ENTRY = {
CONF_HOST: EXISTING_IP,
CONF_ID: "0d1cef00-00dc-1000-9c80-4844f7b172de_old",
CONF_METHOD: "legacy",
CONF_PORT: None,
}
MOCK_WS_ENTRY = {
CONF_HOST: "fake_host",
CONF_METHOD: METHOD_WEBSOCKET,
CONF_PORT: 8002,
CONF_MODEL: "any",
CONF_NAME: "any",
}
MOCK_DEVICE_INFO = {
"device": {
"type": "Samsung SmartTV",
"name": "fake_name",
"modelName": "fake_model",
},
"id": "123",
}
MOCK_DEVICE_INFO_2 = {
"device": {
"type": "Samsung SmartTV",
"name": "fake2_name",
"modelName": "fake2_model",
},
"id": "345",
}
AUTODETECT_LEGACY = {
"name": "HomeAssistant",
"description": "HomeAssistant",
"id": "ha.component.samsung",
"method": "legacy",
"port": None,
"host": "fake_host",
"timeout": TIMEOUT_REQUEST,
}
AUTODETECT_WEBSOCKET_PLAIN = {
"host": "fake_host",
"name": "HomeAssistant",
"port": 8001,
"timeout": TIMEOUT_REQUEST,
"token": None,
}
AUTODETECT_WEBSOCKET_SSL = {
"host": "fake_host",
"name": "HomeAssistant",
"port": 8002,
"timeout": TIMEOUT_REQUEST,
"token": None,
}
DEVICEINFO_WEBSOCKET_SSL = {
"host": "fake_host",
"name": "HomeAssistant",
"port": 8002,
"timeout": TIMEOUT_WEBSOCKET,
"token": "123456789",
}
async def test_user_legacy(hass: HomeAssistant, remote: Mock):
"""Test starting a flow by user."""
# show form
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
# entry was added
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_DATA
)
# legacy tv entry created
assert result["type"] == "create_entry"
assert result["title"] == "fake_name"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "fake_name"
assert result["data"][CONF_METHOD] == "legacy"
assert result["data"][CONF_MANUFACTURER] == DEFAULT_MANUFACTURER
assert result["data"][CONF_MODEL] is None
assert result["result"].unique_id is None
async def test_user_websocket(hass: HomeAssistant, remotews: Mock):
"""Test starting a flow by user."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom")
):
# show form
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
# entry was added
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_DATA
)
# websocket tv entry created
assert result["type"] == "create_entry"
assert result["title"] == "Living Room (82GXARRS)"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "Living Room"
assert result["data"][CONF_METHOD] == "websocket"
assert result["data"][CONF_MANUFACTURER] == "Samsung"
assert result["data"][CONF_MODEL] == "82GXARRS"
assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
async def test_user_legacy_missing_auth(
hass: HomeAssistant, remote: Mock, remotews: Mock
):
"""Test starting a flow by user with authentication."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=AccessDenied("Boom"),
):
# legacy device missing authentication
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_AUTH_MISSING
async def test_user_legacy_not_supported(hass: HomeAssistant, remote: Mock):
"""Test starting a flow by user for not supported device."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=UnhandledResponse("Boom"),
):
# legacy device not supported
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_NOT_SUPPORTED
async def test_user_websocket_not_supported(hass: HomeAssistant, remotews: Mock):
"""Test starting a flow by user for not supported device."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=WebSocketProtocolException("Boom"),
):
# websocket device not supported
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_NOT_SUPPORTED
async def test_user_not_successful(hass: HomeAssistant, remotews: Mock):
"""Test starting a flow by user but no connection found."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=OSError("Boom"),
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_CANNOT_CONNECT
async def test_user_not_successful_2(hass: HomeAssistant, remotews: Mock):
"""Test starting a flow by user but no connection found."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=ConnectionFailure("Boom"),
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_CANNOT_CONNECT
async def test_ssdp(hass: HomeAssistant, remote: Mock, no_mac_address: Mock):
"""Test starting a flow from discovery."""
no_mac_address.return_value = "aa:bb:cc:dd:ee:ff"
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info",
return_value=MOCK_DEVICE_INFO,
):
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
# entry was added
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "create_entry"
assert result["title"] == "fake_name (fake_model)"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "fake_name"
assert result["data"][CONF_MANUFACTURER] == "Samsung fake_manufacturer"
assert result["data"][CONF_MODEL] == "fake_model"
assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_ssdp_noprefix(hass: HomeAssistant, remote: Mock, no_mac_address: Mock):
"""Test starting a flow from discovery without prefixes."""
no_mac_address.return_value = "aa:bb:cc:dd:ee:ff"
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info",
return_value=MOCK_DEVICE_INFO_2,
):
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=MOCK_SSDP_DATA_NOPREFIX,
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
with patch(
"homeassistant.components.samsungtv.bridge.Remote.__enter__",
return_value=True,
):
# entry was added
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "create_entry"
assert result["title"] == "fake2_name (fake2_model)"
assert result["data"][CONF_HOST] == "fake2_host"
assert result["data"][CONF_NAME] == "fake2_name"
assert result["data"][CONF_MANUFACTURER] == "Samsung fake2_manufacturer"
assert result["data"][CONF_MODEL] == "fake2_model"
assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172df"
async def test_ssdp_legacy_missing_auth(
hass: HomeAssistant, remote: Mock, remotews: Mock
):
"""Test starting a flow from discovery with authentication."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=AccessDenied("Boom"),
):
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
# missing authentication
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVLegacyBridge.try_connect",
return_value=RESULT_AUTH_MISSING,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_AUTH_MISSING
async def test_ssdp_legacy_not_supported(
hass: HomeAssistant, remote: Mock, remotews: Mock
):
"""Test starting a flow from discovery for not supported device."""
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVLegacyBridge.try_connect",
return_value=RESULT_NOT_SUPPORTED,
):
# device not supported
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_NOT_SUPPORTED
async def test_ssdp_websocket_success_populates_mac_address(
hass: HomeAssistant,
remote: Mock,
remotews: Mock,
):
"""Test starting a flow from ssdp for a supported device populates the mac."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "create_entry"
assert result["title"] == "Living Room (82GXARRS)"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "Living Room"
assert result["data"][CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert result["data"][CONF_MANUFACTURER] == "Samsung fake_manufacturer"
assert result["data"][CONF_MODEL] == "82GXARRS"
assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_ssdp_websocket_not_supported(hass: HomeAssistant, remote: Mock):
"""Test starting a flow from discovery for not supported device."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=WebSocketProtocolException("Boom"),
):
# device not supported
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_NOT_SUPPORTED
async def test_ssdp_model_not_supported(hass: HomeAssistant, remote: Mock):
"""Test starting a flow from discovery."""
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=MOCK_SSDP_DATA_WRONGMODEL,
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_NOT_SUPPORTED
async def test_ssdp_not_successful(
hass: HomeAssistant, remote: Mock, no_mac_address: Mock
):
"""Test starting a flow from discovery but no device found."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info",
return_value=MOCK_DEVICE_INFO,
):
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
# device not found
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_CANNOT_CONNECT
async def test_ssdp_not_successful_2(
hass: HomeAssistant, remote: Mock, no_mac_address: Mock
):
"""Test starting a flow from discovery but no device found."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=ConnectionFailure("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info",
return_value=MOCK_DEVICE_INFO,
):
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
# device not found
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_CANNOT_CONNECT
async def test_ssdp_already_in_progress(
hass: HomeAssistant, remote: Mock, no_mac_address: Mock
):
"""Test starting a flow from discovery twice."""
no_mac_address.return_value = "aa:bb:cc:dd:ee:ff"
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info",
return_value=MOCK_DEVICE_INFO,
):
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "form"
assert result["step_id"] == "confirm"
# failed as already in progress
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_ALREADY_IN_PROGRESS
async def test_ssdp_already_configured(
hass: HomeAssistant, remote: Mock, no_mac_address: Mock
):
"""Test starting a flow from discovery when already configured."""
no_mac_address.return_value = "aa:bb:cc:dd:ee:ff"
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info",
return_value=MOCK_DEVICE_INFO,
):
# entry was added
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "create_entry"
entry = result["result"]
assert entry.data[CONF_MANUFACTURER] == DEFAULT_MANUFACTURER
assert entry.data[CONF_MODEL] is None
assert entry.unique_id is None
# failed as already configured
result2 = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result2["type"] == "abort"
assert result2["reason"] == RESULT_ALREADY_CONFIGURED
# check updated device info
assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_import_legacy(hass: HomeAssistant, remote: Mock, no_mac_address: Mock):
"""Test importing from yaml with hostname."""
no_mac_address.return_value = "aa:bb:cc:dd:ee:ff"
with patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=MOCK_IMPORT_DATA,
)
await hass.async_block_till_done()
assert result["type"] == "create_entry"
assert result["title"] == "fake"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "fake"
assert result["data"][CONF_MANUFACTURER] == "Samsung"
assert result["result"].unique_id is None
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data[CONF_METHOD] == METHOD_LEGACY
assert entries[0].data[CONF_PORT] == LEGACY_PORT
async def test_import_legacy_without_name(
hass: HomeAssistant,
remote: Mock,
remotews_no_device_info: Mock,
no_mac_address: Mock,
):
"""Test importing from yaml without a name."""
with patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=MOCK_IMPORT_DATA_WITHOUT_NAME,
)
await hass.async_block_till_done()
assert result["type"] == "create_entry"
assert result["title"] == "fake_host"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_MANUFACTURER] == "Samsung"
assert result["result"].unique_id is None
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data[CONF_METHOD] == METHOD_LEGACY
assert entries[0].data[CONF_PORT] == LEGACY_PORT
async def test_import_websocket(hass: HomeAssistant, remotews: Mock):
"""Test importing from yaml with hostname."""
with patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=MOCK_IMPORT_WSDATA,
)
await hass.async_block_till_done()
assert result["type"] == "create_entry"
assert result["title"] == "fake"
assert result["data"][CONF_METHOD] == METHOD_WEBSOCKET
assert result["data"][CONF_PORT] == 8002
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "fake"
assert result["data"][CONF_MANUFACTURER] == "Samsung"
assert result["result"].unique_id is None
async def test_import_websocket_without_port(hass: HomeAssistant, remotews: Mock):
"""Test importing from yaml with hostname by no port."""
with patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=MOCK_IMPORT_WSDATA,
)
await hass.async_block_till_done()
assert result["type"] == "create_entry"
assert result["title"] == "fake"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "fake"
assert result["data"][CONF_MANUFACTURER] == "Samsung"
assert result["result"].unique_id is None
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data[CONF_METHOD] == METHOD_WEBSOCKET
assert entries[0].data[CONF_PORT] == 8002
async def test_import_unknown_host(hass: HomeAssistant, remotews: Mock):
"""Test importing from yaml with hostname that does not resolve."""
with patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
side_effect=socket.gaierror,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=MOCK_IMPORT_DATA,
)
await hass.async_block_till_done()
assert result["type"] == "abort"
assert result["reason"] == RESULT_UNKNOWN_HOST
async def test_dhcp(hass: HomeAssistant, remote: Mock, remotews: Mock):
"""Test starting a flow from dhcp."""
# confirm to add the entry
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=MOCK_DHCP_DATA,
)
await hass.async_block_till_done()
assert result["type"] == "form"
assert result["step_id"] == "confirm"
# entry was added
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "create_entry"
assert result["title"] == "Living Room (82GXARRS)"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "Living Room"
assert result["data"][CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert result["data"][CONF_MANUFACTURER] == "Samsung"
assert result["data"][CONF_MODEL] == "82GXARRS"
assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
async def test_zeroconf(hass: HomeAssistant, remote: Mock, remotews: Mock):
"""Test starting a flow from zeroconf."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=MOCK_ZEROCONF_DATA,
)
await hass.async_block_till_done()
assert result["type"] == "form"
assert result["step_id"] == "confirm"
# entry was added
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input="whatever"
)
assert result["type"] == "create_entry"
assert result["title"] == "Living Room (82GXARRS)"
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_NAME] == "Living Room"
assert result["data"][CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert result["data"][CONF_MANUFACTURER] == "Samsung"
assert result["data"][CONF_MODEL] == "82GXARRS"
assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
async def test_zeroconf_ignores_soundbar(hass: HomeAssistant, remotews_soundbar: Mock):
"""Test starting a flow from zeroconf where the device is actually a soundbar."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=MOCK_ZEROCONF_DATA,
)
await hass.async_block_till_done()
assert result["type"] == "abort"
assert result["reason"] == "not_supported"
async def test_zeroconf_no_device_info(
hass: HomeAssistant, remote: Mock, remotews_no_device_info: Mock
):
"""Test starting a flow from zeroconf where device_info returns None."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=MOCK_ZEROCONF_DATA,
)
await hass.async_block_till_done()
assert result["type"] == "abort"
assert result["reason"] == "not_supported"
async def test_zeroconf_and_dhcp_same_time(hass: HomeAssistant, remotews: Mock):
"""Test starting a flow from zeroconf and dhcp."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=MOCK_DHCP_DATA,
)
await hass.async_block_till_done()
assert result["type"] == "form"
assert result["step_id"] == "confirm"
result2 = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=MOCK_ZEROCONF_DATA,
)
await hass.async_block_till_done()
assert result2["type"] == "abort"
assert result2["reason"] == "already_in_progress"
async def test_autodetect_websocket(hass: HomeAssistant, remote: Mock, remotews: Mock):
"""Test for send key with autodetection of protocol."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remotews:
enter = Mock()
type(enter).token = PropertyMock(return_value="123456789")
remote = Mock()
remote.__enter__ = Mock(return_value=enter)
remote.__exit__ = Mock(return_value=False)
remote.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": {
"modelName": "82GXARRS",
"networkType": "wireless",
"wifiMac": "aa:bb:cc:dd:ee:ff",
"udn": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"mac": "aa:bb:cc:dd:ee:ff",
"name": "[TV] Living Room",
"type": "Samsung SmartTV",
},
}
remotews.return_value = remote
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "create_entry"
assert result["data"][CONF_METHOD] == "websocket"
assert result["data"][CONF_TOKEN] == "123456789"
assert remotews.call_count == 2
assert remotews.call_args_list == [
call(**AUTODETECT_WEBSOCKET_SSL),
call(**DEVICEINFO_WEBSOCKET_SSL),
]
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
async def test_websocket_no_mac(hass: HomeAssistant, remote: Mock, remotews: Mock):
"""Test for send key with autodetection of protocol."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remotews, patch(
"getmac.get_mac_address", return_value="gg:hh:ii:ll:mm:nn"
):
enter = Mock()
type(enter).token = PropertyMock(return_value="123456789")
remote = Mock()
remote.__enter__ = Mock(return_value=enter)
remote.__exit__ = Mock(return_value=False)
remote.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": {
"modelName": "82GXARRS",
"networkType": "lan",
"udn": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"name": "[TV] Living Room",
"type": "Samsung SmartTV",
},
}
remotews.return_value = remote
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "create_entry"
assert result["data"][CONF_METHOD] == "websocket"
assert result["data"][CONF_TOKEN] == "123456789"
assert result["data"][CONF_MAC] == "gg:hh:ii:ll:mm:nn"
assert remotews.call_count == 2
assert remotews.call_args_list == [
call(**AUTODETECT_WEBSOCKET_SSL),
call(**DEVICEINFO_WEBSOCKET_SSL),
]
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data[CONF_MAC] == "gg:hh:ii:ll:mm:nn"
async def test_autodetect_auth_missing(hass: HomeAssistant, remote: Mock):
"""Test for send key with autodetection of protocol."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=[AccessDenied("Boom")],
) as remote, patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_AUTH_MISSING
assert remote.call_count == 1
assert remote.call_args_list == [call(AUTODETECT_LEGACY)]
async def test_autodetect_not_supported(hass: HomeAssistant, remote: Mock):
"""Test for send key with autodetection of protocol."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=[UnhandledResponse("Boom")],
) as remote, patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_NOT_SUPPORTED
assert remote.call_count == 1
assert remote.call_args_list == [call(AUTODETECT_LEGACY)]
async def test_autodetect_legacy(hass: HomeAssistant, remote: Mock):
"""Test for send key with autodetection of protocol."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "create_entry"
assert result["data"][CONF_METHOD] == "legacy"
assert result["data"][CONF_NAME] == "fake_name"
assert result["data"][CONF_MAC] is None
assert result["data"][CONF_PORT] == LEGACY_PORT
async def test_autodetect_none(hass: HomeAssistant, remote: Mock, remotews: Mock):
"""Test for send key with autodetection of protocol."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"),
) as remote, patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=OSError("Boom"),
) as remotews, patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_CANNOT_CONNECT
assert remote.call_count == 1
assert remote.call_args_list == [
call(AUTODETECT_LEGACY),
]
assert remotews.call_count == 2
assert remotews.call_args_list == [
call(**AUTODETECT_WEBSOCKET_SSL),
call(**AUTODETECT_WEBSOCKET_PLAIN),
]
async def test_update_old_entry(hass: HomeAssistant, remote: Mock, remotews: Mock):
"""Test update of old entry."""
with patch("homeassistant.components.samsungtv.bridge.Remote") as remote:
remote().rest_device_info.return_value = {
"device": {
"modelName": "fake_model2",
"name": "[TV] Fake Name",
"udn": "uuid:fake_serial",
}
}
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY)
entry.add_to_hass(hass)
config_entries_domain = hass.config_entries.async_entries(DOMAIN)
assert len(config_entries_domain) == 1
assert entry is config_entries_domain[0]
assert entry.data[CONF_ID] == "0d1cef00-00dc-1000-9c80-4844f7b172de_old"
assert entry.data[CONF_IP_ADDRESS] == EXISTING_IP
assert not entry.unique_id
assert await async_setup_component(hass, DOMAIN, {}) is True
await hass.async_block_till_done()
# failed as already configured
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
)
assert result["type"] == "abort"
assert result["reason"] == RESULT_ALREADY_CONFIGURED
config_entries_domain = hass.config_entries.async_entries(DOMAIN)
assert len(config_entries_domain) == 1
entry2 = config_entries_domain[0]
# check updated device info
assert entry2.data.get(CONF_ID) is not None
assert entry2.data.get(CONF_IP_ADDRESS) is not None
assert entry2.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_update_missing_mac_unique_id_added_from_dhcp(hass, remotews: Mock):
"""Test missing mac and unique id added."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.samsungtv.async_setup",
return_value=True,
) as mock_setup, patch(
"homeassistant.components.samsungtv.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=MOCK_DHCP_DATA,
)
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert entry.unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
async def test_update_missing_mac_unique_id_added_from_zeroconf(hass, remotews: Mock):
"""Test missing mac and unique id added."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.samsungtv.async_setup",
return_value=True,
) as mock_setup, patch(
"homeassistant.components.samsungtv.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=MOCK_ZEROCONF_DATA,
)
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert entry.unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
async def test_update_missing_mac_unique_id_added_from_ssdp(hass, remotews: Mock):
"""Test missing mac and unique id added via ssdp."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.samsungtv.async_setup",
return_value=True,
) as mock_setup, patch(
"homeassistant.components.samsungtv.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=MOCK_SSDP_DATA,
)
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_update_missing_mac_added_unique_id_preserved_from_zeroconf(
hass, remotews: Mock
):
"""Test missing mac and unique id added."""
entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_OLD_ENTRY,
unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de",
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.samsungtv.async_setup",
return_value=True,
) as mock_setup, patch(
"homeassistant.components.samsungtv.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=MOCK_ZEROCONF_DATA,
)
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_update_legacy_missing_mac_from_dhcp(hass, remote: Mock):
"""Test missing mac added."""
entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_LEGACY_ENTRY,
unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de",
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.samsungtv.async_setup",
return_value=True,
) as mock_setup, patch(
"homeassistant.components.samsungtv.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname"
),
)
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(hass, remote: Mock):
"""Test missing mac added when there is no unique id."""
entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_LEGACY_ENTRY,
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS.rest_device_info",
side_effect=HttpApiError,
), patch(
"homeassistant.components.samsungtv.bridge.Remote.__enter__",
return_value=True,
), patch(
"homeassistant.components.samsungtv.async_setup",
return_value=True,
) as mock_setup, patch(
"homeassistant.components.samsungtv.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname"
),
)
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
assert result["type"] == "abort"
assert result["reason"] == "not_supported"
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
assert entry.unique_id is None
async def test_form_reauth_legacy(hass, remote: Mock):
"""Test reauthenticate legacy."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"entry_id": entry.entry_id, "source": config_entries.SOURCE_REAUTH},
data=entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful"
async def test_form_reauth_websocket(hass, remotews: Mock):
"""Test reauthenticate websocket."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY)
entry.add_to_hass(hass)
assert entry.state == config_entries.ConfigEntryState.NOT_LOADED
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"entry_id": entry.entry_id, "source": config_entries.SOURCE_REAUTH},
data=entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful"
assert entry.state == config_entries.ConfigEntryState.LOADED
async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock):
"""Test reauthenticate websocket when we cannot connect on the first attempt."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"entry_id": entry.entry_id, "source": config_entries.SOURCE_REAUTH},
data=entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=ConnectionFailure,
), patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result2["type"] == "form"
assert result2["errors"] == {"base": RESULT_AUTH_MISSING}
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result3["type"] == "abort"
assert result3["reason"] == "reauth_successful"
async def test_form_reauth_websocket_not_supported(hass, remotews: Mock):
"""Test reauthenticate websocket when the device is not supported."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"entry_id": entry.entry_id, "source": config_entries.SOURCE_REAUTH},
data=entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=WebSocketException,
), patch(
"homeassistant.components.samsungtv.config_flow.socket.gethostbyname",
return_value="fake_host",
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result2["type"] == "abort"
assert result2["reason"] == "not_supported"