core/tests/components/hue/conftest.py

286 lines
8.9 KiB
Python

"""Test helpers for Hue."""
import asyncio
from collections import deque
from collections.abc import Generator
import logging
from typing import Any
from unittest.mock import AsyncMock, Mock, patch
import aiohue.v1 as aiohue_v1
import aiohue.v2 as aiohue_v2
from aiohue.v2.controllers.events import EventType
import pytest
from homeassistant.components import hue
from homeassistant.components.hue.v1 import sensor_base as hue_sensor_base
from homeassistant.components.hue.v2.device import async_setup_devices
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util.json import JsonArrayType
from .const import FAKE_BRIDGE, FAKE_BRIDGE_DEVICE
from tests.common import MockConfigEntry, load_json_array_fixture
@pytest.fixture(autouse=True)
def no_request_delay() -> Generator[None]:
"""Make the request refresh delay 0 for instant tests."""
with patch("homeassistant.components.hue.const.REQUEST_REFRESH_DELAY", 0):
yield
def create_mock_bridge(hass: HomeAssistant, api_version: int = 1) -> Mock:
"""Create a mocked HueBridge instance."""
bridge = Mock(
hass=hass,
authorized=True,
config_entry=None,
reset_jobs=[],
api_version=api_version,
spec=hue.HueBridge,
)
bridge.logger = logging.getLogger(__name__)
if bridge.api_version == 2:
bridge.api = create_mock_api_v2()
bridge.mock_requests = bridge.api.mock_requests
else:
bridge.api = create_mock_api_v1()
bridge.sensor_manager = hue_sensor_base.SensorManager(bridge)
bridge.mock_requests = bridge.api.mock_requests
bridge.mock_light_responses = bridge.api.mock_light_responses
bridge.mock_group_responses = bridge.api.mock_group_responses
bridge.mock_sensor_responses = bridge.api.mock_sensor_responses
async def async_initialize_bridge():
if bridge.config_entry:
hass.data.setdefault(hue.DOMAIN, {})[bridge.config_entry.entry_id] = bridge
if bridge.api_version == 2:
await async_setup_devices(bridge)
return True
bridge.async_initialize_bridge = async_initialize_bridge
async def async_request_call(task, *args, **kwargs):
await task(*args, **kwargs)
bridge.async_request_call = async_request_call
async def async_reset():
if bridge.config_entry:
hass.data[hue.DOMAIN].pop(bridge.config_entry.entry_id)
return True
bridge.async_reset = async_reset
return bridge
@pytest.fixture
def mock_api_v1() -> Mock:
"""Mock the Hue V1 api."""
return create_mock_api_v1()
@pytest.fixture
def mock_api_v2() -> Mock:
"""Mock the Hue V2 api."""
return create_mock_api_v2()
def create_mock_api_v1() -> Mock:
"""Create a mock V1 API."""
api = Mock(spec=aiohue_v1.HueBridgeV1)
api.initialize = AsyncMock()
api.mock_requests = []
api.mock_light_responses = deque()
api.mock_group_responses = deque()
api.mock_sensor_responses = deque()
api.mock_scene_responses = deque()
async def mock_request(method, path, **kwargs):
kwargs["method"] = method
kwargs["path"] = path
api.mock_requests.append(kwargs)
if path == "lights":
return api.mock_light_responses.popleft()
if path == "groups":
return api.mock_group_responses.popleft()
if path == "sensors":
return api.mock_sensor_responses.popleft()
if path == "scenes":
return api.mock_scene_responses.popleft()
return None
logger = logging.getLogger(__name__)
api.config = Mock(
bridge_id="ff:ff:ff:ff:ff:ff",
mac_address="aa:bb:cc:dd:ee:ff",
model_id="BSB002",
apiversion="9.9.9",
software_version="1935144040",
)
api.config.name = "Home"
api.lights = aiohue_v1.Lights(logger, {}, mock_request)
api.groups = aiohue_v1.Groups(logger, {}, mock_request)
api.sensors = aiohue_v1.Sensors(logger, {}, mock_request)
api.scenes = aiohue_v1.Scenes(logger, {}, mock_request)
return api
@pytest.fixture(scope="package")
def v2_resources_test_data() -> JsonArrayType:
"""Load V2 resources mock data."""
return load_json_array_fixture("hue/v2_resources.json")
def create_mock_api_v2() -> Mock:
"""Create a mock V2 API."""
api = Mock(spec=aiohue_v2.HueBridgeV2)
api.initialize = AsyncMock()
api.mock_requests = []
api.logger = logging.getLogger(__name__)
api.config = aiohue_v2.ConfigController(api)
api.events = aiohue_v2.EventStream(api)
api.devices = aiohue_v2.DevicesController(api)
api.lights = aiohue_v2.LightsController(api)
api.sensors = aiohue_v2.SensorsController(api)
api.groups = aiohue_v2.GroupsController(api)
api.scenes = aiohue_v2.ScenesController(api)
async def mock_request(method, path, **kwargs):
kwargs["method"] = method
kwargs["path"] = path
api.mock_requests.append(kwargs)
return kwargs.get("json")
api.request = mock_request
async def load_test_data(data: list[dict[str, Any]]):
"""Load test data into controllers."""
# append default bridge if none explicitly given in test data
if not any(x for x in data if x["type"] == "bridge"):
data.append(FAKE_BRIDGE)
data.append(FAKE_BRIDGE_DEVICE)
await asyncio.gather(
api.config.initialize(data),
api.devices.initialize(data),
api.lights.initialize(data),
api.scenes.initialize(data),
api.sensors.initialize(data),
api.groups.initialize(data),
)
def emit_event(event_type, data):
"""Emit an event from a (hue resource) dict."""
api.events.emit(EventType(event_type), data)
api.load_test_data = load_test_data
api.emit_event = emit_event
# mock context manager too
api.__aenter__ = AsyncMock(return_value=api)
api.__aexit__ = AsyncMock()
return api
@pytest.fixture
def mock_bridge_v1(hass: HomeAssistant) -> Mock:
"""Mock a Hue bridge with V1 api."""
return create_mock_bridge(hass, api_version=1)
@pytest.fixture
def mock_bridge_v2(hass: HomeAssistant) -> Mock:
"""Mock a Hue bridge with V2 api."""
return create_mock_bridge(hass, api_version=2)
@pytest.fixture
def mock_config_entry_v1() -> MockConfigEntry:
"""Mock a config entry for a Hue V1 bridge."""
return create_config_entry(api_version=1)
@pytest.fixture
def mock_config_entry_v2() -> MockConfigEntry:
"""Mock a config entry."""
return create_config_entry(api_version=2)
def create_config_entry(
api_version: int = 1, host: str = "mock-host"
) -> MockConfigEntry:
"""Mock a config entry for a Hue bridge."""
return MockConfigEntry(
domain=hue.DOMAIN,
title=f"Mock bridge {api_version}",
data={"host": host, "api_version": api_version, "api_key": ""},
)
async def setup_component(hass: HomeAssistant) -> None:
"""Mock setup Hue component."""
with patch.object(hue, "async_setup_entry", return_value=True):
assert (
await async_setup_component(
hass,
hue.DOMAIN,
{},
)
is True
)
async def setup_bridge(
hass: HomeAssistant, mock_bridge: Mock, config_entry: MockConfigEntry
) -> None:
"""Load the Hue integration with the provided bridge."""
mock_bridge.config_entry = config_entry
with patch.object(
hue.migration, "is_v2_bridge", return_value=mock_bridge.api_version == 2
):
config_entry.add_to_hass(hass)
with patch("homeassistant.components.hue.HueBridge", return_value=mock_bridge):
await hass.config_entries.async_setup(config_entry.entry_id)
async def setup_platform(
hass: HomeAssistant,
mock_bridge: Mock,
platforms: list[Platform] | tuple[Platform] | Platform,
hostname: str | None = None,
) -> None:
"""Load the Hue integration with the provided bridge for given platform(s)."""
if not isinstance(platforms, (list, tuple)):
platforms = [platforms]
if hostname is None:
hostname = "mock-host"
hass.config.components.add(hue.DOMAIN)
config_entry = create_config_entry(
api_version=mock_bridge.api_version, host=hostname
)
mock_bridge.config_entry = config_entry
hass.data[hue.DOMAIN] = {config_entry.entry_id: mock_bridge}
# simulate a full setup by manually adding the bridge config entry
await setup_bridge(hass, mock_bridge, config_entry)
assert await async_setup_component(hass, hue.DOMAIN, {}) is True
await hass.async_block_till_done()
config_entry.mock_state(hass, ConfigEntryState.LOADED)
await hass.config_entries.async_forward_entry_setups(config_entry, platforms)
# and make sure it completes before going further
await hass.async_block_till_done()