mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 17:31:15 -04:00
Request Tuya QR code when the user confirms reauth (#183035)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Paulus Schoutsen
Claude Opus 5
parent
106c7608ea
commit
9dbc194de6
@@ -149,9 +149,23 @@ class TuyaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle initiation of re-authentication with Tuya."""
|
||||
if CONF_USER_CODE in entry_data:
|
||||
success, _ = await self.__async_get_qr_code(entry_data[CONF_USER_CODE])
|
||||
if success:
|
||||
return await self.async_step_scan()
|
||||
self.__user_code = entry_data[CONF_USER_CODE]
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
return await self.async_step_reauth_user_code()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Confirm re-authentication with Tuya."""
|
||||
# The QR code expires in minutes, while a reauth flow can sit untouched
|
||||
# for days. Request the code when the user is ready to scan it.
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
|
||||
success, _ = await self.__async_get_qr_code(self.__user_code)
|
||||
if success:
|
||||
return await self.async_step_scan()
|
||||
|
||||
return await self.async_step_reauth_user_code()
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
"login_error": "Login error ({code}): {msg}"
|
||||
},
|
||||
"step": {
|
||||
"reauth_confirm": {
|
||||
"description": "Your Smart Life or Tuya Smart account needs to be reauthenticated.\n\nSelect **Submit** to get a QR code to scan with the app.",
|
||||
"title": "[%key:common::config_flow::title::reauth%]"
|
||||
},
|
||||
"reauth_user_code": {
|
||||
"data": {
|
||||
"user_code": "User code"
|
||||
|
||||
@@ -134,9 +134,9 @@ async def test_user_flow_failed_scan(
|
||||
assert result4.get("type") is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_tuya_login_control")
|
||||
async def test_reauth_flow(
|
||||
hass: HomeAssistant,
|
||||
mock_tuya_login_control: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
@@ -146,19 +146,66 @@ async def test_reauth_flow(
|
||||
result = await mock_config_entry.start_reauth_flow(hass)
|
||||
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "scan"
|
||||
assert result.get("step_id") == "reauth_confirm"
|
||||
|
||||
# A reauth flow can sit untouched for days, so the QR code must not be
|
||||
# requested before the user is ready to scan it.
|
||||
mock_tuya_login_control.qr_code.assert_not_called()
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={},
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.ABORT
|
||||
assert result2.get("reason") == "reauth_successful"
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("step_id") == "scan"
|
||||
mock_tuya_login_control.qr_code.assert_called_once()
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={},
|
||||
)
|
||||
|
||||
assert result3.get("type") is FlowResultType.ABORT
|
||||
assert result3.get("reason") == "reauth_successful"
|
||||
|
||||
assert mock_config_entry == snapshot
|
||||
|
||||
|
||||
async def test_reauth_flow_without_user_code(
|
||||
hass: HomeAssistant,
|
||||
mock_tuya_login_control: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test reauthentication of an entry that predates the stored user code."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
data = dict(mock_config_entry.data)
|
||||
del data[CONF_USER_CODE]
|
||||
hass.config_entries.async_update_entry(mock_config_entry, data=data)
|
||||
|
||||
result = await mock_config_entry.start_reauth_flow(hass)
|
||||
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "reauth_user_code"
|
||||
mock_tuya_login_control.qr_code.assert_not_called()
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_USER_CODE: "12345"},
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("step_id") == "scan"
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={},
|
||||
)
|
||||
|
||||
assert result3.get("type") is FlowResultType.ABORT
|
||||
assert result3.get("reason") == "reauth_successful"
|
||||
|
||||
|
||||
async def test_reauth_flow_failed_qr_code(
|
||||
hass: HomeAssistant,
|
||||
mock_tuya_login_control: MagicMock,
|
||||
@@ -173,15 +220,23 @@ async def test_reauth_flow_failed_qr_code(
|
||||
result = await mock_config_entry.start_reauth_flow(hass)
|
||||
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "reauth_user_code"
|
||||
assert result.get("step_id") == "reauth_confirm"
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={},
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("step_id") == "reauth_user_code"
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_USER_CODE: "12345"},
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("errors") == {"base": "login_error"}
|
||||
assert result3.get("type") is FlowResultType.FORM
|
||||
assert result3.get("errors") == {"base": "login_error"}
|
||||
|
||||
# This time it worked out
|
||||
mock_tuya_login_control.qr_code.return_value["success"] = True
|
||||
|
||||
Reference in New Issue
Block a user