mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add Autarco integration (#121600)
* Init Autarco integration * Add integration code with tests * Update every 5 minutes * Process all feedback from Joost * Bump lib to v2.0.0 * Add more then one site if present * Fix issue with entity translation * Update the test for sensor entities * Fix round two based on feedback from Joost * Add autarco to strict typing * Update tests/components/autarco/test_config_flow.py * Update tests/components/autarco/test_config_flow.py --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
abeac3f3aa
commit
fce68018b7
@ -97,6 +97,7 @@ homeassistant.components.assist_pipeline.*
|
||||
homeassistant.components.asterisk_cdr.*
|
||||
homeassistant.components.asterisk_mbox.*
|
||||
homeassistant.components.asuswrt.*
|
||||
homeassistant.components.autarco.*
|
||||
homeassistant.components.auth.*
|
||||
homeassistant.components.automation.*
|
||||
homeassistant.components.awair.*
|
||||
|
@ -155,6 +155,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/aurora_abb_powerone/ @davet2001
|
||||
/homeassistant/components/aussie_broadband/ @nickw444 @Bre77
|
||||
/tests/components/aussie_broadband/ @nickw444 @Bre77
|
||||
/homeassistant/components/autarco/ @klaasnicolaas
|
||||
/tests/components/autarco/ @klaasnicolaas
|
||||
/homeassistant/components/auth/ @home-assistant/core
|
||||
/tests/components/auth/ @home-assistant/core
|
||||
/homeassistant/components/automation/ @home-assistant/core
|
||||
|
49
homeassistant/components/autarco/__init__.py
Normal file
49
homeassistant/components/autarco/__init__.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""The Autarco integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from autarco import Autarco
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .coordinator import AutarcoDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
type AutarcoConfigEntry = ConfigEntry[list[AutarcoDataUpdateCoordinator]]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AutarcoConfigEntry) -> bool:
|
||||
"""Set up Autarco from a config entry."""
|
||||
client = Autarco(
|
||||
email=entry.data[CONF_EMAIL],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
session=async_get_clientsession(hass),
|
||||
)
|
||||
account_sites = await client.get_account()
|
||||
|
||||
coordinators: list[AutarcoDataUpdateCoordinator] = [
|
||||
AutarcoDataUpdateCoordinator(hass, client, site) for site in account_sites
|
||||
]
|
||||
|
||||
await asyncio.gather(
|
||||
*[
|
||||
coordinator.async_config_entry_first_refresh()
|
||||
for coordinator in coordinators
|
||||
]
|
||||
)
|
||||
|
||||
entry.runtime_data = coordinators
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: AutarcoConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
57
homeassistant/components/autarco/config_flow.py
Normal file
57
homeassistant/components/autarco/config_flow.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""Config flow for Autarco integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from autarco import Autarco, AutarcoAuthenticationError, AutarcoConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_EMAIL): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AutarcoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Autarco."""
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
self._async_abort_entries_match({CONF_EMAIL: user_input[CONF_EMAIL]})
|
||||
client = Autarco(
|
||||
email=user_input[CONF_EMAIL],
|
||||
password=user_input[CONF_PASSWORD],
|
||||
session=async_get_clientsession(self.hass),
|
||||
)
|
||||
try:
|
||||
await client.get_account()
|
||||
except AutarcoAuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except AutarcoConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_EMAIL],
|
||||
data={
|
||||
CONF_EMAIL: user_input[CONF_EMAIL],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
},
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
errors=errors,
|
||||
data_schema=DATA_SCHEMA,
|
||||
)
|
13
homeassistant/components/autarco/const.py
Normal file
13
homeassistant/components/autarco/const.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""Constants for the Autarco integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Final
|
||||
|
||||
DOMAIN: Final = "autarco"
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
SCAN_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
SENSORS_SOLAR: Final = "solar"
|
47
homeassistant/components/autarco/coordinator.py
Normal file
47
homeassistant/components/autarco/coordinator.py
Normal file
@ -0,0 +1,47 @@
|
||||
"""Coordinator for Autarco integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
from autarco import AccountSite, Autarco, Solar
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
||||
|
||||
|
||||
class AutarcoData(NamedTuple):
|
||||
"""Class for defining data in dict."""
|
||||
|
||||
solar: Solar
|
||||
|
||||
|
||||
class AutarcoDataUpdateCoordinator(DataUpdateCoordinator[AutarcoData]):
|
||||
"""Class to manage fetching Autarco data from the API."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
client: Autarco,
|
||||
site: AccountSite,
|
||||
) -> None:
|
||||
"""Initialize global Autarco data updater."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
self.client = client
|
||||
self.site = site
|
||||
|
||||
async def _async_update_data(self) -> AutarcoData:
|
||||
"""Fetch data from Autarco API."""
|
||||
return AutarcoData(
|
||||
solar=await self.client.get_solar(self.site.public_key),
|
||||
)
|
9
homeassistant/components/autarco/manifest.json
Normal file
9
homeassistant/components/autarco/manifest.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"domain": "autarco",
|
||||
"name": "Autarco",
|
||||
"codeowners": ["@klaasnicolaas"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/autarco",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["autarco==2.0.0"]
|
||||
}
|
113
homeassistant/components/autarco/sensor.py
Normal file
113
homeassistant/components/autarco/sensor.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""Support for Autarco sensors."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from autarco import Solar
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import UnitOfEnergy, UnitOfPower
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AutarcoConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AutarcoDataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AutarcoSolarSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes an Autarco sensor entity."""
|
||||
|
||||
state: Callable[[Solar], StateType]
|
||||
|
||||
|
||||
SENSORS_SOLAR: tuple[AutarcoSolarSensorEntityDescription, ...] = (
|
||||
AutarcoSolarSensorEntityDescription(
|
||||
key="power_production",
|
||||
translation_key="power_production",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
state=lambda solar: solar.power_production,
|
||||
),
|
||||
AutarcoSolarSensorEntityDescription(
|
||||
key="energy_production_today",
|
||||
translation_key="energy_production_today",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state=lambda solar: solar.energy_production_today,
|
||||
),
|
||||
AutarcoSolarSensorEntityDescription(
|
||||
key="energy_production_month",
|
||||
translation_key="energy_production_month",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state=lambda solar: solar.energy_production_month,
|
||||
),
|
||||
AutarcoSolarSensorEntityDescription(
|
||||
key="energy_production_total",
|
||||
translation_key="energy_production_total",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state=lambda solar: solar.energy_production_total,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AutarcoConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Autarco sensors based on a config entry."""
|
||||
for coordinator in entry.runtime_data:
|
||||
async_add_entities(
|
||||
AutarcoSolarSensorEntity(
|
||||
coordinator=coordinator,
|
||||
description=description,
|
||||
)
|
||||
for description in SENSORS_SOLAR
|
||||
)
|
||||
|
||||
|
||||
class AutarcoSolarSensorEntity(
|
||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
"""Defines an Autarco solar sensor."""
|
||||
|
||||
entity_description: AutarcoSolarSensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
coordinator: AutarcoDataUpdateCoordinator,
|
||||
description: AutarcoSolarSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Autarco sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.site.site_id}_solar_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{coordinator.site.site_id}_solar")},
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
manufacturer="Autarco",
|
||||
name="Solar",
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.state(self.coordinator.data.solar)
|
40
homeassistant/components/autarco/strings.json
Normal file
40
homeassistant/components/autarco/strings.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Connect to your Autarco account to get information about your solar panels.",
|
||||
"data": {
|
||||
"email": "[%key:common::config_flow::data::email%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
"data_description": {
|
||||
"email": "The email address of your Autarco account.",
|
||||
"password": "The password of your Autarco account."
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"power_production": {
|
||||
"name": "Power production"
|
||||
},
|
||||
"energy_production_today": {
|
||||
"name": "Energy production today"
|
||||
},
|
||||
"energy_production_month": {
|
||||
"name": "Energy production month"
|
||||
},
|
||||
"energy_production_total": {
|
||||
"name": "ENergy production total"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -69,6 +69,7 @@ FLOWS = {
|
||||
"aurora",
|
||||
"aurora_abb_powerone",
|
||||
"aussie_broadband",
|
||||
"autarco",
|
||||
"awair",
|
||||
"axis",
|
||||
"azure_data_explorer",
|
||||
|
@ -581,6 +581,12 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"autarco": {
|
||||
"name": "Autarco",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"avion": {
|
||||
"name": "Avi-on",
|
||||
"integration_type": "hub",
|
||||
|
10
mypy.ini
10
mypy.ini
@ -732,6 +732,16 @@ disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.autarco.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.auth.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -516,6 +516,9 @@ auroranoaa==0.0.3
|
||||
# homeassistant.components.aurora_abb_powerone
|
||||
aurorapy==0.2.7
|
||||
|
||||
# homeassistant.components.autarco
|
||||
autarco==2.0.0
|
||||
|
||||
# homeassistant.components.avea
|
||||
# avea==1.5.1
|
||||
|
||||
|
@ -462,6 +462,9 @@ auroranoaa==0.0.3
|
||||
# homeassistant.components.aurora_abb_powerone
|
||||
aurorapy==0.2.7
|
||||
|
||||
# homeassistant.components.autarco
|
||||
autarco==2.0.0
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==62
|
||||
|
||||
|
12
tests/components/autarco/__init__.py
Normal file
12
tests/components/autarco/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""Tests for the Autarco integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||
"""Fixture for setting up the integration."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
66
tests/components/autarco/conftest.py
Normal file
66
tests/components/autarco/conftest.py
Normal file
@ -0,0 +1,66 @@
|
||||
"""Common fixtures for the Autarco tests."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from autarco import AccountSite, Solar
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.autarco.const import DOMAIN
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.autarco.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_autarco_client() -> Generator[AsyncMock]:
|
||||
"""Mock a Autarco client."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.autarco.Autarco",
|
||||
autospec=True,
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.autarco.config_flow.Autarco",
|
||||
new=mock_client,
|
||||
),
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.get_account.return_value = [
|
||||
AccountSite(
|
||||
site_id=1,
|
||||
public_key="key-public",
|
||||
system_name="test-system",
|
||||
retailer="test-retailer",
|
||||
health="OK",
|
||||
)
|
||||
]
|
||||
client.get_solar.return_value = Solar(
|
||||
power_production=200,
|
||||
energy_production_today=4,
|
||||
energy_production_month=58,
|
||||
energy_production_total=10379,
|
||||
)
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Mock a config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Autarco",
|
||||
data={
|
||||
CONF_EMAIL: "test@autarco.com",
|
||||
CONF_PASSWORD: "test-password",
|
||||
},
|
||||
)
|
6
tests/components/autarco/fixtures/solar.json
Normal file
6
tests/components/autarco/fixtures/solar.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"pv_now": 200,
|
||||
"pv_today": 4,
|
||||
"pv_month": 58,
|
||||
"pv_to_date": 10379
|
||||
}
|
196
tests/components/autarco/snapshots/test_sensor.ambr
Normal file
196
tests/components/autarco/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,196 @@
|
||||
# serializer version: 1
|
||||
# name: test_solar_sensors[sensor.solar_energy_production_month-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.solar_energy_production_month',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy production month',
|
||||
'platform': 'autarco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_production_month',
|
||||
'unique_id': '1_solar_energy_production_month',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_solar_sensors[sensor.solar_energy_production_month-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Solar Energy production month',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.solar_energy_production_month',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '58',
|
||||
})
|
||||
# ---
|
||||
# name: test_solar_sensors[sensor.solar_energy_production_today-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.solar_energy_production_today',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy production today',
|
||||
'platform': 'autarco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_production_today',
|
||||
'unique_id': '1_solar_energy_production_today',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_solar_sensors[sensor.solar_energy_production_today-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Solar Energy production today',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.solar_energy_production_today',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '4',
|
||||
})
|
||||
# ---
|
||||
# name: test_solar_sensors[sensor.solar_energy_production_total-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.solar_energy_production_total',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'ENergy production total',
|
||||
'platform': 'autarco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_production_total',
|
||||
'unique_id': '1_solar_energy_production_total',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_solar_sensors[sensor.solar_energy_production_total-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Solar ENergy production total',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.solar_energy_production_total',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '10379',
|
||||
})
|
||||
# ---
|
||||
# name: test_solar_sensors[sensor.solar_power_production-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.solar_power_production',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Power production',
|
||||
'platform': 'autarco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'power_production',
|
||||
'unique_id': '1_solar_power_production',
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_solar_sensors[sensor.solar_power_production-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'Solar Power production',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.solar_power_production',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '200',
|
||||
})
|
||||
# ---
|
101
tests/components/autarco/test_config_flow.py
Normal file
101
tests/components/autarco/test_config_flow.py
Normal file
@ -0,0 +1,101 @@
|
||||
"""Test the Autarco config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from autarco import AutarcoAuthenticationError, AutarcoConnectionError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.autarco.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_full_user_flow(
|
||||
hass: HomeAssistant,
|
||||
mock_autarco_client: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test the full user configuration flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "user"
|
||||
assert not result.get("errors")
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_EMAIL: "test@autarco.com", CONF_PASSWORD: "test-password"},
|
||||
)
|
||||
|
||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
||||
assert result.get("title") == "test@autarco.com"
|
||||
assert result.get("data") == {
|
||||
CONF_EMAIL: "test@autarco.com",
|
||||
CONF_PASSWORD: "test-password",
|
||||
}
|
||||
assert len(mock_autarco_client.get_account.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_duplicate_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_autarco_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test abort when setting up duplicate entry."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert not result.get("errors")
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_EMAIL: "test@autarco.com", CONF_PASSWORD: "test-password"},
|
||||
)
|
||||
|
||||
assert result.get("type") is FlowResultType.ABORT
|
||||
assert result.get("reason") == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error"),
|
||||
[
|
||||
(AutarcoConnectionError, "cannot_connect"),
|
||||
(AutarcoAuthenticationError, "invalid_auth"),
|
||||
],
|
||||
)
|
||||
async def test_exceptions(
|
||||
hass: HomeAssistant,
|
||||
mock_autarco_client: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
exception: Exception,
|
||||
error: str,
|
||||
) -> None:
|
||||
"""Test exceptions."""
|
||||
mock_autarco_client.get_account.side_effect = exception
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_EMAIL: "test@autarco.com", CONF_PASSWORD: "test-password"},
|
||||
)
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("errors") == {"base": error}
|
||||
|
||||
mock_autarco_client.get_account.side_effect = None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_EMAIL: "test@autarco.com", CONF_PASSWORD: "test-password"},
|
||||
)
|
||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
28
tests/components/autarco/test_init.py
Normal file
28
tests/components/autarco/test_init.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""Test the Autarco init module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
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_autarco_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test load and unload entry."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_remove(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
27
tests/components/autarco/test_sensor.py
Normal file
27
tests/components/autarco/test_sensor.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""Test the sensor provided by the Autarco integration."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
async def test_solar_sensors(
|
||||
hass: HomeAssistant,
|
||||
mock_autarco_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the Autarco - Solar sensor."""
|
||||
with patch("homeassistant.components.autarco.PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
Loading…
x
Reference in New Issue
Block a user