mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add Epion integration (#107570)
* Adding initial Epion Air integration logic * Skipping sensors with missing data * Patching Epion integration * Adding additional Epion measurement types * Cleaning up logging * Cleaning up code * Fixing error handling for invalid Epion keys * Adding tests and improving error handling * Patching Epion tests * Cleaning up Epion integration code * Bumping Epion package and including missing files * Moving data updates to coordinator and addressing feedback * Improve exception handling * Exposing model name and firmware version * Cleaning up code according to review * Cleaning up code according to review * Adding check to prevent duplicate account setup * Refactoring tests and checking for duplicates * Cleaning up test code according to review * Cleaning up test code * Removing entity name overrides * Fix code format for tests * Adding missing newlines in JSON files * Fixing formatting * Updating device method to always return a device * Updating coordinator
This commit is contained in:
parent
e8b962ea89
commit
5011a25ea6
@ -331,6 +331,9 @@ omit =
|
|||||||
homeassistant/components/environment_canada/weather.py
|
homeassistant/components/environment_canada/weather.py
|
||||||
homeassistant/components/envisalink/*
|
homeassistant/components/envisalink/*
|
||||||
homeassistant/components/ephember/climate.py
|
homeassistant/components/ephember/climate.py
|
||||||
|
homeassistant/components/epion/__init__.py
|
||||||
|
homeassistant/components/epion/coordinator.py
|
||||||
|
homeassistant/components/epion/sensor.py
|
||||||
homeassistant/components/epson/__init__.py
|
homeassistant/components/epson/__init__.py
|
||||||
homeassistant/components/epson/media_player.py
|
homeassistant/components/epson/media_player.py
|
||||||
homeassistant/components/epsonworkforce/sensor.py
|
homeassistant/components/epsonworkforce/sensor.py
|
||||||
|
@ -359,6 +359,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/environment_canada/ @gwww @michaeldavie
|
/homeassistant/components/environment_canada/ @gwww @michaeldavie
|
||||||
/tests/components/environment_canada/ @gwww @michaeldavie
|
/tests/components/environment_canada/ @gwww @michaeldavie
|
||||||
/homeassistant/components/ephember/ @ttroy50
|
/homeassistant/components/ephember/ @ttroy50
|
||||||
|
/homeassistant/components/epion/ @lhgravendeel
|
||||||
|
/tests/components/epion/ @lhgravendeel
|
||||||
/homeassistant/components/epson/ @pszafer
|
/homeassistant/components/epson/ @pszafer
|
||||||
/tests/components/epson/ @pszafer
|
/tests/components/epson/ @pszafer
|
||||||
/homeassistant/components/epsonworkforce/ @ThaStealth
|
/homeassistant/components/epsonworkforce/ @ThaStealth
|
||||||
|
32
homeassistant/components/epion/__init__.py
Normal file
32
homeassistant/components/epion/__init__.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""The Epion integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from epion import Epion
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_API_KEY, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import EpionCoordinator
|
||||||
|
|
||||||
|
PLATFORMS = [Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up the Epion coordinator from a config entry."""
|
||||||
|
api = Epion(entry.data[CONF_API_KEY])
|
||||||
|
coordinator = EpionCoordinator(hass, api)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload Epion config entry."""
|
||||||
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
if unload_ok:
|
||||||
|
del hass.data[DOMAIN][entry.entry_id]
|
||||||
|
return unload_ok
|
54
homeassistant/components/epion/config_flow.py
Normal file
54
homeassistant/components/epion/config_flow.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""Config flow for Epion."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from epion import Epion, EpionAuthenticationError, EpionConnectionError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EpionConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Epion."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle a flow initiated by the user."""
|
||||||
|
errors = {}
|
||||||
|
if user_input:
|
||||||
|
api = Epion(user_input[CONF_API_KEY])
|
||||||
|
try:
|
||||||
|
api_data = await self.hass.async_add_executor_job(api.get_current)
|
||||||
|
except EpionAuthenticationError:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except EpionConnectionError:
|
||||||
|
_LOGGER.error("Unexpected problem when configuring Epion API")
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
else:
|
||||||
|
await self.async_set_unique_id(api_data["accountId"])
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(
|
||||||
|
title="Epion integration",
|
||||||
|
data=user_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_API_KEY): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
)
|
5
homeassistant/components/epion/const.py
Normal file
5
homeassistant/components/epion/const.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"""Constants for the Epion API."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
DOMAIN = "epion"
|
||||||
|
REFRESH_INTERVAL = timedelta(minutes=1)
|
45
homeassistant/components/epion/coordinator.py
Normal file
45
homeassistant/components/epion/coordinator.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""The Epion data coordinator."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from epion import Epion, EpionAuthenticationError, EpionConnectionError
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import REFRESH_INTERVAL
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EpionCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
|
"""Epion data update coordinator."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, epion_api: Epion) -> None:
|
||||||
|
"""Initialize the Epion coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="Epion",
|
||||||
|
update_interval=REFRESH_INTERVAL,
|
||||||
|
)
|
||||||
|
self.epion_api = epion_api
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Fetch data from Epion API and construct a dictionary with device IDs as keys."""
|
||||||
|
try:
|
||||||
|
response = await self.hass.async_add_executor_job(
|
||||||
|
self.epion_api.get_current
|
||||||
|
)
|
||||||
|
except EpionAuthenticationError as err:
|
||||||
|
_LOGGER.error("Authentication error with Epion API")
|
||||||
|
raise ConfigEntryAuthFailed from err
|
||||||
|
except EpionConnectionError as err:
|
||||||
|
_LOGGER.error("Epion API connection problem")
|
||||||
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||||
|
device_data = {}
|
||||||
|
for epion_device in response["devices"]:
|
||||||
|
device_data[epion_device["deviceId"]] = epion_device
|
||||||
|
return device_data
|
11
homeassistant/components/epion/manifest.json
Normal file
11
homeassistant/components/epion/manifest.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"domain": "epion",
|
||||||
|
"name": "Epion",
|
||||||
|
"codeowners": ["@lhgravendeel"],
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/epion",
|
||||||
|
"integration_type": "hub",
|
||||||
|
"iot_class": "cloud_polling",
|
||||||
|
"loggers": ["epion"],
|
||||||
|
"requirements": ["epion==0.0.3"]
|
||||||
|
}
|
113
homeassistant/components/epion/sensor.py
Normal file
113
homeassistant/components/epion/sensor.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
"""Support for Epion API."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
PERCENTAGE,
|
||||||
|
UnitOfPressure,
|
||||||
|
UnitOfTemperature,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import EpionCoordinator
|
||||||
|
|
||||||
|
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
|
SensorEntityDescription(
|
||||||
|
device_class=SensorDeviceClass.CO2,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
key="co2",
|
||||||
|
suggested_display_precision=0,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
key="temperature",
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
key="humidity",
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
|
key="pressure",
|
||||||
|
suggested_display_precision=0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Add an Epion entry."""
|
||||||
|
coordinator: EpionCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
entities = [
|
||||||
|
EpionSensor(coordinator, epion_device_id, description)
|
||||||
|
for epion_device_id in coordinator.data
|
||||||
|
for description in SENSOR_TYPES
|
||||||
|
]
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class EpionSensor(CoordinatorEntity[EpionCoordinator], SensorEntity):
|
||||||
|
"""Representation of an Epion Air sensor."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: EpionCoordinator,
|
||||||
|
epion_device_id: str,
|
||||||
|
description: SensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize an EpionSensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._epion_device_id = epion_device_id
|
||||||
|
self.entity_description = description
|
||||||
|
self.unique_id = f"{epion_device_id}_{description.key}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._epion_device_id)},
|
||||||
|
manufacturer="Epion",
|
||||||
|
name=self.device.get("deviceName"),
|
||||||
|
sw_version=self.device.get("fwVersion"),
|
||||||
|
model="Epion Air",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> float | None:
|
||||||
|
"""Return the value reported by the sensor, or None if the relevant sensor can't produce a current measurement."""
|
||||||
|
return self.device.get(self.entity_description.key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return the availability of the device that provides this sensor data."""
|
||||||
|
return super().available and self._epion_device_id in self.coordinator.data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device(self) -> dict[str, Any]:
|
||||||
|
"""Get the device record from the current coordinator data, or None if there is no data being returned for this device ID anymore."""
|
||||||
|
return self.coordinator.data[self._epion_device_id]
|
18
homeassistant/components/epion/strings.json
Normal file
18
homeassistant/components/epion/strings.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -140,6 +140,7 @@ FLOWS = {
|
|||||||
"enocean",
|
"enocean",
|
||||||
"enphase_envoy",
|
"enphase_envoy",
|
||||||
"environment_canada",
|
"environment_canada",
|
||||||
|
"epion",
|
||||||
"epson",
|
"epson",
|
||||||
"escea",
|
"escea",
|
||||||
"esphome",
|
"esphome",
|
||||||
|
@ -1577,6 +1577,12 @@
|
|||||||
"config_flow": false,
|
"config_flow": false,
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
},
|
},
|
||||||
|
"epion": {
|
||||||
|
"name": "Epion",
|
||||||
|
"integration_type": "hub",
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "cloud_polling"
|
||||||
|
},
|
||||||
"epson": {
|
"epson": {
|
||||||
"name": "Epson",
|
"name": "Epson",
|
||||||
"integrations": {
|
"integrations": {
|
||||||
|
@ -787,6 +787,9 @@ env-canada==0.6.0
|
|||||||
# homeassistant.components.season
|
# homeassistant.components.season
|
||||||
ephem==4.1.5
|
ephem==4.1.5
|
||||||
|
|
||||||
|
# homeassistant.components.epion
|
||||||
|
epion==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.epson
|
# homeassistant.components.epson
|
||||||
epson-projector==0.5.1
|
epson-projector==0.5.1
|
||||||
|
|
||||||
|
@ -638,6 +638,9 @@ env-canada==0.6.0
|
|||||||
# homeassistant.components.season
|
# homeassistant.components.season
|
||||||
ephem==4.1.5
|
ephem==4.1.5
|
||||||
|
|
||||||
|
# homeassistant.components.epion
|
||||||
|
epion==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.epson
|
# homeassistant.components.epson
|
||||||
epson-projector==0.5.1
|
epson-projector==0.5.1
|
||||||
|
|
||||||
|
1
tests/components/epion/__init__.py
Normal file
1
tests/components/epion/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Epion component."""
|
25
tests/components/epion/conftest.py
Normal file
25
tests/components/epion/conftest.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"""Epion tests configuration."""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tests.common import load_json_object_fixture
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_epion():
|
||||||
|
"""Build a fixture for the Epion API that connects successfully and returns one device."""
|
||||||
|
current_one_device_data = load_json_object_fixture(
|
||||||
|
"epion/get_current_one_device.json"
|
||||||
|
)
|
||||||
|
mock_epion_api = MagicMock()
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.epion.config_flow.Epion",
|
||||||
|
return_value=mock_epion_api,
|
||||||
|
) as mock_epion_api, patch(
|
||||||
|
"homeassistant.components.epion.Epion",
|
||||||
|
return_value=mock_epion_api,
|
||||||
|
):
|
||||||
|
mock_epion_api.return_value.get_current.return_value = current_one_device_data
|
||||||
|
yield mock_epion_api
|
15
tests/components/epion/fixtures/get_current_one_device.json
Normal file
15
tests/components/epion/fixtures/get_current_one_device.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"deviceId": "abc",
|
||||||
|
"deviceName": "Test Device",
|
||||||
|
"co2": 500,
|
||||||
|
"temperature": 12.34,
|
||||||
|
"humidity": 34.56,
|
||||||
|
"pressure": 1010.101,
|
||||||
|
"lastMeasurement": 1705329293171,
|
||||||
|
"fwVersion": "1.2.3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accountId": "account-dupe-123"
|
||||||
|
}
|
115
tests/components/epion/test_config_flow.py
Normal file
115
tests/components/epion/test_config_flow.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
"""Tests for the Epion config flow."""
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from epion import EpionAuthenticationError, EpionConnectionError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.epion.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
API_KEY = "test-key-123"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_flow(hass: HomeAssistant, mock_epion: MagicMock) -> None:
|
||||||
|
"""Test we can handle a regular successflow setup flow."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.epion.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_API_KEY: API_KEY},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "Epion integration"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_API_KEY: API_KEY,
|
||||||
|
}
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("exception", "error"),
|
||||||
|
[
|
||||||
|
(EpionAuthenticationError("Invalid auth"), "invalid_auth"),
|
||||||
|
(EpionConnectionError("Timeout error"), "cannot_connect"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_form_exceptions(
|
||||||
|
hass: HomeAssistant, exception: Exception, error: str, mock_epion: MagicMock
|
||||||
|
) -> None:
|
||||||
|
"""Test we can handle Form exceptions."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_epion.return_value.get_current.side_effect = exception
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_API_KEY: API_KEY},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["errors"] == {"base": error}
|
||||||
|
|
||||||
|
mock_epion.return_value.get_current.side_effect = None
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.epion.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_API_KEY: API_KEY},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "Epion integration"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_API_KEY: API_KEY,
|
||||||
|
}
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_duplicate_entry(hass: HomeAssistant, mock_epion: MagicMock) -> None:
|
||||||
|
"""Test duplicate setup handling."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_API_KEY: API_KEY,
|
||||||
|
},
|
||||||
|
unique_id="account-dupe-123",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.epion.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_API_KEY: API_KEY},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert mock_setup_entry.call_count == 0
|
Loading…
x
Reference in New Issue
Block a user