From 421251308fce398608ab46156384090d27f8bb34 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 16 Jun 2025 16:19:35 +0200 Subject: [PATCH] Add Meater sensor tests (#146952) --- tests/components/meater/__init__.py | 12 + tests/components/meater/conftest.py | 35 +- tests/components/meater/const.py | 3 + .../meater/snapshots/test_init.ambr | 34 ++ .../meater/snapshots/test_sensor.ambr | 411 ++++++++++++++++++ tests/components/meater/test_init.py | 34 ++ tests/components/meater/test_sensor.py | 36 ++ 7 files changed, 563 insertions(+), 2 deletions(-) create mode 100644 tests/components/meater/const.py create mode 100644 tests/components/meater/snapshots/test_init.ambr create mode 100644 tests/components/meater/snapshots/test_sensor.ambr create mode 100644 tests/components/meater/test_init.py create mode 100644 tests/components/meater/test_sensor.py diff --git a/tests/components/meater/__init__.py b/tests/components/meater/__init__.py index ef96dafe88c..48d576ce79b 100644 --- a/tests/components/meater/__init__.py +++ b/tests/components/meater/__init__.py @@ -1 +1,13 @@ """Tests for the Meater 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 component.""" + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/meater/conftest.py b/tests/components/meater/conftest.py index 8a84da859f7..ccaa48437f3 100644 --- a/tests/components/meater/conftest.py +++ b/tests/components/meater/conftest.py @@ -1,13 +1,17 @@ """Meater tests configuration.""" from collections.abc import Generator -from unittest.mock import AsyncMock, patch +from datetime import datetime +from unittest.mock import AsyncMock, Mock, patch +from meater.MeaterApi import MeaterCook, MeaterProbe import pytest from homeassistant.components.meater.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from .const import PROBE_ID + from tests.common import MockConfigEntry @@ -22,7 +26,7 @@ def mock_setup_entry() -> Generator[AsyncMock]: @pytest.fixture -def mock_meater_client() -> Generator[AsyncMock]: +def mock_meater_client(mock_probe: Mock) -> Generator[AsyncMock]: """Mock a Meater client.""" with ( patch( @@ -35,6 +39,7 @@ def mock_meater_client() -> Generator[AsyncMock]: ), ): client = mock_client.return_value + client.get_all_devices.return_value = [mock_probe] yield client @@ -47,3 +52,29 @@ def mock_config_entry() -> MockConfigEntry: data={CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"}, unique_id="user@host.com", ) + + +@pytest.fixture +def mock_cook() -> Mock: + """Mock a cook.""" + mock = Mock(spec=MeaterCook) + mock.id = "123123" + mock.name = "Whole chicken" + mock.state = "Started" + mock.target_temperature = 25.0 + mock.peak_temperature = 27.0 + mock.time_remaining = 32 + mock.time_elapsed = 32 + return mock + + +@pytest.fixture +def mock_probe(mock_cook: Mock) -> Mock: + """Mock a probe.""" + mock = Mock(spec=MeaterProbe) + mock.id = PROBE_ID + mock.internal_temperature = 26.0 + mock.ambient_temperature = 28.0 + mock.cook = mock_cook + mock.time_updated = datetime.fromisoformat("2025-06-16T13:53:51+00:00") + return mock diff --git a/tests/components/meater/const.py b/tests/components/meater/const.py new file mode 100644 index 00000000000..52ba9ac3feb --- /dev/null +++ b/tests/components/meater/const.py @@ -0,0 +1,3 @@ +"""Constants for the Meater tests.""" + +PROBE_ID = "40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58" diff --git a/tests/components/meater/snapshots/test_init.ambr b/tests/components/meater/snapshots/test_init.ambr new file mode 100644 index 00000000000..582fd68efb1 --- /dev/null +++ b/tests/components/meater/snapshots/test_init.ambr @@ -0,0 +1,34 @@ +# serializer version: 1 +# name: test_device_info + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'meater', + '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Apption Labs', + 'model': 'Meater Probe', + 'model_id': None, + 'name': 'Meater Probe 40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- diff --git a/tests/components/meater/snapshots/test_sensor.ambr b/tests/components/meater/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..954dbf8b138 --- /dev/null +++ b/tests/components/meater/snapshots/test_sensor.ambr @@ -0,0 +1,411 @@ +# serializer version: 1 +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_ambient-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_ambient', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ambient', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-ambient', + 'unit_of_measurement': , + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_ambient-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_ambient', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '28.0', + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_name-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_name', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cook_name', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-cook_name', + 'unit_of_measurement': None, + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_name-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_name', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Whole chicken', + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_peak_temp-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_peak_temp', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cook_peak_temp', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-cook_peak_temp', + 'unit_of_measurement': , + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_peak_temp-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_peak_temp', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '27.0', + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_state', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cook_state', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-cook_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Started', + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_target_temp-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_target_temp', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cook_target_temp', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-cook_target_temp', + 'unit_of_measurement': , + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_target_temp-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_target_temp', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '25.0', + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_elapsed-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_elapsed', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cook_time_elapsed', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-cook_time_elapsed', + 'unit_of_measurement': None, + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_elapsed-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_elapsed', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2023-10-20T23:59:58+00:00', + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_remaining-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_remaining', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cook_time_remaining', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-cook_time_remaining', + 'unit_of_measurement': None, + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_remaining-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_cook_time_remaining', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2023-10-21T00:01:02+00:00', + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_internal-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_internal', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'meater', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'internal', + 'unique_id': '40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58-internal', + 'unit_of_measurement': , + }) +# --- +# name: test_entities[sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_internal-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.meater_40a72384fa80349314dfd97c84b73a5c1c9da57b59e26d67b573d618fe0d6e58_internal', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '26.0', + }) +# --- diff --git a/tests/components/meater/test_init.py b/tests/components/meater/test_init.py new file mode 100644 index 00000000000..52fb73ffdd8 --- /dev/null +++ b/tests/components/meater/test_init.py @@ -0,0 +1,34 @@ +"""Tests for the Meater integration.""" + +from datetime import timedelta +from unittest.mock import AsyncMock + +from freezegun.api import FrozenDateTimeFactory +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.meater.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + +from . import setup_integration +from .const import PROBE_ID + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_device_info( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_meater_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test device registry integration.""" + await setup_integration(hass, mock_config_entry) + freezer.tick(timedelta(seconds=30)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + device_entry = device_registry.async_get_device(identifiers={(DOMAIN, PROBE_ID)}) + assert device_entry is not None + assert device_entry == snapshot diff --git a/tests/components/meater/test_sensor.py b/tests/components/meater/test_sensor.py new file mode 100644 index 00000000000..7a39538b914 --- /dev/null +++ b/tests/components/meater/test_sensor.py @@ -0,0 +1,36 @@ +"""Tests for the Meater sensors.""" + +from datetime import timedelta +from unittest.mock import AsyncMock, patch + +from freezegun.api import FrozenDateTimeFactory +import pytest +from syrupy.assertion 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, async_fire_time_changed, snapshot_platform + + +@pytest.mark.freeze_time("2023-10-21") +async def test_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + mock_meater_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test the sensor entities.""" + with patch("homeassistant.components.meater.PLATFORMS", [Platform.SENSOR]): + await setup_integration(hass, mock_config_entry) + + freezer.tick(timedelta(seconds=30)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)