From 22bb68d6107f1b9b251f5edb76fc6dc0ffec8195 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 12 Jul 2024 17:15:18 +0000 Subject: [PATCH] Add tests --- tests/components/compensation/conftest.py | 81 ++++++ .../compensation/test_config_flow.py | 266 ++++++++++++++++++ tests/components/compensation/test_init.py | 44 +++ tests/components/compensation/test_sensor.py | 33 +++ 4 files changed, 424 insertions(+) create mode 100644 tests/components/compensation/conftest.py create mode 100644 tests/components/compensation/test_config_flow.py create mode 100644 tests/components/compensation/test_init.py diff --git a/tests/components/compensation/conftest.py b/tests/components/compensation/conftest.py new file mode 100644 index 00000000000..20bceadb35d --- /dev/null +++ b/tests/components/compensation/conftest.py @@ -0,0 +1,81 @@ +"""Fixtures for the Compensation integration.""" + +from __future__ import annotations + +from collections.abc import Generator +from typing import Any +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.compensation.const import ( + CONF_DATAPOINTS, + CONF_DEGREE, + CONF_LOWER_LIMIT, + CONF_PRECISION, + CONF_UPPER_LIMIT, + DEFAULT_DEGREE, + DEFAULT_NAME, + DOMAIN, +) +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, CONF_UNIT_OF_MEASUREMENT +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock]: + """Automatically patch compensation setup_entry.""" + with patch( + "homeassistant.components.compensation.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture(name="get_config") +async def get_config_to_integration_load() -> dict[str, Any]: + """Return configuration. + + To override the config, tests can be marked with: + @pytest.mark.parametrize("get_config", [{...}]) + """ + return { + CONF_NAME: DEFAULT_NAME, + CONF_ENTITY_ID: "sensor.uncompensated", + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: DEFAULT_DEGREE, + CONF_UNIT_OF_MEASUREMENT: "mm", + } + + +@pytest.fixture(name="loaded_entry") +async def load_integration( + hass: HomeAssistant, get_config: dict[str, Any] +) -> MockConfigEntry: + """Set up the Compensation integration in Home Assistant.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="Compensation sensor", + source=SOURCE_USER, + options=get_config, + entry_id="1", + ) + config_entry.add_to_hass(hass) + + entity_id = get_config[CONF_ENTITY_ID] + hass.states.async_set(entity_id, 4, {}) + await hass.async_block_till_done() + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry diff --git a/tests/components/compensation/test_config_flow.py b/tests/components/compensation/test_config_flow.py new file mode 100644 index 00000000000..2639a6c0d03 --- /dev/null +++ b/tests/components/compensation/test_config_flow.py @@ -0,0 +1,266 @@ +"""Test the Compensation config flow.""" + +from __future__ import annotations + +from unittest.mock import AsyncMock + +from homeassistant import config_entries +from homeassistant.components.compensation.const import ( + CONF_DATAPOINTS, + CONF_DEGREE, + CONF_LOWER_LIMIT, + CONF_PRECISION, + CONF_UPPER_LIMIT, + DEFAULT_NAME, + DOMAIN, +) +from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, CONF_UNIT_OF_MEASUREMENT +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + + +async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: + """Test we get the form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] is FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: DEFAULT_NAME, + CONF_ENTITY_ID: "sensor.test_monitored", + }, + ) + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 1, + CONF_UNIT_OF_MEASUREMENT: "mm", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["version"] == 1 + assert result["options"] == { + CONF_NAME: DEFAULT_NAME, + CONF_ENTITY_ID: "sensor.test_monitored", + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 1, + CONF_UNIT_OF_MEASUREMENT: "mm", + } + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_options_flow(hass: HomeAssistant, loaded_entry: MockConfigEntry) -> None: + """Test options flow.""" + + result = await hass.config_entries.options.async_init(loaded_entry.entry_id) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 1, + CONF_UNIT_OF_MEASUREMENT: "km", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["data"] == { + CONF_NAME: DEFAULT_NAME, + CONF_ENTITY_ID: "sensor.uncompensated", + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 1, + CONF_UNIT_OF_MEASUREMENT: "km", + } + + await hass.async_block_till_done() + + # Check the entity was updated, no new entity was created + assert len(hass.states.async_all()) == 2 + + state = hass.states.get("sensor.compensation_sensor") + assert state is not None + + +async def test_validation_options( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test validation.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] is FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: DEFAULT_NAME, + CONF_ENTITY_ID: "sensor.test_monitored", + }, + ) + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 2, + CONF_UNIT_OF_MEASUREMENT: "km", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "not_enough_datapoints"} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 1, + CONF_UNIT_OF_MEASUREMENT: "km", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "incorrect_datapoints"} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2,0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 1, + CONF_UNIT_OF_MEASUREMENT: "km", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "incorrect_datapoints"} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DATAPOINTS: ["1.0, 2.0", "2.0, 3.0", "3.0, 4.0"], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 2, + CONF_UNIT_OF_MEASUREMENT: "km", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["version"] == 1 + assert result["options"] == { + CONF_NAME: DEFAULT_NAME, + CONF_ENTITY_ID: "sensor.test_monitored", + CONF_DATAPOINTS: ["1.0, 2.0", "2.0, 3.0", "3.0, 4.0"], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 2, + CONF_UNIT_OF_MEASUREMENT: "km", + } + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_entry_already_exist( + hass: HomeAssistant, loaded_entry: MockConfigEntry +) -> None: + """Test abort when entry already exist.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] is FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: DEFAULT_NAME, + CONF_ENTITY_ID: "sensor.uncompensated", + }, + ) + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DATAPOINTS: [ + "1.0, 2.0", + "2.0, 3.0", + ], + CONF_UPPER_LIMIT: False, + CONF_LOWER_LIMIT: False, + CONF_PRECISION: 2, + CONF_DEGREE: 1, + CONF_UNIT_OF_MEASUREMENT: "mm", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" diff --git a/tests/components/compensation/test_init.py b/tests/components/compensation/test_init.py new file mode 100644 index 00000000000..369f72f6aee --- /dev/null +++ b/tests/components/compensation/test_init.py @@ -0,0 +1,44 @@ +"""Test Statistics component setup process.""" + +from __future__ import annotations + +from typing import Any +from unittest.mock import patch + +from homeassistant.components.compensation.const import DOMAIN +from homeassistant.config_entries import SOURCE_USER, ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_unload_entry(hass: HomeAssistant, loaded_entry: MockConfigEntry) -> None: + """Test unload an entry.""" + + assert loaded_entry.state is ConfigEntryState.LOADED + assert await hass.config_entries.async_unload(loaded_entry.entry_id) + await hass.async_block_till_done() + assert loaded_entry.state is ConfigEntryState.NOT_LOADED + + +async def test_could_not_setup(hass: HomeAssistant, get_config: dict[str, Any]) -> None: + """Test exception.""" + + config_entry = MockConfigEntry( + domain=DOMAIN, + title="Compensation sensor", + source=SOURCE_USER, + options=get_config, + entry_id="1", + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.compensation.np.polyfit", + side_effect=FloatingPointError, + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.SETUP_ERROR + assert config_entry.error_reason_translation_key == "setup_error" diff --git a/tests/components/compensation/test_sensor.py b/tests/components/compensation/test_sensor.py index 877a4f972a9..88bd1ccb47d 100644 --- a/tests/components/compensation/test_sensor.py +++ b/tests/components/compensation/test_sensor.py @@ -1,5 +1,7 @@ """The tests for the integration sensor platform.""" +from typing import Any + import pytest from homeassistant.components.compensation.const import CONF_PRECISION, DOMAIN @@ -7,6 +9,7 @@ from homeassistant.components.compensation.sensor import ATTR_COEFFICIENTS from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, + CONF_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_STATE_CHANGED, STATE_UNKNOWN, @@ -14,6 +17,8 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry + async def test_linear_state(hass: HomeAssistant) -> None: """Test compensation sensor state.""" @@ -60,6 +65,34 @@ async def test_linear_state(hass: HomeAssistant) -> None: assert state.state == STATE_UNKNOWN +async def test_linear_state_from_config_entry( + hass: HomeAssistant, loaded_entry: MockConfigEntry, get_config: dict[str, Any] +) -> None: + """Test compensation sensor state loaded from config entry.""" + expected_entity_id = "sensor.compensation_sensor" + entity_id = get_config[CONF_ENTITY_ID] + + hass.states.async_set(entity_id, 5, {}) + await hass.async_block_till_done() + + state = hass.states.get(expected_entity_id) + assert state is not None + assert round(float(state.state), get_config[CONF_PRECISION]) == 6.0 + + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "mm" + + coefs = [round(v, 1) for v in state.attributes.get(ATTR_COEFFICIENTS)] + assert coefs == [1.0, 1.0] + + hass.states.async_set(entity_id, "foo", {}) + await hass.async_block_till_done() + + state = hass.states.get(expected_entity_id) + assert state is not None + + assert state.state == STATE_UNKNOWN + + async def test_linear_state_from_attribute(hass: HomeAssistant) -> None: """Test compensation sensor state that pulls from attribute.""" config = {