Add protected call for data retrieval (#150035)

This commit is contained in:
Willem-Jan van Rootselaar
2025-08-08 23:58:19 +02:00
committed by GitHub
parent e585b3abd1
commit b41a9575af
3 changed files with 88 additions and 7 deletions

View File

@@ -2,7 +2,16 @@
import dataclasses import dataclasses
from bsblan import BSBLAN, BSBLANConfig, Device, Info, StaticState from bsblan import (
BSBLAN,
BSBLANAuthError,
BSBLANConfig,
BSBLANConnectionError,
BSBLANError,
Device,
Info,
StaticState,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@@ -13,9 +22,14 @@ from homeassistant.const import (
Platform, Platform,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_PASSKEY from .const import CONF_PASSKEY, DOMAIN
from .coordinator import BSBLanUpdateCoordinator from .coordinator import BSBLanUpdateCoordinator
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.WATER_HEATER] PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.WATER_HEATER]
@@ -54,10 +68,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) -> bo
coordinator = BSBLanUpdateCoordinator(hass, entry, bsblan) coordinator = BSBLanUpdateCoordinator(hass, entry, bsblan)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
# Fetch all required data concurrently try:
device = await bsblan.device() # Fetch all required data sequentially
info = await bsblan.info() device = await bsblan.device()
static = await bsblan.static_values() info = await bsblan.info()
static = await bsblan.static_values()
except BSBLANConnectionError as err:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="setup_connection_error",
translation_placeholders={"host": entry.data[CONF_HOST]},
) from err
except BSBLANAuthError as err:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_auth_error",
) from err
except BSBLANError as err:
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="setup_general_error",
) from err
entry.runtime_data = BSBLanData( entry.runtime_data = BSBLanData(
client=bsblan, client=bsblan,

View File

@@ -41,6 +41,11 @@
"passkey": "[%key:component::bsblan::config::step::user::data::passkey%]", "passkey": "[%key:component::bsblan::config::step::user::data::passkey%]",
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]" "password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"passkey": "[%key:component::bsblan::config::step::user::data_description::passkey%]",
"username": "[%key:component::bsblan::config::step::user::data_description::username%]",
"password": "[%key:component::bsblan::config::step::user::data_description::password%]"
} }
} }
}, },
@@ -66,6 +71,15 @@
}, },
"set_operation_mode_error": { "set_operation_mode_error": {
"message": "An error occurred while setting the operation mode" "message": "An error occurred while setting the operation mode"
},
"setup_connection_error": {
"message": "Failed to retrieve static device data from BSB-Lan device at {host}"
},
"setup_auth_error": {
"message": "Authentication failed while retrieving static device data"
},
"setup_general_error": {
"message": "An unknown error occurred while retrieving static device data"
} }
}, },
"entity": { "entity": {

View File

@@ -2,8 +2,9 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
from bsblan import BSBLANAuthError, BSBLANConnectionError from bsblan import BSBLANAuthError, BSBLANConnectionError, BSBLANError
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.bsblan.const import DOMAIN from homeassistant.components.bsblan.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
@@ -75,3 +76,38 @@ async def test_config_entry_auth_failed_triggers_reauth(
assert len(flows) == 1 assert len(flows) == 1
assert flows[0]["context"]["source"] == "reauth" assert flows[0]["context"]["source"] == "reauth"
assert flows[0]["context"]["entry_id"] == mock_config_entry.entry_id assert flows[0]["context"]["entry_id"] == mock_config_entry.entry_id
@pytest.mark.parametrize(
("method", "exception", "expected_state"),
[
(
"device",
BSBLANConnectionError("Connection failed"),
ConfigEntryState.SETUP_RETRY,
),
(
"info",
BSBLANAuthError("Authentication failed"),
ConfigEntryState.SETUP_ERROR,
),
("static_values", BSBLANError("General error"), ConfigEntryState.SETUP_ERROR),
],
)
async def test_config_entry_static_data_errors(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_bsblan: MagicMock,
method: str,
exception: Exception,
expected_state: ConfigEntryState,
) -> None:
"""Test various errors during static data fetching trigger appropriate config entry states."""
# Mock the specified method to raise the exception
getattr(mock_bsblan, method).side_effect = exception
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is expected_state