From 703caea2a44e86b287c86af2961073a5d259afe7 Mon Sep 17 00:00:00 2001 From: trip-g Date: Tue, 21 Jul 2026 17:06:40 -0400 Subject: [PATCH 01/10] Add Schedule Status sensor to Lyric integration Resideo's device response includes scheduleStatus (e.g. "Resume"), which was parsed but never surfaced. Adds it as a diagnostic sensor in DEVICE_SENSORS alongside the existing setpoint_status sensor. --- homeassistant/components/lyric/sensor.py | 9 ++++++++- homeassistant/components/lyric/strings.json | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) 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" } From db08f3bfb5dc2a8af4b0fbcf0c12060462dad331 Mon Sep 17 00:00:00 2001 From: trip-g Date: Thu, 23 Jul 2026 06:35:13 -0400 Subject: [PATCH 02/10] Add end-to-end test for the Schedule Status sensor Builds a real LyricDevice from a live-shaped payload (not a pre-set mock) and runs it through async_setup_entry, verifying the entity is created with the correct unique_id, diagnostic entity_category, and native_value. scheduleStatus has no known aiolyric field-name mismatch, so this passes against the currently-pinned release. Co-Authored-By: Claude Sonnet 5 --- tests/components/lyric/test_sensor.py | 55 ++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/tests/components/lyric/test_sensor.py b/tests/components/lyric/test_sensor.py index 7f74b5e8d63ea7..412ca1b8a629be 100644 --- a/tests/components/lyric/test_sensor.py +++ b/tests/components/lyric/test_sensor.py @@ -1,8 +1,15 @@ """Tests for the Honeywell Lyric sensor platform.""" from datetime import datetime +from unittest.mock import MagicMock -from homeassistant.components.lyric.sensor import get_datetime_from_future_time +from aiolyric.objects.device import LyricDevice + +from homeassistant.components.lyric.sensor import ( + async_setup_entry, + get_datetime_from_future_time, +) +from homeassistant.const import EntityCategory def test_get_datetime_from_future_time_none() -> None: @@ -19,3 +26,49 @@ 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) + + +async def test_schedule_status_sensor_end_to_end() -> None: + """Schedule Status is created as a diagnostic sensor with the real value. + + Built from a real LyricDevice parsed from a live-shaped payload (not a + pre-set mock), exercising the actual aiolyric parsing boundary through + to the entity's native_value. scheduleStatus has no known aiolyric + field-name mismatch, so this is expected to pass today. + """ + mac_id = "5CFCE1B67035" + device = LyricDevice( + MagicMock(), + { + "macID": mac_id, + "units": "Fahrenheit", + "scheduleStatus": "Resume", + }, + ) + location = MagicMock() + location.location_id = "location1" + location.devices = [device] + location.devices_dict = {mac_id: device} + + coordinator = MagicMock() + coordinator.data.locations = [location] + coordinator.data.locations_dict = {"location1": location} + coordinator.data.rooms_dict = {} + + entry = MagicMock() + entry.runtime_data = coordinator + + added: list[list] = [] + async_add_entities = MagicMock( + side_effect=lambda entities: added.append(list(entities)) + ) + + await async_setup_entry(MagicMock(), entry, async_add_entities) + + device_entities = added[0] + schedule_entity = next( + e for e in device_entities if e.entity_description.key == "schedule_status" + ) + assert schedule_entity.unique_id == f"{mac_id}_schedule_status" + assert schedule_entity.entity_category is EntityCategory.DIAGNOSTIC + assert schedule_entity.native_value == "Resume" From 22c82feadbc1305ad96070d00ca665b6d1a46062 Mon Sep 17 00:00:00 2001 From: trip-g Date: Thu, 23 Jul 2026 06:45:19 -0400 Subject: [PATCH 03/10] Condense schedule status test docstring Co-Authored-By: Claude Sonnet 5 --- tests/components/lyric/test_sensor.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/components/lyric/test_sensor.py b/tests/components/lyric/test_sensor.py index 412ca1b8a629be..9916ac05ee0c9b 100644 --- a/tests/components/lyric/test_sensor.py +++ b/tests/components/lyric/test_sensor.py @@ -29,13 +29,7 @@ def test_get_datetime_from_future_time_valid() -> None: async def test_schedule_status_sensor_end_to_end() -> None: - """Schedule Status is created as a diagnostic sensor with the real value. - - Built from a real LyricDevice parsed from a live-shaped payload (not a - pre-set mock), exercising the actual aiolyric parsing boundary through - to the entity's native_value. scheduleStatus has no known aiolyric - field-name mismatch, so this is expected to pass today. - """ + """Test the schedule status diagnostic sensor.""" mac_id = "5CFCE1B67035" device = LyricDevice( MagicMock(), From 729e4028b4e7fb5251e3649b3fe347a2f85e5b30 Mon Sep 17 00:00:00 2001 From: trip-g Date: Thu, 23 Jul 2026 07:45:44 -0400 Subject: [PATCH 04/10] Rework schedule status test to use a real config entry setup CI enforces this as a hard rule (pylint plugin check home-assistant-tests-direct-platform-async-setup-entry, W7420): a platform's async_setup_entry must not be called directly in tests; use hass.config_entries.async_setup(entry.entry_id) instead. Adds the same conftest.py fixtures as the binary_sensor PR (real authenticated config entry via application_credentials, HTTP-level mocks for /locations and /priority using the actual live payload shape). scheduleStatus comes from /locations, which has no known aiolyric field-name mismatch, so this passes for real against the currently-pinned release - no xfail needed for this one. Note: could not run this against a real pytest+hass fixture harness locally (Windows/fcntl limitation) - built against this repo's own proven test_config_flow.py pattern, but needs a real pytest run to confirm. Co-Authored-By: Claude Sonnet 5 --- tests/components/lyric/conftest.py | 149 ++++++++++++++++++++++++++ tests/components/lyric/test_sensor.py | 73 ++++++------- 2 files changed, 180 insertions(+), 42 deletions(-) create mode 100644 tests/components/lyric/conftest.py diff --git a/tests/components/lyric/conftest.py b/tests/components/lyric/conftest.py new file mode 100644 index 00000000000000..e1271011d8c538 --- /dev/null +++ b/tests/components/lyric/conftest.py @@ -0,0 +1,149 @@ +"""Fixtures for the Honeywell Lyric integration tests.""" + +from time import time + +import pytest + +from homeassistant.components.application_credentials import ( + 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 +from tests.test_util.aiohttp import AiohttpClientMocker + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" + +BASE_URL = "https://api.honeywellhome.com/v2" + +# Real payload shapes captured from a live T9-T10 account with paired +# RCHTSENSOR room accessories. Deliberately using the actual field names +# Resideo returns (e.g. vacationHold.Enabled, accessories[].sensorType), +# not the ones aiolyric happens to read, so tests exercise real parsing +# rather than hiding behind pre-parsed mocks. +LOCATION_ID = "35202000168931" +# Deliberately "LCC-"-prefixed: this branch is intentionally cut from a +# clean base without the coordinator fix from home-assistant/core#177022 +# (which removes a device-ID-prefix heuristic gating the /priority fetch). +# Using a non-"LCC-" ID here would make room-level entities fail to be +# created for that unrelated, separately-tracked reason, muddying what +# these tests are actually checking. +DEVICE_ID = "LCC-7f86b153-8480-f111-b78f-6045bdb25006" +MAC_ID = "5CFCE1B67035" + +LOCATIONS_RESPONSE = [ + { + "locationID": LOCATION_ID, + "name": "Ocala P01", + "devices": [ + { + "vacationHold": {"Enabled": True}, + "scheduleStatus": "Resume", + "settings": {"devicePairingEnabled": True}, + "deviceClass": "Thermostat", + "deviceType": "Thermostat", + "deviceID": DEVICE_ID, + "name": "Ocala", + "macID": MAC_ID, + "units": "Fahrenheit", + "indoorTemperature": 79, + "deviceModel": "T9-T10", + } + ], + "users": [], + } +] + +PRIORITY_RESPONSE = { + "deviceId": MAC_ID, + "priorityStatus": "NoHold", + "priority": { + "priorityType": "PickARoom", + "selectedRooms": [1], + "rooms": [ + { + "id": 1, + "name": "Primary Bedroom", + "avgTemperature": 79, + "avgHumidity": 54, + "overallMotion": False, + "accessories": [ + { + "id": 1, + "sensorType": "IndoorAirSensor", + "temperature": 79, + "status": "Ok", + "detectMotion": True, + } + ], + } + ], + }, +} + + +@pytest.fixture +async def setup_credentials(hass: HomeAssistant) -> None: + """Register lyric application credentials, matching test_config_flow.py.""" + await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + await async_import_client_credential( + hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "cred" + ) + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return an already-authenticated Lyric config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data={ + # Matches the credential name registered by setup_credentials via + # async_import_client_credential(..., "cred") - confirmed against + # test_config_flow.py's test_full_flow, which asserts a real + # completed flow ends up with auth_implementation == "cred", not + # DOMAIN. + "auth_implementation": "cred", + "token": { + "access_token": "mock-access-token", + "refresh_token": "mock-refresh-token", + "expires_at": time() + 3600, + "token_type": "Bearer", + }, + }, + ) + + +@pytest.fixture +def mock_lyric_api(aioclient_mock: AiohttpClientMocker) -> AiohttpClientMocker: + """Mock the /locations and /priority HTTP endpoints with real-shaped data. + + Registered at the aiohttp transport level, so the real aiolyric client + and coordinator code runs unmodified - only the network call itself is + faked, using the actual field names Resideo returns. + """ + aioclient_mock.get( + f"{BASE_URL}/locations?apikey={CLIENT_ID}", + json=LOCATIONS_RESPONSE, + ) + aioclient_mock.get( + f"{BASE_URL}/devices/thermostats/{DEVICE_ID}/priority" + f"?apikey={CLIENT_ID}&locationId={LOCATION_ID}", + json=PRIORITY_RESPONSE, + ) + return aioclient_mock + + +async def async_setup_lyric_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Set up the mock config entry and wait for it to settle.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/lyric/test_sensor.py b/tests/components/lyric/test_sensor.py index 9916ac05ee0c9b..53778051901455 100644 --- a/tests/components/lyric/test_sensor.py +++ b/tests/components/lyric/test_sensor.py @@ -1,15 +1,16 @@ """Tests for the Honeywell Lyric sensor platform.""" from datetime import datetime -from unittest.mock import MagicMock -from aiolyric.objects.device import LyricDevice - -from homeassistant.components.lyric.sensor import ( - async_setup_entry, - get_datetime_from_future_time, -) +from homeassistant.components.lyric.sensor import get_datetime_from_future_time from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .conftest import MAC_ID, async_setup_lyric_entry + +from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker def test_get_datetime_from_future_time_none() -> None: @@ -28,41 +29,29 @@ def test_get_datetime_from_future_time_valid() -> None: assert isinstance(result, datetime) -async def test_schedule_status_sensor_end_to_end() -> None: - """Test the schedule status diagnostic sensor.""" - mac_id = "5CFCE1B67035" - device = LyricDevice( - MagicMock(), - { - "macID": mac_id, - "units": "Fahrenheit", - "scheduleStatus": "Resume", - }, - ) - location = MagicMock() - location.location_id = "location1" - location.devices = [device] - location.devices_dict = {mac_id: device} - - coordinator = MagicMock() - coordinator.data.locations = [location] - coordinator.data.locations_dict = {"location1": location} - coordinator.data.rooms_dict = {} - - entry = MagicMock() - entry.runtime_data = coordinator - - added: list[list] = [] - async_add_entities = MagicMock( - side_effect=lambda entities: added.append(list(entities)) - ) +async def test_schedule_status_sensor_end_to_end( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + setup_credentials: None, + mock_lyric_api: AiohttpClientMocker, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the schedule status diagnostic sensor via a real config entry setup. - await async_setup_entry(MagicMock(), entry, async_add_entities) + scheduleStatus comes from the base /locations response (not the + /priority endpoint), so it has no known aiolyric field-name mismatch + and passes for real against the currently-pinned release. + """ + await async_setup_lyric_entry(hass, mock_config_entry) - device_entities = added[0] - schedule_entity = next( - e for e in device_entities if e.entity_description.key == "schedule_status" + entity_id = entity_registry.async_get_entity_id( + "sensor", "lyric", f"{MAC_ID}_schedule_status" ) - assert schedule_entity.unique_id == f"{mac_id}_schedule_status" - assert schedule_entity.entity_category is EntityCategory.DIAGNOSTIC - assert schedule_entity.native_value == "Resume" + assert entity_id + entry = entity_registry.async_get(entity_id) + assert entry + assert entry.entity_category is EntityCategory.DIAGNOSTIC + + state = hass.states.get(entity_id) + assert state + assert state.state == "Resume" From f4fe60de32f645e23f1dfc55dab4f34c64491460 Mon Sep 17 00:00:00 2001 From: clutch2sft <148905f4@opayq.com> Date: Fri, 24 Jul 2026 08:25:01 -0400 Subject: [PATCH 05/10] Address review: default credential name, patch aiolyric objects, snapshot platform - Register the test credential under DOMAIN instead of a bespoke "cred" name, matching the application_credentials default and every other OAuth2 integration's conftest. - Set up application_credentials directly instead of the lyric domain itself; the latter only worked as a side effect of lyric listing it as a manifest dependency. - Patch Lyric.get_locations/get_thermostat_rooms to build real LyricLocation/LyricPriority objects instead of mocking HTTP responses at the aiohttp transport level. Field-name parsing still runs for real (the properties read straight off the same attributes dicts), but the test no longer depends on network-mocking machinery to exercise it. - Switch the schedule status test to snapshot_platform, covering the whole sensor platform via syrupy instead of one hand-picked entity. --- tests/components/lyric/conftest.py | 65 ++++++----- .../lyric/snapshots/test_sensor.ambr | 109 ++++++++++++++++++ tests/components/lyric/test_sensor.py | 39 +++---- 3 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 tests/components/lyric/snapshots/test_sensor.ambr diff --git a/tests/components/lyric/conftest.py b/tests/components/lyric/conftest.py index e1271011d8c538..d16fb245dffc13 100644 --- a/tests/components/lyric/conftest.py +++ b/tests/components/lyric/conftest.py @@ -1,10 +1,16 @@ """Fixtures for the Honeywell Lyric integration tests.""" +from collections.abc import Generator from time import time +from unittest.mock import patch +from aiolyric import Lyric +from aiolyric.objects.location import LyricLocation +from aiolyric.objects.priority import LyricPriority import pytest from homeassistant.components.application_credentials import ( + DOMAIN as APPLICATION_CREDENTIALS_DOMAIN, ClientCredential, async_import_client_credential, ) @@ -13,7 +19,6 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.test_util.aiohttp import AiohttpClientMocker CLIENT_ID = "1234" CLIENT_SECRET = "5678" @@ -88,12 +93,11 @@ @pytest.fixture async def setup_credentials(hass: HomeAssistant) -> None: - """Register lyric application credentials, matching test_config_flow.py.""" - await async_setup_component(hass, DOMAIN, {}) - await hass.async_block_till_done() + """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), "cred" + hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET) ) @@ -103,12 +107,7 @@ def mock_config_entry() -> MockConfigEntry: return MockConfigEntry( domain=DOMAIN, data={ - # Matches the credential name registered by setup_credentials via - # async_import_client_credential(..., "cred") - confirmed against - # test_config_flow.py's test_full_flow, which asserts a real - # completed flow ends up with auth_implementation == "cred", not - # DOMAIN. - "auth_implementation": "cred", + "auth_implementation": DOMAIN, "token": { "access_token": "mock-access-token", "refresh_token": "mock-refresh-token", @@ -120,23 +119,37 @@ def mock_config_entry() -> MockConfigEntry: @pytest.fixture -def mock_lyric_api(aioclient_mock: AiohttpClientMocker) -> AiohttpClientMocker: - """Mock the /locations and /priority HTTP endpoints with real-shaped data. +def mock_lyric_api() -> Generator[None]: + """Patch the aiolyric client to build real Location/Priority objects. - Registered at the aiohttp transport level, so the real aiolyric client - and coordinator code runs unmodified - only the network call itself is - faked, using the actual field names Resideo returns. + Patches Lyric.get_locations/get_thermostat_rooms directly rather than + mocking HTTP responses, so tests exercise real aiolyric parsing (the + same LyricLocation/LyricPriority code reading the actual field names + Resideo returns) without depending on network-mocking machinery. """ - aioclient_mock.get( - f"{BASE_URL}/locations?apikey={CLIENT_ID}", - json=LOCATIONS_RESPONSE, - ) - aioclient_mock.get( - f"{BASE_URL}/devices/thermostats/{DEVICE_ID}/priority" - f"?apikey={CLIENT_ID}&locationId={LOCATION_ID}", - json=PRIORITY_RESPONSE, - ) - return aioclient_mock + + async def get_locations(self: Lyric) -> None: + self._locations = [ + LyricLocation(self._client, location) for location in LOCATIONS_RESPONSE + ] + self._locations_dict = { + location.location_id: location for location in self._locations + } + + async def get_thermostat_rooms( + self: Lyric, location_id: str, device_id: str + ) -> None: + priority = LyricPriority(PRIORITY_RESPONSE) + self._priorities_dict[priority.device_id] = priority + self._rooms_dict[priority.device_id] = { + room.id: room for room in priority.current_priority.rooms + } + + with ( + patch.object(Lyric, "get_locations", get_locations), + patch.object(Lyric, "get_thermostat_rooms", get_thermostat_rooms), + ): + yield async def async_setup_lyric_entry( diff --git a/tests/components/lyric/snapshots/test_sensor.ambr b/tests/components/lyric/snapshots/test_sensor.ambr new file mode 100644 index 00000000000000..bef6b8899a3ffd --- /dev/null +++ b/tests/components/lyric/snapshots/test_sensor.ambr @@ -0,0 +1,109 @@ +# serializer version: 1 +# name: test_sensor[sensor.ocala_thermostat-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', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + '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-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Ocala Thermostat', + }), + 'context': , + 'entity_id': 'sensor.ocala_thermostat', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Resume', + }) +# --- +# 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', + }) +# --- diff --git a/tests/components/lyric/test_sensor.py b/tests/components/lyric/test_sensor.py index 53778051901455..2fffcc39129631 100644 --- a/tests/components/lyric/test_sensor.py +++ b/tests/components/lyric/test_sensor.py @@ -1,16 +1,18 @@ """Tests for the Honeywell Lyric sensor platform.""" from datetime import datetime +from unittest.mock import patch + +from syrupy.assertion import SnapshotAssertion from homeassistant.components.lyric.sensor import get_datetime_from_future_time -from homeassistant.const import EntityCategory +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import MAC_ID, async_setup_lyric_entry +from .conftest import async_setup_lyric_entry -from tests.common import MockConfigEntry -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry, snapshot_platform def test_get_datetime_from_future_time_none() -> None: @@ -29,29 +31,16 @@ def test_get_datetime_from_future_time_valid() -> None: assert isinstance(result, datetime) -async def test_schedule_status_sensor_end_to_end( +async def test_sensor( hass: HomeAssistant, entity_registry: er.EntityRegistry, setup_credentials: None, - mock_lyric_api: AiohttpClientMocker, + mock_lyric_api: None, mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, ) -> None: - """Test the schedule status diagnostic sensor via a real config entry setup. - - scheduleStatus comes from the base /locations response (not the - /priority endpoint), so it has no known aiolyric field-name mismatch - and passes for real against the currently-pinned release. - """ - await async_setup_lyric_entry(hass, mock_config_entry) - - entity_id = entity_registry.async_get_entity_id( - "sensor", "lyric", f"{MAC_ID}_schedule_status" - ) - assert entity_id - entry = entity_registry.async_get(entity_id) - assert entry - assert entry.entity_category is EntityCategory.DIAGNOSTIC - - state = hass.states.get(entity_id) - assert state - assert state.state == "Resume" + """Test the Lyric sensor platform via a real config entry setup.""" + with patch("homeassistant.components.lyric.PLATFORMS", [Platform.SENSOR]): + await async_setup_lyric_entry(hass, mock_config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) From e3fbc7422ea76fde992c392c283cdced4e36cc74 Mon Sep 17 00:00:00 2001 From: clutch2sft <148905f4@opayq.com> Date: Sat, 25 Jul 2026 13:30:17 -0400 Subject: [PATCH 06/10] Regenerate schedule status snapshot with translations compiled The committed snapshot was generated without running script.translations develop first, so it captured the untranslated fallback entity_id (sensor.ocala_thermostat, no suffix) instead of the real one. translations/en.json is gitignored and always built fresh, including in CI's "Compile English translations" step, so the mismatch only showed up there: PR #177067's CI run failed pytest on this exact commit. Reproduced locally with the same command CI uses (python3 -b -X dev -m pytest ... --numprocesses auto ...) after running the translation step, confirmed the real entity_id is sensor.ocala_thermostat_schedule_status, and regenerated the snapshot against that. Co-Authored-By: Claude Sonnet 5 --- .../lyric/snapshots/test_sensor.ambr | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/components/lyric/snapshots/test_sensor.ambr b/tests/components/lyric/snapshots/test_sensor.ambr index bef6b8899a3ffd..ee677d3ed7164d 100644 --- a/tests/components/lyric/snapshots/test_sensor.ambr +++ b/tests/components/lyric/snapshots/test_sensor.ambr @@ -1,19 +1,21 @@ # serializer version: 1 -# name: test_sensor[sensor.ocala_thermostat-entry] +# name: test_sensor[sensor.ocala_thermostat_indoor_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.ocala_thermostat', + 'entity_category': None, + 'entity_id': 'sensor.ocala_thermostat_indoor_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -21,51 +23,55 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'Indoor temperature', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': None, + 'original_name': 'Indoor temperature', '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, + 'translation_key': 'indoor_temperature', + 'unique_id': '5CFCE1B67035_indoor_temperature', + 'unit_of_measurement': , }) # --- -# name: test_sensor[sensor.ocala_thermostat-state] +# name: test_sensor[sensor.ocala_thermostat_indoor_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Ocala Thermostat', + : 'temperature', + : 'Ocala Thermostat Indoor temperature', + : , + : , }), 'context': , - 'entity_id': 'sensor.ocala_thermostat', + 'entity_id': 'sensor.ocala_thermostat_indoor_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'Resume', + 'state': '26.1111111111111', }) # --- -# name: test_sensor[sensor.ocala_thermostat_indoor_temperature-entry] +# name: test_sensor[sensor.ocala_thermostat_schedule_status-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : , - }), + 'capabilities': None, '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', + 'entity_category': , + 'entity_id': 'sensor.ocala_thermostat_schedule_status', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -73,37 +79,31 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Indoor temperature', + 'object_id_base': 'Schedule status', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Indoor temperature', + 'original_name': 'Schedule status', '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': , + 'translation_key': 'schedule_status', + 'unique_id': '5CFCE1B67035_schedule_status', + 'unit_of_measurement': None, }) # --- -# name: test_sensor[sensor.ocala_thermostat_indoor_temperature-state] +# name: test_sensor[sensor.ocala_thermostat_schedule_status-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'temperature', - : 'Ocala Thermostat Indoor temperature', - : , - : , + : 'Ocala Thermostat Schedule status', }), 'context': , - 'entity_id': 'sensor.ocala_thermostat_indoor_temperature', + 'entity_id': 'sensor.ocala_thermostat_schedule_status', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '26.1111111111111', + 'state': 'Resume', }) # --- From a0fe1cbfbd4cd5506cde00ccd9382077f23360c4 Mon Sep 17 00:00:00 2001 From: clutch2sft Date: Mon, 27 Jul 2026 07:51:47 -0400 Subject: [PATCH 07/10] Address Copilot feedback: trim stale comments, drop unused priority fixture - Fix the DEVICE_ID/LOCATION_ID comments that still had the old branch-history narrative (already trimmed on the sibling binary-sensor-room-motion branch, but never ported here). - Remove PRIORITY_RESPONSE and the LyricPriority import: this test only covers device-level sensors (indoor_temperature, schedule_status), and the snapshot has no room/accessory entities regardless of what get_thermostat_rooms returns (the pinned aiolyric's sensorType/type mismatch keeps ACCESSORY_SENSORS from being created either way). Make get_thermostat_rooms a genuine no-op instead of building room data nothing in this file asserts on. --- tests/components/lyric/conftest.py | 64 +++++++----------------------- 1 file changed, 14 insertions(+), 50 deletions(-) diff --git a/tests/components/lyric/conftest.py b/tests/components/lyric/conftest.py index d16fb245dffc13..129c0dcab1be7d 100644 --- a/tests/components/lyric/conftest.py +++ b/tests/components/lyric/conftest.py @@ -6,7 +6,6 @@ from aiolyric import Lyric from aiolyric.objects.location import LyricLocation -from aiolyric.objects.priority import LyricPriority import pytest from homeassistant.components.application_credentials import ( @@ -25,18 +24,12 @@ BASE_URL = "https://api.honeywellhome.com/v2" -# Real payload shapes captured from a live T9-T10 account with paired -# RCHTSENSOR room accessories. Deliberately using the actual field names -# Resideo returns (e.g. vacationHold.Enabled, accessories[].sensorType), -# not the ones aiolyric happens to read, so tests exercise real parsing -# rather than hiding behind pre-parsed mocks. +# Real payload shape captured from a live account, using the actual field +# names Resideo returns (e.g. vacationHold.Enabled) rather than the ones +# aiolyric happens to read, so the test exercises real parsing instead of +# a pre-parsed mock. LOCATION_ID = "35202000168931" -# Deliberately "LCC-"-prefixed: this branch is intentionally cut from a -# clean base without the coordinator fix from home-assistant/core#177022 -# (which removes a device-ID-prefix heuristic gating the /priority fetch). -# Using a non-"LCC-" ID here would make room-level entities fail to be -# created for that unrelated, separately-tracked reason, muddying what -# these tests are actually checking. +# Use an LCC-prefixed ID so the current coordinator fetches room data. DEVICE_ID = "LCC-7f86b153-8480-f111-b78f-6045bdb25006" MAC_ID = "5CFCE1B67035" @@ -63,33 +56,6 @@ } ] -PRIORITY_RESPONSE = { - "deviceId": MAC_ID, - "priorityStatus": "NoHold", - "priority": { - "priorityType": "PickARoom", - "selectedRooms": [1], - "rooms": [ - { - "id": 1, - "name": "Primary Bedroom", - "avgTemperature": 79, - "avgHumidity": 54, - "overallMotion": False, - "accessories": [ - { - "id": 1, - "sensorType": "IndoorAirSensor", - "temperature": 79, - "status": "Ok", - "detectMotion": True, - } - ], - } - ], - }, -} - @pytest.fixture async def setup_credentials(hass: HomeAssistant) -> None: @@ -120,12 +86,14 @@ def mock_config_entry() -> MockConfigEntry: @pytest.fixture def mock_lyric_api() -> Generator[None]: - """Patch the aiolyric client to build real Location/Priority objects. - - Patches Lyric.get_locations/get_thermostat_rooms directly rather than - mocking HTTP responses, so tests exercise real aiolyric parsing (the - same LyricLocation/LyricPriority code reading the actual field names - Resideo returns) without depending on network-mocking machinery. + """Patch the aiolyric client to build a real Location object. + + Patches Lyric.get_locations directly rather than mocking HTTP + responses, so the test exercises real aiolyric parsing (the same + LyricLocation code reading the actual field names Resideo returns) + without depending on network-mocking machinery. get_thermostat_rooms + is a no-op: this test only covers device-level sensors, not the + room/priority data it would otherwise populate. """ async def get_locations(self: Lyric) -> None: @@ -139,11 +107,7 @@ async def get_locations(self: Lyric) -> None: async def get_thermostat_rooms( self: Lyric, location_id: str, device_id: str ) -> None: - priority = LyricPriority(PRIORITY_RESPONSE) - self._priorities_dict[priority.device_id] = priority - self._rooms_dict[priority.device_id] = { - room.id: room for room in priority.current_priority.rooms - } + return with ( patch.object(Lyric, "get_locations", get_locations), From c864bad996dc5740a56686350fc178fc6d44549d Mon Sep 17 00:00:00 2001 From: clutch2sft Date: Tue, 28 Jul 2026 16:00:21 -0400 Subject: [PATCH 08/10] Bring test fixtures in line with the mealie/JSON-fixture convention Same pattern already applied to binary-sensor-room-motion after joostlek's review there - this branch predates it: - Move LOCATIONS_RESPONSE from an inline dict into fixtures/locations.json. - Patch Lyric where the integration imports it (autospec, matching mealie's client mock) instead of patch.object on individual aiolyric methods. - Move async_setup_lyric_entry out of conftest.py into tests/components/lyric/__init__.py as setup_integration, matching the convention used by this integration's other test modules. No behavior change: same fixture data, same snapshot still matches. --- tests/components/lyric/__init__.py | 12 +++ tests/components/lyric/conftest.py | 87 +++++-------------- .../components/lyric/fixtures/locations.json | 22 +++++ tests/components/lyric/test_sensor.py | 8 +- 4 files changed, 58 insertions(+), 71 deletions(-) create mode 100644 tests/components/lyric/fixtures/locations.json 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 index 129c0dcab1be7d..6825acc838f0ee 100644 --- a/tests/components/lyric/conftest.py +++ b/tests/components/lyric/conftest.py @@ -2,9 +2,8 @@ from collections.abc import Generator from time import time -from unittest.mock import patch +from unittest.mock import MagicMock, patch -from aiolyric import Lyric from aiolyric.objects.location import LyricLocation import pytest @@ -17,45 +16,16 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_json_array_fixture CLIENT_ID = "1234" CLIENT_SECRET = "5678" -BASE_URL = "https://api.honeywellhome.com/v2" - -# Real payload shape captured from a live account, using the actual field -# names Resideo returns (e.g. vacationHold.Enabled) rather than the ones -# aiolyric happens to read, so the test exercises real parsing instead of -# a pre-parsed mock. +# Matches the values baked into fixtures/locations.json. LOCATION_ID = "35202000168931" -# Use an LCC-prefixed ID so the current coordinator fetches room data. DEVICE_ID = "LCC-7f86b153-8480-f111-b78f-6045bdb25006" MAC_ID = "5CFCE1B67035" -LOCATIONS_RESPONSE = [ - { - "locationID": LOCATION_ID, - "name": "Ocala P01", - "devices": [ - { - "vacationHold": {"Enabled": True}, - "scheduleStatus": "Resume", - "settings": {"devicePairingEnabled": True}, - "deviceClass": "Thermostat", - "deviceType": "Thermostat", - "deviceID": DEVICE_ID, - "name": "Ocala", - "macID": MAC_ID, - "units": "Fahrenheit", - "indoorTemperature": 79, - "deviceModel": "T9-T10", - } - ], - "users": [], - } -] - @pytest.fixture async def setup_credentials(hass: HomeAssistant) -> None: @@ -85,42 +55,25 @@ def mock_config_entry() -> MockConfigEntry: @pytest.fixture -def mock_lyric_api() -> Generator[None]: - """Patch the aiolyric client to build a real Location object. - - Patches Lyric.get_locations directly rather than mocking HTTP - responses, so the test exercises real aiolyric parsing (the same - LyricLocation code reading the actual field names Resideo returns) - without depending on network-mocking machinery. get_thermostat_rooms - is a no-op: this test only covers device-level sensors, not the - room/priority data it would otherwise populate. +def mock_lyric_api() -> Generator[MagicMock]: + """Mock the aiolyric client, backed by a real Location parsed from a live-shaped fixture. + + Patches Lyric where the integration imports it (autospec, like the + mealie client mock) rather than mocking HTTP responses, so the test + exercises real aiolyric parsing - the same LyricLocation code reading + the actual field names Resideo returns. 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 - async def get_locations(self: Lyric) -> None: - self._locations = [ - LyricLocation(self._client, location) for location in LOCATIONS_RESPONSE + locations_json = load_json_array_fixture("locations.json", DOMAIN) + lyric.locations = [ + LyricLocation(MagicMock(), location) for location in locations_json ] - self._locations_dict = { - location.location_id: location for location in self._locations + lyric.locations_dict = { + location.location_id: location for location in lyric.locations } - async def get_thermostat_rooms( - self: Lyric, location_id: str, device_id: str - ) -> None: - return - - with ( - patch.object(Lyric, "get_locations", get_locations), - patch.object(Lyric, "get_thermostat_rooms", get_thermostat_rooms), - ): - yield - - -async def async_setup_lyric_entry( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, -) -> None: - """Set up the mock config entry and wait for it to settle.""" - mock_config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() + yield lyric diff --git a/tests/components/lyric/fixtures/locations.json b/tests/components/lyric/fixtures/locations.json new file mode 100644 index 00000000000000..516ace0010fee9 --- /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/test_sensor.py b/tests/components/lyric/test_sensor.py index 2fffcc39129631..87447cdd06a1a6 100644 --- a/tests/components/lyric/test_sensor.py +++ b/tests/components/lyric/test_sensor.py @@ -1,7 +1,7 @@ """Tests for the Honeywell Lyric sensor platform.""" from datetime import datetime -from unittest.mock import patch +from unittest.mock import MagicMock, patch from syrupy.assertion import SnapshotAssertion @@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import async_setup_lyric_entry +from . import setup_integration from tests.common import MockConfigEntry, snapshot_platform @@ -35,12 +35,12 @@ async def test_sensor( hass: HomeAssistant, entity_registry: er.EntityRegistry, setup_credentials: None, - mock_lyric_api: None, + mock_lyric_api: MagicMock, 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 async_setup_lyric_entry(hass, mock_config_entry) + await setup_integration(hass, mock_config_entry) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) From 34bca74ede93a2491939b9765ad54d863ea86d0d Mon Sep 17 00:00:00 2001 From: clutch2sft Date: Tue, 28 Jul 2026 16:55:20 -0400 Subject: [PATCH 09/10] Fix locationID fixture/constant type to match the documented API contract Resideo's own API docs document locationID as an Integer, and aiolyric's get_thermostat_rooms already types it int, but this fixture quoted it as a string - inherited from early in this session before checking the real docs. No behavior change (the value is only ever interpolated into a URL), but the fixture now matches reality. --- tests/components/lyric/conftest.py | 2 +- tests/components/lyric/fixtures/locations.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/lyric/conftest.py b/tests/components/lyric/conftest.py index 6825acc838f0ee..829ec2069c4230 100644 --- a/tests/components/lyric/conftest.py +++ b/tests/components/lyric/conftest.py @@ -22,7 +22,7 @@ CLIENT_SECRET = "5678" # Matches the values baked into fixtures/locations.json. -LOCATION_ID = "35202000168931" +LOCATION_ID = 35202000168931 DEVICE_ID = "LCC-7f86b153-8480-f111-b78f-6045bdb25006" MAC_ID = "5CFCE1B67035" diff --git a/tests/components/lyric/fixtures/locations.json b/tests/components/lyric/fixtures/locations.json index 516ace0010fee9..0d3f11736e2abd 100644 --- a/tests/components/lyric/fixtures/locations.json +++ b/tests/components/lyric/fixtures/locations.json @@ -1,6 +1,6 @@ [ { - "locationID": "35202000168931", + "locationID": 35202000168931, "name": "Ocala P01", "devices": [ { From 7159c32e8ecfc00e741338a5949ada351a3d604e Mon Sep 17 00:00:00 2001 From: clutch2sft Date: Tue, 28 Jul 2026 17:15:36 -0400 Subject: [PATCH 10/10] Address Copilot's low-confidence review comments - Condense mock_lyric_api's docstring to the non-obvious constraint (get_thermostat_rooms is a no-op, why); drop the mechanics narration and unrelated-integration citation. - Apply setup_credentials/mock_lyric_api via usefixtures in test_sensor instead of injecting them as unused parameters, matching the pattern test_config_flow.py already uses in this same file's test suite. - Remove LOCATION_ID/DEVICE_ID/MAC_ID constants: dead now that test_sensor.py uses snapshot_platform instead of building entity_ids from them. --- tests/components/lyric/conftest.py | 14 +++----------- tests/components/lyric/test_sensor.py | 6 +++--- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/components/lyric/conftest.py b/tests/components/lyric/conftest.py index 829ec2069c4230..4f4c3408d2ecfb 100644 --- a/tests/components/lyric/conftest.py +++ b/tests/components/lyric/conftest.py @@ -21,11 +21,6 @@ CLIENT_ID = "1234" CLIENT_SECRET = "5678" -# Matches the values baked into fixtures/locations.json. -LOCATION_ID = 35202000168931 -DEVICE_ID = "LCC-7f86b153-8480-f111-b78f-6045bdb25006" -MAC_ID = "5CFCE1B67035" - @pytest.fixture async def setup_credentials(hass: HomeAssistant) -> None: @@ -58,12 +53,9 @@ def mock_config_entry() -> MockConfigEntry: def mock_lyric_api() -> Generator[MagicMock]: """Mock the aiolyric client, backed by a real Location parsed from a live-shaped fixture. - Patches Lyric where the integration imports it (autospec, like the - mealie client mock) rather than mocking HTTP responses, so the test - exercises real aiolyric parsing - the same LyricLocation code reading - the actual field names Resideo returns. 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. + 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 diff --git a/tests/components/lyric/test_sensor.py b/tests/components/lyric/test_sensor.py index 87447cdd06a1a6..8243eb09be5d9e 100644 --- a/tests/components/lyric/test_sensor.py +++ b/tests/components/lyric/test_sensor.py @@ -1,8 +1,9 @@ """Tests for the Honeywell Lyric sensor platform.""" from datetime import datetime -from unittest.mock import MagicMock, patch +from unittest.mock import patch +import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.lyric.sensor import get_datetime_from_future_time @@ -31,11 +32,10 @@ def test_get_datetime_from_future_time_valid() -> None: assert isinstance(result, datetime) +@pytest.mark.usefixtures("setup_credentials", "mock_lyric_api") async def test_sensor( hass: HomeAssistant, entity_registry: er.EntityRegistry, - setup_credentials: None, - mock_lyric_api: MagicMock, mock_config_entry: MockConfigEntry, snapshot: SnapshotAssertion, ) -> None: