From 83f2acddf88744b53f66048e447fa4a8c7bf01ce Mon Sep 17 00:00:00 2001 From: LG-ThinQ-Integration Date: Sat, 19 Apr 2025 18:50:13 +0900 Subject: [PATCH] Raise ConfigEntryNotReady mqtt setup fails In LG ThinQ (#140488) Co-authored-by: yunseon.park --- homeassistant/components/lg_thinq/__init__.py | 12 +++++- homeassistant/components/lg_thinq/mqtt.py | 17 +++----- .../components/lg_thinq/strings.json | 5 +++ tests/components/lg_thinq/conftest.py | 43 ++++++++++++------- tests/components/lg_thinq/test_config_flow.py | 11 +++-- tests/components/lg_thinq/test_init.py | 33 ++++++++++++-- 6 files changed, 85 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/lg_thinq/__init__.py b/homeassistant/components/lg_thinq/__init__.py index f83cbadf925..47282b6cc22 100644 --- a/homeassistant/components/lg_thinq/__init__.py +++ b/homeassistant/components/lg_thinq/__init__.py @@ -22,7 +22,7 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_track_time_interval -from .const import CONF_CONNECT_CLIENT_ID, MQTT_SUBSCRIPTION_INTERVAL +from .const import CONF_CONNECT_CLIENT_ID, DOMAIN, MQTT_SUBSCRIPTION_INTERVAL from .coordinator import DeviceDataUpdateCoordinator, async_setup_device_coordinator from .mqtt import ThinQMQTT @@ -137,7 +137,15 @@ async def async_setup_mqtt( entry.runtime_data.mqtt_client = mqtt_client # Try to connect. - result = await mqtt_client.async_connect() + try: + result = await mqtt_client.async_connect() + except (AttributeError, ThinQAPIException, TypeError, ValueError) as exc: + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="failed_to_connect_mqtt", + translation_placeholders={"error": str(exc)}, + ) from exc + if not result: _LOGGER.error("Failed to set up mqtt connection") return diff --git a/homeassistant/components/lg_thinq/mqtt.py b/homeassistant/components/lg_thinq/mqtt.py index 025f80f78b1..d6ff1f72b8f 100644 --- a/homeassistant/components/lg_thinq/mqtt.py +++ b/homeassistant/components/lg_thinq/mqtt.py @@ -43,19 +43,16 @@ class ThinQMQTT: async def async_connect(self) -> bool: """Create a mqtt client and then try to connect.""" - try: - self.client = await ThinQMQTTClient( - self.thinq_api, self.client_id, self.on_message_received - ) - if self.client is None: - return False - # Connect to server and create certificate. - return await self.client.async_prepare_mqtt() - except (ThinQAPIException, TypeError, ValueError): - _LOGGER.exception("Failed to connect") + self.client = await ThinQMQTTClient( + self.thinq_api, self.client_id, self.on_message_received + ) + if self.client is None: return False + # Connect to server and create certificate. + return await self.client.async_prepare_mqtt() + async def async_disconnect(self, event: Event | None = None) -> None: """Unregister client and disconnects handlers.""" await self.async_end_subscribes() diff --git a/homeassistant/components/lg_thinq/strings.json b/homeassistant/components/lg_thinq/strings.json index f609be91de5..a5fb81e3818 100644 --- a/homeassistant/components/lg_thinq/strings.json +++ b/homeassistant/components/lg_thinq/strings.json @@ -1034,5 +1034,10 @@ } } } + }, + "exceptions": { + "failed_to_connect_mqtt": { + "message": "Failed to connect MQTT: {error}" + } } } diff --git a/tests/components/lg_thinq/conftest.py b/tests/components/lg_thinq/conftest.py index 05cb3164137..17bbf068305 100644 --- a/tests/components/lg_thinq/conftest.py +++ b/tests/components/lg_thinq/conftest.py @@ -68,7 +68,7 @@ def mock_uuid() -> Generator[AsyncMock]: @pytest.fixture -def mock_thinq_api(mock_thinq_mqtt_client: AsyncMock) -> Generator[AsyncMock]: +def mock_config_thinq_api() -> Generator[AsyncMock]: """Mock a thinq api.""" with ( patch("homeassistant.components.lg_thinq.ThinQApi", autospec=True) as mock_api, @@ -77,6 +77,26 @@ def mock_thinq_api(mock_thinq_mqtt_client: AsyncMock) -> Generator[AsyncMock]: new=mock_api, ), ): + thinq_api = mock_api.return_value + thinq_api.async_get_device_list.return_value = ["air_conditioner"] + yield thinq_api + + +@pytest.fixture +def mock_invalid_thinq_api(mock_config_thinq_api: AsyncMock) -> AsyncMock: + """Mock an invalid thinq api.""" + mock_config_thinq_api.async_get_device_list = AsyncMock( + side_effect=ThinQAPIException( + code="1309", message="Not allowed api call", headers=None + ) + ) + return mock_config_thinq_api + + +@pytest.fixture +def mock_thinq_api() -> Generator[AsyncMock]: + """Mock a thinq api.""" + with patch("homeassistant.components.lg_thinq.ThinQApi", autospec=True) as mock_api: thinq_api = mock_api.return_value thinq_api.async_get_device_list.return_value = [ load_json_object_fixture("air_conditioner/device.json", DOMAIN) @@ -92,19 +112,10 @@ def mock_thinq_api(mock_thinq_mqtt_client: AsyncMock) -> Generator[AsyncMock]: @pytest.fixture def mock_thinq_mqtt_client() -> Generator[AsyncMock]: - """Mock a thinq api.""" + """Mock a thinq mqtt client.""" with patch( - "homeassistant.components.lg_thinq.mqtt.ThinQMQTTClient", autospec=True - ) as mock_api: - yield mock_api - - -@pytest.fixture -def mock_invalid_thinq_api(mock_thinq_api: AsyncMock) -> AsyncMock: - """Mock an invalid thinq api.""" - mock_thinq_api.async_get_device_list = AsyncMock( - side_effect=ThinQAPIException( - code="1309", message="Not allowed api call", headers=None - ) - ) - return mock_thinq_api + "homeassistant.components.lg_thinq.mqtt.ThinQMQTTClient", + autospec=True, + return_value=True, + ): + yield diff --git a/tests/components/lg_thinq/test_config_flow.py b/tests/components/lg_thinq/test_config_flow.py index 8c5afb4dac7..d1530ed29cd 100644 --- a/tests/components/lg_thinq/test_config_flow.py +++ b/tests/components/lg_thinq/test_config_flow.py @@ -15,7 +15,7 @@ from tests.common import MockConfigEntry async def test_config_flow( hass: HomeAssistant, - mock_thinq_api: AsyncMock, + mock_config_thinq_api: AsyncMock, mock_uuid: AsyncMock, mock_setup_entry: AsyncMock, ) -> None: @@ -37,11 +37,12 @@ async def test_config_flow( CONF_CONNECT_CLIENT_ID: MOCK_CONNECT_CLIENT_ID, } - mock_thinq_api.async_get_device_list.assert_called_once() + mock_config_thinq_api.async_get_device_list.assert_called_once() async def test_config_flow_invalid_pat( - hass: HomeAssistant, mock_invalid_thinq_api: AsyncMock + hass: HomeAssistant, + mock_invalid_thinq_api: AsyncMock, ) -> None: """Test that an thinq flow should be aborted with an invalid PAT.""" result = await hass.config_entries.flow.async_init( @@ -55,7 +56,9 @@ async def test_config_flow_invalid_pat( async def test_config_flow_already_configured( - hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_thinq_api: AsyncMock + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_config_thinq_api: AsyncMock, ) -> None: """Test that thinq flow should be aborted when already configured.""" mock_config_entry.add_to_hass(hass) diff --git a/tests/components/lg_thinq/test_init.py b/tests/components/lg_thinq/test_init.py index 7da7e79fec0..bf24704d379 100644 --- a/tests/components/lg_thinq/test_init.py +++ b/tests/components/lg_thinq/test_init.py @@ -1,22 +1,29 @@ """Tests for the LG ThinQ integration.""" -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, patch + +import pytest from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from . import setup_integration + from tests.common import MockConfigEntry async def test_load_unload_entry( hass: HomeAssistant, mock_thinq_api: AsyncMock, + mock_thinq_mqtt_client: AsyncMock, mock_config_entry: MockConfigEntry, ) -> None: """Test load and unload entry.""" - mock_config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() + with patch( + "homeassistant.components.lg_thinq.ThinQMQTT.async_connect", + return_value=True, + ): + await setup_integration(hass, mock_config_entry) assert mock_config_entry.state is ConfigEntryState.LOADED @@ -24,3 +31,21 @@ async def test_load_unload_entry( await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +@pytest.mark.parametrize("exception", [AttributeError(), TypeError(), ValueError()]) +async def test_config_not_ready( + hass: HomeAssistant, + mock_thinq_api: AsyncMock, + mock_thinq_mqtt_client: AsyncMock, + mock_config_entry: MockConfigEntry, + exception: Exception, +) -> None: + """Test for setup failure exception occurred.""" + with patch( + "homeassistant.components.lg_thinq.ThinQMQTT.async_connect", + side_effect=exception, + ): + await setup_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY