Raise ConfigEntryNotReady mqtt setup fails In LG ThinQ (#140488)

Co-authored-by: yunseon.park <yunseon.park@lge.com>
pull/135772/merge
LG-ThinQ-Integration 2025-04-19 18:50:13 +09:00 committed by GitHub
parent 35f9cc55f1
commit 83f2acddf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 85 additions and 36 deletions

View File

@ -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

View File

@ -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()

View File

@ -1034,5 +1034,10 @@
}
}
}
},
"exceptions": {
"failed_to_connect_mqtt": {
"message": "Failed to connect MQTT: {error}"
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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