Files
core/tests/components/lyric/conftest.py
T
2026-08-29 17:35:23 +02:00

191 lines
6.1 KiB
Python

"""Fixtures for the Honeywell Lyric integration tests."""
from collections.abc import AsyncGenerator, Generator
import time
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from aiolyric import Lyric
from aiolyric.exceptions import LyricException
from aiolyric.objects.location import LyricLocation
from aiolyric.objects.priority import LyricPriority, LyricRoom
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,
load_json_array_fixture,
load_json_object_fixture,
)
CLIENT_ID = "1234"
CLIENT_SECRET = "5678"
MAC_ID = "5CFCE1B67035"
# Second device: has room data but no priority data yet, exercising the
# defensive "no priority entry" branch of LyricPriorityStatusSensor.
NO_PRIORITY_DATA_MAC_ID = "5CFCE1B67036"
@pytest.fixture
async def setup_credentials(hass: HomeAssistant) -> None:
"""Set up the application credentials required for the Lyric OAuth2 flow."""
assert await async_setup_component(hass, "application_credentials", {})
await async_import_client_credential(
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), DOMAIN
)
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return a Lyric config entry with a valid OAuth2 token."""
return MockConfigEntry(
domain=DOMAIN,
data={
"auth_implementation": DOMAIN,
"token": {
"access_token": "mock-access-token",
"refresh_token": "mock-refresh-token",
"expires_at": time.time() + 3600,
"token_type": "Bearer",
},
},
)
def thermostat_json(
mac_id: str, device_id: str, name: str = "Thermostat"
) -> dict[str, Any]:
"""Return a raw aiolyric device payload for a single thermostat."""
return {
**load_json_object_fixture("thermostat.json", DOMAIN),
"deviceID": device_id,
"macID": mac_id,
"name": name,
}
def location_json(location_id: int, devices: list[dict[str, Any]]) -> dict[str, Any]:
"""Return a raw aiolyric location payload containing the given devices."""
return {"locationID": location_id, "name": "Home", "devices": devices}
def build_mock_lyric(
locations: list[LyricLocation], rooms_dict: dict | None = None
) -> MagicMock:
"""Build a mocked aiolyric client backed by real aiolyric location/device objects."""
lyric = MagicMock(spec=Lyric)
lyric.locations = locations
lyric.locations_dict = {location.location_id: location for location in locations}
lyric.rooms_dict = rooms_dict or {}
return lyric
def lyric_exception(
status: int, code: str | None = "GetPriorityFailed"
) -> LyricException:
"""Build a LyricException matching aiolyric's actual payload shape."""
return LyricException(
{
"request": {"method": "GET", "url": "https://example.com"},
"response": {"code": code} if code else {},
"status": status,
}
)
LCC_SUPPORTED_MAC = "AABBCC000001"
NON_LCC_SUPPORTED_MAC = "AABBCC000002"
UNSUPPORTED_MAC = "AABBCC000003"
UNSUPPORTED_DEVICE_ID = "LCC-AABBCC000003"
LIVING_ROOM_JSON = {
"id": 0,
"name": "Living Room",
"avgTemperature": 22,
"avgHumidity": 50,
"accessories": [
{"id": 0, "sensorType": "IndoorAirSensor", "temperature": 22.5, "status": "Ok"}
],
}
OFFICE_ROOM_JSON = {
"id": 0,
"name": "Office",
"avgTemperature": 24,
"avgHumidity": 40,
"accessories": [
{"id": 0, "sensorType": "IndoorAirSensor", "temperature": 24.5, "status": "Ok"}
],
}
@pytest.fixture
async def mock_lyric_mixed_devices() -> AsyncGenerator[MagicMock]:
"""Yield a mocked Lyric client with an LCC device, a non-LCC device, and an unsupported device."""
location = LyricLocation(
MagicMock(),
location_json(
1,
[
thermostat_json(LCC_SUPPORTED_MAC, "LCC-AABBCC000001", "Living Room"),
thermostat_json(NON_LCC_SUPPORTED_MAC, "TCC-AABBCC000002", "Office"),
thermostat_json(UNSUPPORTED_MAC, UNSUPPORTED_DEVICE_ID, "Bedroom"),
],
),
)
rooms = {
LCC_SUPPORTED_MAC: LIVING_ROOM_JSON,
NON_LCC_SUPPORTED_MAC: OFFICE_ROOM_JSON,
}
mac_by_device_id = {device.device_id: device.mac_id for device in location.devices}
lyric = MagicMock(spec=Lyric)
lyric.locations = [location]
lyric.locations_dict = {location.location_id: location}
lyric.rooms_dict = {}
lyric.priorities_dict = {}
async def get_thermostat_rooms(location_id: int, device_id: str) -> None:
if device_id == UNSUPPORTED_DEVICE_ID:
raise lyric_exception(400)
mac_id = mac_by_device_id[device_id]
room_json = rooms[mac_id]
lyric.rooms_dict[mac_id] = {room_json["id"]: LyricRoom(room_json)}
lyric.get_thermostat_rooms = AsyncMock(side_effect=get_thermostat_rooms)
with patch("homeassistant.components.lyric.Lyric", return_value=lyric):
yield lyric
@pytest.fixture
def mock_lyric_api() -> Generator[MagicMock]:
"""Mock the aiolyric client, backed by a real Location and a real LyricPriority."""
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
}
priority_json = load_json_object_fixture("priority.json", DOMAIN)
lyric.priorities_dict = {MAC_ID: LyricPriority(priority_json)}
lyric.rooms_dict = {
MAC_ID: {1: MagicMock()},
NO_PRIORITY_DATA_MAC_ID: {1: MagicMock()},
}
yield lyric