diff --git a/CODEOWNERS b/CODEOWNERS index b382d63cf44..c059e84f677 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -384,6 +384,7 @@ build.json @home-assistant/supervisor /tests/components/elvia/ @ludeeus /homeassistant/components/emby/ @mezz64 /homeassistant/components/emoncms/ @borpin @alexandrecuer +/tests/components/emoncms/ @borpin @alexandrecuer /homeassistant/components/emonitor/ @bdraco /tests/components/emonitor/ @bdraco /homeassistant/components/emulated_hue/ @bdraco @Tho85 diff --git a/homeassistant/components/emoncms/const.py b/homeassistant/components/emoncms/const.py index dc43e7a07dc..96269218316 100644 --- a/homeassistant/components/emoncms/const.py +++ b/homeassistant/components/emoncms/const.py @@ -6,6 +6,7 @@ CONF_EXCLUDE_FEEDID = "exclude_feed_id" CONF_ONLY_INCLUDE_FEEDID = "include_only_feed_id" CONF_MESSAGE = "message" CONF_SUCCESS = "success" +DOMAIN = "emoncms" LOGGER = logging.getLogger(__package__) diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8b7344dd3eb..35ae05a00d0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1453,6 +1453,9 @@ pyefergy==22.5.0 # homeassistant.components.energenie_power_sockets pyegps==0.2.5 +# homeassistant.components.emoncms +pyemoncms==0.0.7 + # homeassistant.components.enphase_envoy pyenphase==1.20.6 diff --git a/tests/components/emoncms/__init__.py b/tests/components/emoncms/__init__.py new file mode 100644 index 00000000000..ecf3c54e9ed --- /dev/null +++ b/tests/components/emoncms/__init__.py @@ -0,0 +1 @@ +"""Tests for the emoncms component.""" diff --git a/tests/components/emoncms/conftest.py b/tests/components/emoncms/conftest.py new file mode 100644 index 00000000000..500fff228e9 --- /dev/null +++ b/tests/components/emoncms/conftest.py @@ -0,0 +1,47 @@ +"""Fixtures for emoncms integration tests.""" + +from collections.abc import AsyncGenerator +from unittest.mock import AsyncMock, patch + +import pytest + +UNITS = ["kWh", "Wh", "W", "V", "A", "VA", "°C", "°F", "K", "Hz", "hPa", ""] + + +def get_feed( + number: int, unit: str = "W", value: int = 18.04, timestamp: int = 1665509570 +): + """Generate feed details.""" + return { + "id": str(number), + "userid": "1", + "name": f"parameter {number}", + "tag": "tag", + "size": "35809224", + "unit": unit, + "time": timestamp, + "value": value, + } + + +FEEDS = [get_feed(i + 1, unit=unit) for i, unit in enumerate(UNITS)] + + +EMONCMS_FAILURE = {"success": False, "message": "failure"} + + +@pytest.fixture +async def emoncms_client() -> AsyncGenerator[AsyncMock]: + """Mock pyemoncms success response.""" + with ( + patch( + "homeassistant.components.emoncms.sensor.EmoncmsClient", autospec=True + ) as mock_client, + patch( + "homeassistant.components.emoncms.coordinator.EmoncmsClient", + new=mock_client, + ), + ): + client = mock_client.return_value + client.async_request.return_value = {"success": True, "message": FEEDS} + yield client diff --git a/tests/components/emoncms/snapshots/test_sensor.ambr b/tests/components/emoncms/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..62c85aaba01 --- /dev/null +++ b/tests/components/emoncms/snapshots/test_sensor.ambr @@ -0,0 +1,24 @@ +# serializer version: 1 +# name: test_coordinator_update[sensor.emoncms_parameter_1] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'FeedId': '1', + 'FeedName': 'parameter 1', + 'LastUpdated': 1665509570, + 'LastUpdatedStr': '2022-10-11T10:32:50-07:00', + 'Size': '35809224', + 'Tag': 'tag', + 'UserId': '1', + 'device_class': 'temperature', + 'friendly_name': 'EmonCMS parameter 1', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.emoncms_parameter_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '18.04', + }) +# --- diff --git a/tests/components/emoncms/test_sensor.py b/tests/components/emoncms/test_sensor.py new file mode 100644 index 00000000000..a039239077e --- /dev/null +++ b/tests/components/emoncms/test_sensor.py @@ -0,0 +1,90 @@ +"""Test emoncms sensor.""" + +from typing import Any +from unittest.mock import AsyncMock + +from freezegun.api import FrozenDateTimeFactory +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.emoncms.const import CONF_ONLY_INCLUDE_FEEDID, DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import CONF_API_KEY, CONF_ID, CONF_PLATFORM, CONF_URL +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from homeassistant.setup import async_setup_component + +from .conftest import EMONCMS_FAILURE, FEEDS, get_feed + +from tests.common import async_fire_time_changed + +YAML = { + CONF_PLATFORM: "emoncms", + CONF_API_KEY: "my_api_key", + CONF_ID: 1, + CONF_URL: "http://1.1.1.1", + CONF_ONLY_INCLUDE_FEEDID: [1, 2], + "scan_interval": 30, +} + + +@pytest.fixture +def emoncms_yaml_config() -> ConfigType: + """Mock emoncms configuration from yaml.""" + return {"sensor": YAML} + + +def get_entity_ids(feeds: list[dict[str, Any]]) -> list[str]: + """Get emoncms entity ids.""" + return [ + f"{SENSOR_DOMAIN}.{DOMAIN}_{feed["name"].replace(' ', '_')}" for feed in feeds + ] + + +def get_feeds(nbs: list[int]) -> list[dict[str, Any]]: + """Get feeds.""" + return [feed for feed in FEEDS if feed["id"] in str(nbs)] + + +async def test_coordinator_update( + hass: HomeAssistant, + emoncms_yaml_config: ConfigType, + snapshot: SnapshotAssertion, + emoncms_client: AsyncMock, + caplog: pytest.LogCaptureFixture, + freezer: FrozenDateTimeFactory, +) -> None: + """Test coordinator update.""" + emoncms_client.async_request.return_value = { + "success": True, + "message": [get_feed(1, unit="°C")], + } + await async_setup_component(hass, SENSOR_DOMAIN, emoncms_yaml_config) + await hass.async_block_till_done() + feeds = get_feeds([1]) + for entity_id in get_entity_ids(feeds): + state = hass.states.get(entity_id) + assert state == snapshot(name=entity_id) + + async def skip_time() -> None: + freezer.tick(60) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + emoncms_client.async_request.return_value = { + "success": True, + "message": [get_feed(1, unit="°C", value=24.04, timestamp=1665509670)], + } + + await skip_time() + + for entity_id in get_entity_ids(feeds): + state = hass.states.get(entity_id) + assert state.attributes["LastUpdated"] == 1665509670 + assert state.state == "24.04" + + emoncms_client.async_request.return_value = EMONCMS_FAILURE + + await skip_time() + + assert f"Error fetching {DOMAIN}_coordinator data" in caplog.text