diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 4cdcbe5cfdd005..1b933778eb7163 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -15,7 +15,7 @@ SensorEntityDescription, SensorStateClass, ) -from homeassistant.const import PERCENTAGE, UnitOfTemperature +from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType @@ -101,6 +101,13 @@ class LyricSensorAccessoryEntityDescription(SensorEntityDescription): device.changeable_values and device.changeable_values.next_period_time ), ), + LyricSensorEntityDescription( + key="schedule_status", + translation_key="schedule_status", + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: device.schedule_status, + suitable_fn=lambda device: device.schedule_status, + ), LyricSensorEntityDescription( key="setpoint_status", translation_key="setpoint_status", diff --git a/homeassistant/components/lyric/strings.json b/homeassistant/components/lyric/strings.json index b9547c8251475c..ef02d3efa4935d 100644 --- a/homeassistant/components/lyric/strings.json +++ b/homeassistant/components/lyric/strings.json @@ -67,6 +67,9 @@ "room_temperature": { "name": "Room temperature" }, + "schedule_status": { + "name": "Schedule status" + }, "setpoint_status": { "name": "Setpoint status" } diff --git a/tests/components/lyric/__init__.py b/tests/components/lyric/__init__.py index 794c6bf1ba095b..4b4ee70008487a 100644 --- a/tests/components/lyric/__init__.py +++ b/tests/components/lyric/__init__.py @@ -1 +1,13 @@ """Tests for the Honeywell Lyric integration.""" + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None: + """Set up the Lyric integration for tests.""" + 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/lyric/conftest.py b/tests/components/lyric/conftest.py new file mode 100644 index 00000000000000..4f4c3408d2ecfb --- /dev/null +++ b/tests/components/lyric/conftest.py @@ -0,0 +1,71 @@ +"""Fixtures for the Honeywell Lyric integration tests.""" + +from collections.abc import Generator +from time import time +from unittest.mock import MagicMock, patch + +from aiolyric.objects.location import LyricLocation +import pytest + +from homeassistant.components.application_credentials import ( + DOMAIN as APPLICATION_CREDENTIALS_DOMAIN, + ClientCredential, + async_import_client_credential, +) +from homeassistant.components.lyric.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry, load_json_array_fixture + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" + + +@pytest.fixture +async def setup_credentials(hass: HomeAssistant) -> None: + """Register lyric application credentials.""" + assert await async_setup_component(hass, APPLICATION_CREDENTIALS_DOMAIN, {}) + + await async_import_client_credential( + hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET) + ) + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return an already-authenticated Lyric config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data={ + "auth_implementation": DOMAIN, + "token": { + "access_token": "mock-access-token", + "refresh_token": "mock-refresh-token", + "expires_at": time() + 3600, + "token_type": "Bearer", + }, + }, + ) + + +@pytest.fixture +def mock_lyric_api() -> Generator[MagicMock]: + """Mock the aiolyric client, backed by a real Location parsed from a live-shaped fixture. + + get_thermostat_rooms is left as an autospec'd no-op: this test only + covers device-level sensors, not the room/priority data it would + otherwise populate. + """ + with patch("homeassistant.components.lyric.Lyric", autospec=True) as mock_lyric_cls: + lyric = mock_lyric_cls.return_value + + locations_json = load_json_array_fixture("locations.json", DOMAIN) + lyric.locations = [ + LyricLocation(MagicMock(), location) for location in locations_json + ] + lyric.locations_dict = { + location.location_id: location for location in lyric.locations + } + + yield lyric diff --git a/tests/components/lyric/fixtures/locations.json b/tests/components/lyric/fixtures/locations.json new file mode 100644 index 00000000000000..0d3f11736e2abd --- /dev/null +++ b/tests/components/lyric/fixtures/locations.json @@ -0,0 +1,22 @@ +[ + { + "locationID": 35202000168931, + "name": "Ocala P01", + "devices": [ + { + "vacationHold": { "Enabled": true }, + "scheduleStatus": "Resume", + "settings": { "devicePairingEnabled": true }, + "deviceClass": "Thermostat", + "deviceType": "Thermostat", + "deviceID": "LCC-7f86b153-8480-f111-b78f-6045bdb25006", + "name": "Ocala", + "macID": "5CFCE1B67035", + "units": "Fahrenheit", + "indoorTemperature": 79, + "deviceModel": "T9-T10" + } + ], + "users": [] + } +] diff --git a/tests/components/lyric/snapshots/test_sensor.ambr b/tests/components/lyric/snapshots/test_sensor.ambr new file mode 100644 index 00000000000000..ee677d3ed7164d --- /dev/null +++ b/tests/components/lyric/snapshots/test_sensor.ambr @@ -0,0 +1,109 @@ +# serializer version: 1 +# name: test_sensor[sensor.ocala_thermostat_indoor_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.ocala_thermostat_indoor_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Indoor temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Indoor temperature', + 'platform': 'lyric', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indoor_temperature', + 'unique_id': '5CFCE1B67035_indoor_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.ocala_thermostat_indoor_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Ocala Thermostat Indoor temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.ocala_thermostat_indoor_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '26.1111111111111', + }) +# --- +# name: test_sensor[sensor.ocala_thermostat_schedule_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.ocala_thermostat_schedule_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Schedule status', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Schedule status', + 'platform': 'lyric', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'schedule_status', + 'unique_id': '5CFCE1B67035_schedule_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.ocala_thermostat_schedule_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Ocala Thermostat Schedule status', + }), + 'context': , + 'entity_id': 'sensor.ocala_thermostat_schedule_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Resume', + }) +# --- diff --git a/tests/components/lyric/test_sensor.py b/tests/components/lyric/test_sensor.py index 7f74b5e8d63ea7..8243eb09be5d9e 100644 --- a/tests/components/lyric/test_sensor.py +++ b/tests/components/lyric/test_sensor.py @@ -1,8 +1,19 @@ """Tests for the Honeywell Lyric sensor platform.""" from datetime import datetime +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion from homeassistant.components.lyric.sensor import get_datetime_from_future_time +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 def test_get_datetime_from_future_time_none() -> None: @@ -19,3 +30,17 @@ def test_get_datetime_from_future_time_valid() -> None: """Test that a valid time string returns a datetime.""" result = get_datetime_from_future_time("13:30:00") assert isinstance(result, datetime) + + +@pytest.mark.usefixtures("setup_credentials", "mock_lyric_api") +async def test_sensor( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test the Lyric sensor platform via a real config entry setup.""" + with patch("homeassistant.components.lyric.PLATFORMS", [Platform.SENSOR]): + await setup_integration(hass, mock_config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)