Abort config flow is device is unsupported (#136505)

* Abort config flow if device is not yet supported

* Abort on user step for unsupported device

* Add string for unsupported device

* fix tests due to extra get_info calls

* add tests for unsupported devices to abort flow
This commit is contained in:
TimL 2025-01-25 23:17:38 +11:00 committed by GitHub
parent 71d63bac8d
commit 05bdfe7aa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 112 additions and 7 deletions

View File

@ -6,6 +6,7 @@ from collections.abc import Mapping
from typing import Any from typing import Any
from pysmlight import Api2 from pysmlight import Api2
from pysmlight.const import Devices
from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError
import voluptuous as vol import voluptuous as vol
@ -51,6 +52,11 @@ class SmlightConfigFlow(ConfigFlow, domain=DOMAIN):
self.client = Api2(self.host, session=async_get_clientsession(self.hass)) self.client = Api2(self.host, session=async_get_clientsession(self.hass))
try: try:
info = await self.client.get_info()
if info.model not in Devices:
return self.async_abort(reason="unsupported_device")
if not await self._async_check_auth_required(user_input): if not await self._async_check_auth_required(user_input):
return await self._async_complete_entry(user_input) return await self._async_complete_entry(user_input)
except SmlightConnectionError: except SmlightConnectionError:
@ -70,6 +76,11 @@ class SmlightConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
try: try:
info = await self.client.get_info()
if info.model not in Devices:
return self.async_abort(reason="unsupported_device")
if not await self._async_check_auth_required(user_input): if not await self._async_check_auth_required(user_input):
return await self._async_complete_entry(user_input) return await self._async_complete_entry(user_input)
except SmlightConnectionError: except SmlightConnectionError:
@ -116,6 +127,11 @@ class SmlightConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
user_input[CONF_HOST] = self.host user_input[CONF_HOST] = self.host
try: try:
info = await self.client.get_info()
if info.model not in Devices:
return self.async_abort(reason="unsupported_device")
if not await self._async_check_auth_required(user_input): if not await self._async_check_auth_required(user_input):
return await self._async_complete_entry(user_input) return await self._async_complete_entry(user_input)

View File

@ -38,7 +38,8 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reauth_failed": "[%key:common::config_flow::error::invalid_auth%]", "reauth_failed": "[%key:common::config_flow::error::invalid_auth%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unsupported_device": "This device is not yet supported by the SMLIGHT integration"
} }
}, },
"entity": { "entity": {

View File

@ -3,6 +3,7 @@
from ipaddress import ip_address from ipaddress import ip_address
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock, MagicMock
from pysmlight import Info
from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError
import pytest import pytest
@ -97,7 +98,7 @@ async def test_zeroconf_flow(
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_smlight_client.get_info.mock_calls) == 1 assert len(mock_smlight_client.get_info.mock_calls) == 2
async def test_zeroconf_flow_auth( async def test_zeroconf_flow_auth(
@ -151,12 +152,99 @@ async def test_zeroconf_flow_auth(
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_smlight_client.get_info.mock_calls) == 1 assert len(mock_smlight_client.get_info.mock_calls) == 3
async def test_zeroconf_unsupported_abort(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test we abort zeroconf flow if device unsupported."""
mock_smlight_client.get_info.return_value = Info(model="SLZB-X")
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=DISCOVERY_INFO
)
assert result["description_placeholders"] == {"host": MOCK_HOST}
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm_discovery"
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={}
)
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "unsupported_device"
async def test_user_unsupported_abort(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test we abort user flow if unsupported device."""
mock_smlight_client.get_info.return_value = Info(model="SLZB-X")
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: MOCK_HOST,
},
)
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "unsupported_device"
async def test_user_unsupported_abort_auth(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test we abort user flow if unsupported device (with auth)."""
mock_smlight_client.check_auth_needed.return_value = True
mock_smlight_client.authenticate.side_effect = SmlightAuthError
mock_smlight_client.get_info.side_effect = SmlightAuthError
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_HOST: MOCK_HOST,
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "auth"
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(model="SLZB-X")
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
},
)
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "unsupported_device"
@pytest.mark.usefixtures("mock_smlight_client") @pytest.mark.usefixtures("mock_smlight_client")
async def test_user_device_exists_abort( async def test_user_device_exists_abort(
hass: HomeAssistant, mock_config_entry: MockConfigEntry hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test we abort user flow if device already configured.""" """Test we abort user flow if device already configured."""
mock_config_entry.add_to_hass(hass) mock_config_entry.add_to_hass(hass)
@ -239,7 +327,7 @@ async def test_user_invalid_auth(
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_smlight_client.get_info.mock_calls) == 1 assert len(mock_smlight_client.get_info.mock_calls) == 4
async def test_user_cannot_connect( async def test_user_cannot_connect(
@ -276,7 +364,7 @@ async def test_user_cannot_connect(
assert result2["title"] == "SLZB-06p7" assert result2["title"] == "SLZB-06p7"
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_smlight_client.get_info.mock_calls) == 1 assert len(mock_smlight_client.get_info.mock_calls) == 3
async def test_auth_cannot_connect( async def test_auth_cannot_connect(
@ -378,7 +466,7 @@ async def test_zeroconf_legacy_mac(
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_smlight_client.get_info.mock_calls) == 2 assert len(mock_smlight_client.get_info.mock_calls) == 3
async def test_reauth_flow( async def test_reauth_flow(