Add ecoforest integration (#100647)

* Add ecoforest integration

* fix file title

* remove host default from schema, hints will be given in the documentation

* moved input validation to async_step_user

* ensure we can receive device data while doing entry setup

* remove unecessary check before unique id is set

* added shorter syntax for async create entry

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use variable to set unique id

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Use _attr_has_entity_name from base entity

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* remove unecessary comments in coordinator

* use shorthand for device information

* remove empty objects from manifest

* remove unecessary flag

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use _async_abort_entries_match to ensure device is not duplicated

* remove unecessary host attr

* fixed coordinator host attr to be used by entities to identify device

* remove unecessary assert

* use default device class temperature trasnlation key

* reuse base entity description

* use device serial number as identifier

* remove unused code

* Improve logging message

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Remove unused errors

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Raise a generic update failed

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use coordinator directly

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* No need to check for serial number

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* rename variable

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use renamed variable

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* improve assertion

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use serial number in entity unique id

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* raise config entry not ready on setup when error in connection

* improve test readability

* Improve python syntax

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* abort when device already configured with same serial number

* improve tests

* fix test name

* use coordinator data

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* improve asserts

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* fix ci

* improve error handling

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Pedro Januário
2023-09-21 15:18:55 +02:00
committed by GitHub
co-authored by Joost Lekkerkerker
parent df73850f56
commit c170babba6
17 changed files with 521 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Tests for the Ecoforest integration."""
+73
View File
@@ -0,0 +1,73 @@
"""Common fixtures for the Ecoforest tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch
from pyecoforest.models.device import Alarm, Device, OperationMode, State
import pytest
from homeassistant.components.ecoforest import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.ecoforest.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture(name="config")
def config_fixture():
"""Define a config entry data fixture."""
return {
CONF_HOST: "1.1.1.1",
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
}
@pytest.fixture(name="serial_number")
def serial_number_fixture():
"""Define a serial number fixture."""
return "1234"
@pytest.fixture(name="mock_device")
def mock_device_fixture(serial_number):
"""Define a mocked Ecoforest device fixture."""
mock = Mock(spec=Device)
mock.model = "model-version"
mock.model_name = "model-name"
mock.firmware = "firmware-version"
mock.serial_number = serial_number
mock.operation_mode = OperationMode.POWER
mock.on = False
mock.state = State.OFF
mock.power = 3
mock.temperature = 21.5
mock.alarm = Alarm.PELLETS
mock.alarm_code = "A099"
mock.environment_temperature = 23.5
mock.cpu_temperature = 36.1
mock.gas_temperature = 40.2
mock.ntc_temperature = 24.2
return mock
@pytest.fixture(name="config_entry")
def config_entry_fixture(hass: HomeAssistant, config, serial_number):
"""Define a config entry fixture."""
entry = MockConfigEntry(
domain=DOMAIN,
entry_id="45a36e55aaddb2007c5f6602e0c38e72",
title=f"Ecoforest {serial_number}",
unique_id=serial_number,
data=config,
)
entry.add_to_hass(hass)
return entry
@@ -0,0 +1,115 @@
"""Test the Ecoforest config flow."""
from unittest.mock import AsyncMock, patch
from pyecoforest.exceptions import EcoforestAuthenticationRequired
import pytest
from homeassistant import config_entries
from homeassistant.components.ecoforest.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_device, config
) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch(
"pyecoforest.api.EcoforestApi.get",
return_value=mock_device,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
config,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert "result" in result
assert result["result"].unique_id == "1234"
assert result["title"] == "Ecoforest 1234"
assert result["data"] == {
CONF_HOST: "1.1.1.1",
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_device_already_configured(
hass: HomeAssistant, mock_setup_entry: AsyncMock, config_entry, mock_device, config
) -> None:
"""Test device already exists."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch(
"pyecoforest.api.EcoforestApi.get",
return_value=mock_device,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
config,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"
@pytest.mark.parametrize(
("error", "message"),
[
(
EcoforestAuthenticationRequired("401"),
"invalid_auth",
),
(
Exception("Something wrong"),
"cannot_connect",
),
],
)
async def test_flow_fails(
hass: HomeAssistant, error: Exception, message: str, mock_device, config
) -> None:
"""Test we handle failed flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"pyecoforest.api.EcoforestApi.get",
side_effect=error,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
config,
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": message}
with patch(
"pyecoforest.api.EcoforestApi.get",
return_value=mock_device,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
config,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY