Add reconfigure to LG webOS TV (#135360)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>pull/135379/head^2
parent
19f460614e
commit
a745e079e9
|
@ -20,7 +20,7 @@ from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from . import async_control_connect, update_client_key
|
||||
from . import async_control_connect
|
||||
from .const import CONF_SOURCES, DEFAULT_NAME, DOMAIN, WEBOSTV_EXCEPTIONS
|
||||
from .helpers import async_get_sources
|
||||
|
||||
|
@ -53,14 +53,11 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
|||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
self._host = user_input[CONF_HOST]
|
||||
return await self.async_step_pairing()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||
)
|
||||
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
|
||||
|
||||
async def async_step_pairing(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
|
@ -69,13 +66,13 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
|||
self._async_abort_entries_match({CONF_HOST: self._host})
|
||||
|
||||
self.context["title_placeholders"] = {"name": self._name}
|
||||
errors = {}
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
try:
|
||||
client = await async_control_connect(self._host, None)
|
||||
except WebOsTvPairError:
|
||||
return self.async_abort(reason="error_pairing")
|
||||
errors["base"] = "error_pairing"
|
||||
except WEBOSTV_EXCEPTIONS:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
|
@ -130,20 +127,56 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
|||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Dialog that informs the user that reauth is required."""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
try:
|
||||
client = await async_control_connect(self._host, None)
|
||||
except WebOsTvPairError:
|
||||
return self.async_abort(reason="error_pairing")
|
||||
errors["base"] = "error_pairing"
|
||||
except WEBOSTV_EXCEPTIONS:
|
||||
return self.async_abort(reason="reauth_unsuccessful")
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
data = {CONF_HOST: self._host, CONF_CLIENT_SECRET: client.client_key}
|
||||
return self.async_update_reload_and_abort(reauth_entry, data=data)
|
||||
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
update_client_key(self.hass, reauth_entry, client)
|
||||
await self.hass.config_entries.async_reload(reauth_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
return self.async_show_form(step_id="reauth_confirm", errors=errors)
|
||||
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration of the integration."""
|
||||
errors: dict[str, str] = {}
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
|
||||
if user_input is not None:
|
||||
host = user_input[CONF_HOST]
|
||||
client_key = reconfigure_entry.data.get(CONF_CLIENT_SECRET)
|
||||
|
||||
try:
|
||||
client = await async_control_connect(host, client_key)
|
||||
except WebOsTvPairError:
|
||||
errors["base"] = "error_pairing"
|
||||
except WEBOSTV_EXCEPTIONS:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(client.hello_info["deviceUUID"])
|
||||
self._abort_if_unique_id_mismatch(reason="wrong_device")
|
||||
data = {CONF_HOST: host, CONF_CLIENT_SECRET: client.client_key}
|
||||
return self.async_update_reload_and_abort(reconfigure_entry, data=data)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_HOST, default=reconfigure_entry.data.get(CONF_HOST)
|
||||
): cv.string
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
class OptionsFlowHandler(OptionsFlow):
|
||||
|
|
|
@ -7,9 +7,7 @@ rules:
|
|||
status: exempt
|
||||
comment: The integration does not use common patterns.
|
||||
config-flow-test-coverage: done
|
||||
config-flow:
|
||||
status: todo
|
||||
comment: make reauth flow more graceful
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions:
|
||||
status: todo
|
||||
|
@ -66,7 +64,7 @@ rules:
|
|||
icon-translations:
|
||||
status: exempt
|
||||
comment: The only entity can use the device class.
|
||||
reconfiguration-flow: todo
|
||||
reconfiguration-flow: done
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: The integration does not have anything to repair.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"host": "[%key:common::config_flow::data::host%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Hostname or IP address of your webOS TV."
|
||||
"host": "Hostname or IP address of your LG webOS TV."
|
||||
}
|
||||
},
|
||||
"pairing": {
|
||||
|
@ -18,17 +18,26 @@
|
|||
"reauth_confirm": {
|
||||
"title": "[%key:component::webostv::config::step::pairing::title%]",
|
||||
"description": "[%key:component::webostv::config::step::pairing::description%]"
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "[%key:component::webostv::config::step::user::data_description::host%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect, please turn on your TV or check the IP address"
|
||||
"cannot_connect": "Failed to connect, please turn on your TV and try again.",
|
||||
"error_pairing": "Pairing failed, make sure to accept the pairing request on the TV and try again."
|
||||
},
|
||||
"abort": {
|
||||
"error_pairing": "Connected to LG webOS TV but not paired",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"reauth_unsuccessful": "Re-authentication was unsuccessful, please turn on your TV and try again."
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"wrong_device": "The configured device is not the same found on this Hostname or IP address."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
@ -38,6 +47,9 @@
|
|||
"description": "Select enabled sources",
|
||||
"data": {
|
||||
"sources": "Sources list"
|
||||
},
|
||||
"data_description": {
|
||||
"sources": "List of sources to enable"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
"""Test the WebOS Tv config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aiowebostv import WebOsTvPairError
|
||||
import pytest
|
||||
|
||||
|
@ -105,7 +103,7 @@ async def test_options_flow_cannot_retrieve(hass: HomeAssistant, client) -> None
|
|||
"""Test options config flow cannot retrieve sources."""
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
client.connect = AsyncMock(side_effect=ConnectionRefusedError())
|
||||
client.connect.side_effect = ConnectionRefusedError
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -113,7 +111,7 @@ async def test_options_flow_cannot_retrieve(hass: HomeAssistant, client) -> None
|
|||
assert result["errors"] == {"base": "cannot_retrieve"}
|
||||
|
||||
# recover
|
||||
client.connect = AsyncMock(return_value=True)
|
||||
client.connect.side_effect = None
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input=None,
|
||||
|
@ -139,7 +137,7 @@ async def test_form_cannot_connect(hass: HomeAssistant, client) -> None:
|
|||
data=MOCK_USER_CONFIG,
|
||||
)
|
||||
|
||||
client.connect = AsyncMock(side_effect=ConnectionRefusedError())
|
||||
client.connect.side_effect = ConnectionRefusedError
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
@ -148,7 +146,7 @@ async def test_form_cannot_connect(hass: HomeAssistant, client) -> None:
|
|||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
# recover
|
||||
client.connect = AsyncMock(return_value=True)
|
||||
client.connect.side_effect = None
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
@ -165,13 +163,22 @@ async def test_form_pairexception(hass: HomeAssistant, client) -> None:
|
|||
data=MOCK_USER_CONFIG,
|
||||
)
|
||||
|
||||
client.connect = AsyncMock(side_effect=WebOsTvPairError("error"))
|
||||
client.connect.side_effect = WebOsTvPairError
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "error_pairing"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "error_pairing"}
|
||||
|
||||
# recover
|
||||
client.connect.side_effect = None
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == TV_NAME
|
||||
|
||||
|
||||
async def test_entry_already_configured(hass: HomeAssistant, client) -> None:
|
||||
|
@ -267,9 +274,7 @@ async def test_form_abort_uuid_configured(hass: HomeAssistant, client) -> None:
|
|||
assert entry.data[CONF_HOST] == "new_host"
|
||||
|
||||
|
||||
async def test_reauth_successful(
|
||||
hass: HomeAssistant, client, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
async def test_reauth_successful(hass: HomeAssistant, client) -> None:
|
||||
"""Test that the reauthorization is successful."""
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
|
@ -282,7 +287,7 @@ async def test_reauth_successful(
|
|||
assert result["step_id"] == "reauth_confirm"
|
||||
assert entry.data[CONF_CLIENT_SECRET] == CLIENT_KEY
|
||||
|
||||
monkeypatch.setattr(client, "client_key", "new_key")
|
||||
client.client_key = "new_key"
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
@ -293,15 +298,13 @@ async def test_reauth_successful(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect", "reason"),
|
||||
("side_effect", "error"),
|
||||
[
|
||||
(WebOsTvPairError, "error_pairing"),
|
||||
(ConnectionRefusedError, "reauth_unsuccessful"),
|
||||
(ConnectionRefusedError, "cannot_connect"),
|
||||
],
|
||||
)
|
||||
async def test_reauth_errors(
|
||||
hass: HomeAssistant, client, monkeypatch: pytest.MonkeyPatch, side_effect, reason
|
||||
) -> None:
|
||||
async def test_reauth_errors(hass: HomeAssistant, client, side_effect, error) -> None:
|
||||
"""Test reauthorization errors."""
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
|
@ -318,5 +321,88 @@ async def test_reauth_errors(
|
|||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": error}
|
||||
|
||||
client.connect.side_effect = None
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == reason
|
||||
assert result["reason"] == "reauth_successful"
|
||||
|
||||
|
||||
async def test_reconfigure_successful(hass: HomeAssistant, client) -> None:
|
||||
"""Test that the reconfigure is successful."""
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
result = await entry.start_reconfigure_flow(hass)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "new_host"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
assert entry.data[CONF_HOST] == "new_host"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect", "error"),
|
||||
[
|
||||
(WebOsTvPairError, "error_pairing"),
|
||||
(ConnectionRefusedError, "cannot_connect"),
|
||||
],
|
||||
)
|
||||
async def test_reconfigure_errors(
|
||||
hass: HomeAssistant, client, side_effect, error
|
||||
) -> None:
|
||||
"""Test reconfigure errors."""
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
result = await entry.start_reconfigure_flow(hass)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure"
|
||||
|
||||
client.connect.side_effect = side_effect
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "new_host"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": error}
|
||||
|
||||
client.connect.side_effect = None
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "new_host"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
|
||||
|
||||
async def test_reconfigure_wrong_device(hass: HomeAssistant, client) -> None:
|
||||
"""Test abort if reconfigure host is wrong webOS TV device."""
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
result = await entry.start_reconfigure_flow(hass)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure"
|
||||
|
||||
client.hello_info = {"deviceUUID": "wrong_uuid"}
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "new_host"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "wrong_device"
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
"""The tests for the LG webOS TV platform."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
from aiowebostv import WebOsTvPairError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.media_player import ATTR_INPUT_SOURCE_LIST
|
||||
from homeassistant.components.webostv.const import CONF_SOURCES, DOMAIN
|
||||
|
@ -15,12 +12,10 @@ from . import setup_webostv
|
|||
from .const import ENTITY_ID
|
||||
|
||||
|
||||
async def test_reauth_setup_entry(
|
||||
hass: HomeAssistant, client, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
async def test_reauth_setup_entry(hass: HomeAssistant, client) -> None:
|
||||
"""Test reauth flow triggered by setup entry."""
|
||||
monkeypatch.setattr(client, "is_connected", Mock(return_value=False))
|
||||
monkeypatch.setattr(client, "connect", Mock(side_effect=WebOsTvPairError))
|
||||
client.is_connected.return_value = False
|
||||
client.connect.side_effect = WebOsTvPairError
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
@ -37,11 +32,9 @@ async def test_reauth_setup_entry(
|
|||
assert flow["context"].get("entry_id") == entry.entry_id
|
||||
|
||||
|
||||
async def test_key_update_setup_entry(
|
||||
hass: HomeAssistant, client, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
async def test_key_update_setup_entry(hass: HomeAssistant, client) -> None:
|
||||
"""Test key update from setup entry."""
|
||||
monkeypatch.setattr(client, "client_key", "new_key")
|
||||
client.client_key = "new_key"
|
||||
entry = await setup_webostv(hass)
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""The tests for the WebOS TV notify platform."""
|
||||
|
||||
from unittest.mock import Mock, call
|
||||
from unittest.mock import call
|
||||
|
||||
from aiowebostv import WebOsTvPairError
|
||||
import pytest
|
||||
|
@ -74,14 +74,12 @@ async def test_notify(hass: HomeAssistant, client) -> None:
|
|||
)
|
||||
|
||||
|
||||
async def test_notify_not_connected(
|
||||
hass: HomeAssistant, client, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
async def test_notify_not_connected(hass: HomeAssistant, client) -> None:
|
||||
"""Test sending a message when client is not connected."""
|
||||
await setup_webostv(hass)
|
||||
assert hass.services.has_service(NOTIFY_DOMAIN, SERVICE_NAME)
|
||||
|
||||
monkeypatch.setattr(client, "is_connected", Mock(return_value=False))
|
||||
client.is_connected.return_value = False
|
||||
await hass.services.async_call(
|
||||
NOTIFY_DOMAIN,
|
||||
SERVICE_NAME,
|
||||
|
@ -99,16 +97,13 @@ async def test_notify_not_connected(
|
|||
|
||||
|
||||
async def test_icon_not_found(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
client,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, client
|
||||
) -> None:
|
||||
"""Test notify icon not found error."""
|
||||
await setup_webostv(hass)
|
||||
assert hass.services.has_service(NOTIFY_DOMAIN, SERVICE_NAME)
|
||||
|
||||
monkeypatch.setattr(client, "send_message", Mock(side_effect=FileNotFoundError))
|
||||
client.send_message.side_effect = FileNotFoundError
|
||||
await hass.services.async_call(
|
||||
NOTIFY_DOMAIN,
|
||||
SERVICE_NAME,
|
||||
|
@ -134,19 +129,14 @@ async def test_icon_not_found(
|
|||
],
|
||||
)
|
||||
async def test_connection_errors(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
client,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
side_effect,
|
||||
error,
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, client, side_effect, error
|
||||
) -> None:
|
||||
"""Test connection errors scenarios."""
|
||||
await setup_webostv(hass)
|
||||
assert hass.services.has_service("notify", SERVICE_NAME)
|
||||
|
||||
monkeypatch.setattr(client, "is_connected", Mock(return_value=False))
|
||||
monkeypatch.setattr(client, "connect", Mock(side_effect=side_effect))
|
||||
client.is_connected.return_value = False
|
||||
client.connect.side_effect = side_effect
|
||||
await hass.services.async_call(
|
||||
NOTIFY_DOMAIN,
|
||||
SERVICE_NAME,
|
||||
|
@ -159,7 +149,7 @@ async def test_connection_errors(
|
|||
blocking=True,
|
||||
)
|
||||
assert client.mock_calls[0] == call.connect()
|
||||
assert client.connect.call_count == 1
|
||||
assert client.connect.call_count == 2
|
||||
client.send_message.assert_not_called()
|
||||
assert error in caplog.text
|
||||
|
||||
|
|
Loading…
Reference in New Issue