Fix authentication error when adding new devices to SMLIGHT (#138373)

* Fix authentication issue

Fixes #138216

* Fix incorrect mocks in unsupported device tests

* set _device_name in auth flow also

* Update get_info Mock to handle authentication

* Update tests
This commit is contained in:
TimL 2025-02-12 22:22:58 +11:00 committed by GitHub
parent 6ef1178a35
commit a3cde3d8ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 74 additions and 15 deletions

View File

@ -77,12 +77,14 @@ class SmlightConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
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):
info = await self.client.get_info()
self._host = str(info.device_ip)
self._device_name = str(info.hostname)
if info.model not in Devices:
return self.async_abort(reason="unsupported_device")
return await self._async_complete_entry(user_input)
except SmlightConnectionError:
return self.async_abort(reason="cannot_connect")

View File

@ -3,6 +3,7 @@
from collections.abc import AsyncGenerator, Generator
from unittest.mock import AsyncMock, MagicMock, patch
from pysmlight.exceptions import SmlightAuthError
from pysmlight.sse import sseClient
from pysmlight.web import CmdWrapper, Firmware, Info, Sensors
import pytest
@ -81,9 +82,16 @@ def mock_smlight_client(request: pytest.FixtureRequest) -> Generator[MagicMock]:
):
api = smlight_mock.return_value
api.host = MOCK_HOST
api.get_info.return_value = Info.from_dict(
load_json_object_fixture("info.json", DOMAIN)
)
def get_info_side_effect(*args, **kwargs) -> Info:
"""Return the info."""
if api.check_auth_needed.return_value and not api.authenticate.called:
raise SmlightAuthError
return Info.from_dict(load_json_object_fixture("info.json", DOMAIN))
api.get_info.side_effect = get_info_side_effect
api.get_sensors.return_value = Sensors.from_dict(
load_json_object_fixture("sensors.json", DOMAIN)
)

View File

@ -45,6 +45,7 @@ async def test_buttons(
mock_smlight_client: MagicMock,
) -> None:
"""Test creation of button entities."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ROUTER
await setup_integration(hass, mock_config_entry)
@ -78,6 +79,7 @@ async def test_disabled_by_default_buttons(
mock_smlight_client: MagicMock,
) -> None:
"""Test the disabled by default buttons."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ROUTER
await setup_integration(hass, mock_config_entry)
@ -96,7 +98,8 @@ async def test_remove_router_reconnect(
mock_smlight_client: MagicMock,
) -> None:
"""Test removal of orphaned router reconnect button."""
save_mock = mock_smlight_client.get_info.return_value
save_mock = mock_smlight_client.get_info.side_effect
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ROUTER
mock_config_entry = await setup_integration(hass, mock_config_entry)
@ -106,7 +109,7 @@ async def test_remove_router_reconnect(
assert len(entities) == 4
assert entities[3].unique_id == "aa:bb:cc:dd:ee:ff-reconnect_zigbee_router"
mock_smlight_client.get_info.return_value = save_mock
mock_smlight_client.get_info.side_effect = save_mock
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)

View File

@ -66,6 +66,46 @@ async def test_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> No
assert len(mock_setup_entry.mock_calls) == 1
async def test_user_flow_auth(
hass: HomeAssistant, mock_smlight_client: MagicMock, mock_setup_entry: AsyncMock
) -> None:
"""Test the full manual user flow with authentication."""
mock_smlight_client.check_auth_needed.return_value = True
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: "slzb-06p7.local",
},
)
assert result2["type"] is FlowResultType.FORM
assert result2["step_id"] == "auth"
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
},
)
assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["title"] == "SLZB-06p7"
assert result3["data"] == {
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
CONF_HOST: MOCK_HOST,
}
assert result3["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff"
assert len(mock_setup_entry.mock_calls) == 1
async def test_zeroconf_flow(
hass: HomeAssistant,
mock_smlight_client: MagicMock,
@ -145,7 +185,7 @@ async def test_zeroconf_flow_auth(
assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["context"]["source"] == "zeroconf"
assert result3["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff"
assert result3["title"] == "slzb-06"
assert result3["title"] == "SLZB-06p7"
assert result3["data"] == {
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
@ -162,6 +202,7 @@ async def test_zeroconf_unsupported_abort(
mock_smlight_client: MagicMock,
) -> None:
"""Test we abort zeroconf flow if device unsupported."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(model="SLZB-X")
result = await hass.config_entries.flow.async_init(
@ -186,6 +227,7 @@ async def test_user_unsupported_abort(
mock_smlight_client: MagicMock,
) -> None:
"""Test we abort user flow if unsupported device."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(model="SLZB-X")
result = await hass.config_entries.flow.async_init(
@ -206,15 +248,13 @@ async def test_user_unsupported_abort(
assert result2["reason"] == "unsupported_device"
async def test_user_unsupported_abort_auth(
async def test_user_unsupported_device_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,
@ -366,7 +406,7 @@ async def test_user_invalid_auth(
}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_smlight_client.get_info.mock_calls) == 4
assert len(mock_smlight_client.get_info.mock_calls) == 3
async def test_user_cannot_connect(

View File

@ -165,6 +165,7 @@ async def test_device_legacy_firmware(
"""Test device setup for old firmware version that dont support required API."""
LEGACY_VERSION = "v0.9.9"
mock_smlight_client.get_sensors.side_effect = SmlightError
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(
legacy_api=2, sw_version=LEGACY_VERSION, MAC="AA:BB:CC:DD:EE:FF"
)

View File

@ -132,6 +132,7 @@ async def test_update_firmware(
event_function(MOCK_FIRMWARE_DONE)
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(
sw_version="v2.7.5",
)
@ -153,6 +154,7 @@ async def test_update_zigbee2_firmware(
mock_smlight_client: MagicMock,
) -> None:
"""Test update of zigbee2 firmware where available."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info.from_dict(
load_json_object_fixture("info-MR1.json", DOMAIN)
)
@ -195,6 +197,7 @@ async def test_update_legacy_firmware_v2(
mock_smlight_client: MagicMock,
) -> None:
"""Test firmware update for legacy v2 firmware."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(
sw_version="v2.0.18",
legacy_api=1,
@ -220,6 +223,7 @@ async def test_update_legacy_firmware_v2(
event_function(MOCK_FIRMWARE_DONE)
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(
sw_version="v2.7.5",
)
@ -333,6 +337,7 @@ async def test_update_release_notes(
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test firmware release notes."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info.from_dict(
load_json_object_fixture("info-MR1.json", DOMAIN)
)