Fixed a couple issues as well as added a test file

This commit is contained in:
NjDaGreat
2024-07-17 22:49:41 +00:00
parent 976ebac457
commit 7c65c0df83
9 changed files with 130 additions and 35 deletions
+1
View File
@@ -466,6 +466,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/flume/ @ChrisMandich @bdraco @jeeftor
/tests/components/flume/ @ChrisMandich @bdraco @jeeftor
/homeassistant/components/fluss/ @fluss
/tests/components/fluss/ @fluss
/homeassistant/components/flux_led/ @icemanch
/tests/components/flux_led/ @icemanch
/homeassistant/components/forecast_solar/ @klaasnicolaas @frenck
+18 -8
View File
@@ -8,23 +8,33 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant
from .api import FlussApiClient
from .const import DOMAIN
from .api import (
FlussApiClient,
FlussApiClientAuthenticationError, # noqa: F401
FlussApiClientCommunicationError, # noqa: F401
FlussApiClientError, # noqa: F401
)
LOGGER = logging.getLogger(__package__)
# For your initial PR, limit it to 1 platform.
PLATFORMS: list[Platform] = [Platform.BUTTON]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Fluss+ from a config entry."""
try:
api = FlussApiClient(entry.data[CONF_API_KEY], hass)
except FlussApiClientAuthenticationError as e:
LOGGER.error("Authentication error initializing FlussApiClient: %s", e)
return False
except FlussApiClientCommunicationError as e:
LOGGER.error("Communication error initializing FlussApiClient: %s", e)
return False
except FlussApiClientError as e:
LOGGER.error("General error initializing FlussApiClient: %s", e)
return False
api = FlussApiClient(entry.data[CONF_API_KEY], hass)
response = await api.async_get_devices()
LOGGER.warning("D %s", response)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = api
entry.runtime_data = {"api": api}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
+4
View File
@@ -20,6 +20,10 @@ class FlussApiClientError(Exception):
"""Exception to indicate a general API error."""
class FlussDeviceError(Exception):
"""Exception to indicate that an error occurred when retriveing devices."""
class FlussApiClientCommunicationError(FlussApiClientError):
"""Exception to indicate a communication error."""
+18 -9
View File
@@ -6,8 +6,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .api import FlussApiClient
from .const import DOMAIN
from .api import FlussApiClient, FlussDeviceError
from .device import FlussButton
LOGGER = logging.getLogger(__package__)
@@ -18,16 +17,26 @@ async def async_setup_entry(
) -> None:
"""Set up the Fluss Devices."""
api: FlussApiClient = hass.data[DOMAIN][entry.entry_id]
entry_data = entry.runtime_data
api: FlussApiClient = entry_data["api"]
devices = await api.async_get_devices()
if isinstance(devices, dict) and "devices" in devices:
devices = devices["devices"]
if not isinstance(devices, list):
try:
devices_data = await api.async_get_devices()
devices = devices_data["devices"]
except FlussDeviceError as e:
LOGGER.error("Error fetching devices: %s", e)
return
device_info_list = []
for device in devices:
device_info = {
"deviceId": device.get("deviceId"),
"deviceName": device.get("deviceName"),
"userType": device.get("userPermissions", {}).get("userType"),
}
device_info_list.append(device_info)
buttons = [
FlussButton(api, device) for device in devices if isinstance(device, dict)
]
async_add_entities(buttons, update_before_add=True)
async_add_entities(buttons)
+24 -7
View File
@@ -20,8 +20,8 @@ _LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): cv.string})
class PlaceholderHub:
"""Placeholder class to store APIs."""
class ApiKeyStorageHub:
"""ApiKeyStorageHub class to store APIs."""
def __init__(self, apikey: str) -> None:
"""Initialize."""
@@ -38,10 +38,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
hub = PlaceholderHub(data[CONF_API_KEY])
hub = ApiKeyStorageHub(data[CONF_API_KEY])
if not await hub.authenticate():
raise InvalidAuth
raise InvalidAuth("Invalid authentication provided.")
return {"title": "Fluss+"}
@@ -56,9 +56,13 @@ class FlussConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
return self.async_create_entry(
title=info.get("title", "Fluss Device"), data=user_input
)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
@@ -66,11 +70,24 @@ class FlussConfigFlow(ConfigFlow, domain=DOMAIN):
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=info["title"], data=user_input)
if errors:
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_API_KEY): str,
}
),
errors=errors,
)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_API_KEY): str,
}
),
)
+13 -6
View File
@@ -1,4 +1,4 @@
"""Base entities for the Motionblinds Bluetooth integration."""
"""Base entities for the Fluss+ integration."""
import logging
@@ -15,26 +15,33 @@ class FlussEntity(Entity):
"""Base class for Fluss entities."""
_attr_has_entity_name = True
_attr_should_poll = False
device: FlussButton
entry: ConfigEntry
def __init__(
def __init__( # noqa: D107
self,
device: FlussButton,
entry: ConfigEntry,
entity_description: EntityDescription,
unique_id_suffix: str | None = None,
) -> None:
self.device = device
self.entry = entry
self.entity_description = entity_description
"""Initialize the entity."""
if unique_id_suffix is None:
self._attr_unique_id = entry.data[CONF_ADDRESS]
else:
self._attr_unique_id = f"{entry.data[CONF_ADDRESS]}_{unique_id_suffix}"
self.device = device
self.entry = entry
self.entity_description = entity_description
if (
CONF_ADDRESS not in entry.data
or entry.data[CONF_ADDRESS] != self._attr_unique_id
):
data = dict(entry.data)
data[CONF_ADDRESS] = self._attr_unique_id
self.hass.config_entries.async_update_entry(entry, data=data)
async def async_update(self) -> None:
"""Update state, called by HA if there is a poll interval and by the service homeassistant.update_entity."""
+1 -5
View File
@@ -3,11 +3,7 @@
"name": "Fluss +",
"codeowners": ["@fluss"],
"config_flow": true,
"dependencies": [],
"documentation": "https://www.home-assistant.io/integrations/fluss",
"homekit": {},
"iot_class": "cloud_polling",
"requirements": [],
"ssdp": [],
"zeroconf": []
"requirements": []
}
+1
View File
@@ -0,0 +1 @@
"""Tests for the fluss integration."""
@@ -0,0 +1,50 @@
from unittest.mock import MagicMock, patch # noqa: D100
import pytest
from homeassistant.components.fluss import config_flow
from homeassistant.components.fluss.config_flow import CannotConnect, InvalidAuth
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
# Mock data for testing
MOCK_CONFIG = {config_flow.CONF_API_KEY: "mock_api_key"}
@pytest.fixture
def mock_hass():
"""Fixture for creating a mock Home Assistant instance."""
return MagicMock()
async def setup_fluss_config_flow(hass: HomeAssistant, config: dict) -> dict: # noqa: D417
"""Set up the configuration flow for Fluss.
Args:
- hass (HomeAssistant): The Home Assistant instance.
- config (dict): The configuration data for setting up Fluss.
Returns:
- dict: Result of the configuration flow.
"""
with patch.object(
config_flow, "validate_input", return_value={"title": "Mock Title"}
):
return await config_flow.FlussConfigFlow().async_step_user(config)
@pytest.mark.asyncio
async def test_form_invalid_auth(mock_hass) -> None:
"""Test handling of invalid authentication."""
with patch.object(config_flow, "validate_input", side_effect=InvalidAuth):
result = await setup_fluss_config_flow(mock_hass, MOCK_CONFIG)
assert result["type"] == FlowResultType.CREATE_ENTRY
@pytest.mark.asyncio
async def test_form_cannot_connect(mock_hass) -> None:
"""Test handling of connection errors."""
with patch.object(config_flow, "validate_input", side_effect=CannotConnect):
result = await setup_fluss_config_flow(mock_hass, MOCK_CONFIG)
assert result["type"] == FlowResultType.CREATE_ENTRY