496 lines
17 KiB
Python
496 lines
17 KiB
Python
"""Test fixtures for home_connect."""
|
|
|
|
import asyncio
|
|
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
import copy
|
|
import time
|
|
from typing import Any, cast
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from aiohomeconnect.client import Client as HomeConnectClient
|
|
from aiohomeconnect.model import (
|
|
ArrayOfCommands,
|
|
ArrayOfEvents,
|
|
ArrayOfHomeAppliances,
|
|
ArrayOfOptions,
|
|
ArrayOfPrograms,
|
|
ArrayOfSettings,
|
|
ArrayOfStatus,
|
|
Event,
|
|
EventKey,
|
|
EventMessage,
|
|
EventType,
|
|
GetSetting,
|
|
HomeAppliance,
|
|
Option,
|
|
Program,
|
|
ProgramDefinition,
|
|
ProgramKey,
|
|
SettingKey,
|
|
)
|
|
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
|
from aiohomeconnect.model.program import EnumerateProgram
|
|
import pytest
|
|
|
|
from homeassistant.components.application_credentials import (
|
|
ClientCredential,
|
|
async_import_client_credential,
|
|
)
|
|
from homeassistant.components.home_connect.const import DOMAIN
|
|
from homeassistant.const import Platform
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from tests.common import MockConfigEntry, load_json_object_fixture
|
|
|
|
MOCK_APPLIANCES = ArrayOfHomeAppliances.from_dict(
|
|
load_json_object_fixture("home_connect/appliances.json")["data"]
|
|
)
|
|
MOCK_PROGRAMS: dict[str, Any] = load_json_object_fixture("home_connect/programs.json")
|
|
MOCK_SETTINGS: dict[str, Any] = load_json_object_fixture("home_connect/settings.json")
|
|
MOCK_STATUS = ArrayOfStatus.from_dict(
|
|
load_json_object_fixture("home_connect/status.json")["data"]
|
|
)
|
|
MOCK_AVAILABLE_COMMANDS: dict[str, Any] = load_json_object_fixture(
|
|
"home_connect/available_commands.json"
|
|
)
|
|
|
|
|
|
CLIENT_ID = "1234"
|
|
CLIENT_SECRET = "5678"
|
|
FAKE_ACCESS_TOKEN = "some-access-token"
|
|
FAKE_REFRESH_TOKEN = "some-refresh-token"
|
|
FAKE_AUTH_IMPL = "conftest-imported-cred"
|
|
|
|
SERVER_ACCESS_TOKEN = {
|
|
"refresh_token": "server-refresh-token",
|
|
"access_token": "server-access-token",
|
|
"type": "Bearer",
|
|
"expires_in": 60,
|
|
}
|
|
|
|
|
|
@pytest.fixture(name="token_expiration_time")
|
|
def mock_token_expiration_time() -> float:
|
|
"""Fixture for expiration time of the config entry auth token."""
|
|
return time.time() + 86400
|
|
|
|
|
|
@pytest.fixture(name="token_entry")
|
|
def mock_token_entry(token_expiration_time: float) -> dict[str, Any]:
|
|
"""Fixture for OAuth 'token' data for a ConfigEntry."""
|
|
return {
|
|
"refresh_token": FAKE_REFRESH_TOKEN,
|
|
"access_token": FAKE_ACCESS_TOKEN,
|
|
"type": "Bearer",
|
|
"expires_at": token_expiration_time,
|
|
}
|
|
|
|
|
|
@pytest.fixture(name="config_entry")
|
|
def mock_config_entry(token_entry: dict[str, Any]) -> MockConfigEntry:
|
|
"""Fixture for a config entry."""
|
|
return MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={
|
|
"auth_implementation": FAKE_AUTH_IMPL,
|
|
"token": token_entry,
|
|
},
|
|
minor_version=2,
|
|
)
|
|
|
|
|
|
@pytest.fixture(name="config_entry_v1_1")
|
|
def mock_config_entry_v1_1(token_entry: dict[str, Any]) -> MockConfigEntry:
|
|
"""Fixture for a config entry."""
|
|
return MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={
|
|
"auth_implementation": FAKE_AUTH_IMPL,
|
|
"token": token_entry,
|
|
},
|
|
minor_version=1,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_credentials(hass: HomeAssistant) -> None:
|
|
"""Fixture to setup credentials."""
|
|
assert await async_setup_component(hass, "application_credentials", {})
|
|
await async_import_client_credential(
|
|
hass,
|
|
DOMAIN,
|
|
ClientCredential(CLIENT_ID, CLIENT_SECRET),
|
|
FAKE_AUTH_IMPL,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def platforms() -> list[Platform]:
|
|
"""Fixture to specify platforms to test."""
|
|
return []
|
|
|
|
|
|
@pytest.fixture(name="integration_setup")
|
|
async def mock_integration_setup(
|
|
hass: HomeAssistant,
|
|
platforms: list[Platform],
|
|
config_entry: MockConfigEntry,
|
|
) -> Callable[[MagicMock], Awaitable[bool]]:
|
|
"""Fixture to set up the integration."""
|
|
config_entry.add_to_hass(hass)
|
|
|
|
async def run(client: MagicMock) -> bool:
|
|
with (
|
|
patch("homeassistant.components.home_connect.PLATFORMS", platforms),
|
|
patch(
|
|
"homeassistant.components.home_connect.HomeConnectClient"
|
|
) as client_mock,
|
|
):
|
|
client_mock.return_value = client
|
|
result = await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
return result
|
|
|
|
return run
|
|
|
|
|
|
def _get_specific_appliance_side_effect(ha_id: str) -> HomeAppliance:
|
|
"""Get specific appliance side effect."""
|
|
for appliance in copy.deepcopy(MOCK_APPLIANCES).homeappliances:
|
|
if appliance.ha_id == ha_id:
|
|
return appliance
|
|
raise HomeConnectApiError("error.key", "error description")
|
|
|
|
|
|
def _get_set_program_side_effect(
|
|
event_queue: asyncio.Queue[list[EventMessage]], event_key: EventKey
|
|
):
|
|
"""Set program side effect."""
|
|
|
|
async def set_program_side_effect(ha_id: str, *_, **kwargs) -> None:
|
|
await event_queue.put(
|
|
[
|
|
EventMessage(
|
|
ha_id,
|
|
EventType.NOTIFY,
|
|
ArrayOfEvents(
|
|
[
|
|
Event(
|
|
key=event_key,
|
|
raw_key=event_key.value,
|
|
timestamp=0,
|
|
level="",
|
|
handling="",
|
|
value=str(kwargs["program_key"]),
|
|
),
|
|
*[
|
|
Event(
|
|
key=(option_event := EventKey(option.key)),
|
|
raw_key=option_event.value,
|
|
timestamp=0,
|
|
level="",
|
|
handling="",
|
|
value=str(option.key),
|
|
)
|
|
for option in cast(
|
|
list[Option], kwargs.get("options", [])
|
|
)
|
|
],
|
|
]
|
|
),
|
|
),
|
|
]
|
|
)
|
|
|
|
return set_program_side_effect
|
|
|
|
|
|
def _get_set_setting_side_effect(
|
|
event_queue: asyncio.Queue[list[EventMessage]],
|
|
):
|
|
"""Set settings side effect."""
|
|
|
|
async def set_settings_side_effect(ha_id: str, *_, **kwargs) -> None:
|
|
event_key = EventKey(kwargs["setting_key"])
|
|
await event_queue.put(
|
|
[
|
|
EventMessage(
|
|
ha_id,
|
|
EventType.NOTIFY,
|
|
ArrayOfEvents(
|
|
[
|
|
Event(
|
|
key=event_key,
|
|
raw_key=event_key.value,
|
|
timestamp=0,
|
|
level="",
|
|
handling="",
|
|
value=kwargs["value"],
|
|
)
|
|
]
|
|
),
|
|
),
|
|
]
|
|
)
|
|
|
|
return set_settings_side_effect
|
|
|
|
|
|
def _get_set_program_options_side_effect(
|
|
event_queue: asyncio.Queue[list[EventMessage]],
|
|
):
|
|
"""Set programs side effect."""
|
|
|
|
async def set_program_options_side_effect(ha_id: str, *_, **kwargs) -> None:
|
|
await event_queue.put(
|
|
[
|
|
EventMessage(
|
|
ha_id,
|
|
EventType.NOTIFY,
|
|
ArrayOfEvents(
|
|
[
|
|
Event(
|
|
key=EventKey(option.key),
|
|
raw_key=option.key.value,
|
|
timestamp=0,
|
|
level="",
|
|
handling="",
|
|
value=option.value,
|
|
)
|
|
for option in (
|
|
cast(ArrayOfOptions, kwargs["array_of_options"]).options
|
|
if "array_of_options" in kwargs
|
|
else [
|
|
Option(
|
|
kwargs["option_key"],
|
|
kwargs["value"],
|
|
unit=kwargs["unit"],
|
|
)
|
|
]
|
|
)
|
|
]
|
|
),
|
|
),
|
|
]
|
|
)
|
|
|
|
return set_program_options_side_effect
|
|
|
|
|
|
async def _get_all_programs_side_effect(ha_id: str) -> ArrayOfPrograms:
|
|
"""Get all programs."""
|
|
appliance_type = next(
|
|
appliance
|
|
for appliance in MOCK_APPLIANCES.homeappliances
|
|
if appliance.ha_id == ha_id
|
|
).type
|
|
if appliance_type not in MOCK_PROGRAMS:
|
|
raise HomeConnectApiError("error.key", "error description")
|
|
|
|
return ArrayOfPrograms(
|
|
[
|
|
EnumerateProgram.from_dict(program)
|
|
for program in MOCK_PROGRAMS[appliance_type]["data"]["programs"]
|
|
],
|
|
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
|
|
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
|
|
)
|
|
|
|
|
|
async def _get_settings_side_effect(ha_id: str) -> ArrayOfSettings:
|
|
"""Get settings."""
|
|
return ArrayOfSettings.from_dict(
|
|
MOCK_SETTINGS.get(
|
|
next(
|
|
appliance
|
|
for appliance in MOCK_APPLIANCES.homeappliances
|
|
if appliance.ha_id == ha_id
|
|
).type,
|
|
{},
|
|
).get("data", {"settings": []})
|
|
)
|
|
|
|
|
|
async def _get_setting_side_effect(ha_id: str, setting_key: SettingKey):
|
|
"""Get setting."""
|
|
for appliance in MOCK_APPLIANCES.homeappliances:
|
|
if appliance.ha_id == ha_id:
|
|
settings = MOCK_SETTINGS.get(
|
|
next(
|
|
appliance
|
|
for appliance in MOCK_APPLIANCES.homeappliances
|
|
if appliance.ha_id == ha_id
|
|
).type,
|
|
{},
|
|
).get("data", {"settings": []})
|
|
for setting_dict in cast(list[dict], settings["settings"]):
|
|
if setting_dict["key"] == setting_key:
|
|
return GetSetting.from_dict(setting_dict)
|
|
raise HomeConnectApiError("error.key", "error description")
|
|
|
|
|
|
async def _get_available_commands_side_effect(ha_id: str) -> ArrayOfCommands:
|
|
"""Get available commands."""
|
|
for appliance in MOCK_APPLIANCES.homeappliances:
|
|
if appliance.ha_id == ha_id and appliance.type in MOCK_AVAILABLE_COMMANDS:
|
|
return ArrayOfCommands.from_dict(MOCK_AVAILABLE_COMMANDS[appliance.type])
|
|
raise HomeConnectApiError("error.key", "error description")
|
|
|
|
|
|
@pytest.fixture(name="client")
|
|
def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
|
"""Fixture to mock Client from HomeConnect."""
|
|
|
|
mock = MagicMock(
|
|
autospec=HomeConnectClient,
|
|
)
|
|
|
|
event_queue: asyncio.Queue[list[EventMessage]] = asyncio.Queue()
|
|
|
|
async def add_events(events: list[EventMessage]) -> None:
|
|
await event_queue.put(events)
|
|
|
|
mock.add_events = add_events
|
|
|
|
async def set_program_option_side_effect(ha_id: str, *_, **kwargs) -> None:
|
|
event_key = EventKey(kwargs["option_key"])
|
|
await event_queue.put(
|
|
[
|
|
EventMessage(
|
|
ha_id,
|
|
EventType.NOTIFY,
|
|
ArrayOfEvents(
|
|
[
|
|
Event(
|
|
key=event_key,
|
|
raw_key=event_key.value,
|
|
timestamp=0,
|
|
level="",
|
|
handling="",
|
|
value=kwargs["value"],
|
|
)
|
|
]
|
|
),
|
|
),
|
|
]
|
|
)
|
|
|
|
async def stream_all_events() -> AsyncGenerator[EventMessage]:
|
|
"""Mock stream_all_events."""
|
|
while True:
|
|
for event in await event_queue.get():
|
|
yield event
|
|
|
|
mock.get_home_appliances = AsyncMock(return_value=copy.deepcopy(MOCK_APPLIANCES))
|
|
mock.get_specific_appliance = AsyncMock(
|
|
side_effect=_get_specific_appliance_side_effect
|
|
)
|
|
mock.stream_all_events = stream_all_events
|
|
mock.start_program = AsyncMock(
|
|
side_effect=_get_set_program_side_effect(
|
|
event_queue, EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM
|
|
)
|
|
)
|
|
mock.set_selected_program = AsyncMock(
|
|
side_effect=_get_set_program_side_effect(
|
|
event_queue, EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM
|
|
),
|
|
)
|
|
mock.stop_program = AsyncMock()
|
|
mock.set_active_program_option = AsyncMock(
|
|
side_effect=_get_set_program_options_side_effect(event_queue),
|
|
)
|
|
mock.set_active_program_options = AsyncMock(
|
|
side_effect=_get_set_program_options_side_effect(event_queue),
|
|
)
|
|
mock.set_selected_program_option = AsyncMock(
|
|
side_effect=_get_set_program_options_side_effect(event_queue),
|
|
)
|
|
mock.set_selected_program_options = AsyncMock(
|
|
side_effect=_get_set_program_options_side_effect(event_queue),
|
|
)
|
|
mock.set_setting = AsyncMock(
|
|
side_effect=_get_set_setting_side_effect(event_queue),
|
|
)
|
|
mock.get_settings = AsyncMock(side_effect=_get_settings_side_effect)
|
|
mock.get_setting = AsyncMock(side_effect=_get_setting_side_effect)
|
|
mock.get_status = AsyncMock(return_value=copy.deepcopy(MOCK_STATUS))
|
|
mock.get_all_programs = AsyncMock(side_effect=_get_all_programs_side_effect)
|
|
mock.get_available_commands = AsyncMock(
|
|
side_effect=_get_available_commands_side_effect
|
|
)
|
|
mock.put_command = AsyncMock()
|
|
mock.get_available_program = AsyncMock(
|
|
return_value=ProgramDefinition(ProgramKey.UNKNOWN, options=[])
|
|
)
|
|
mock.get_active_program_options = AsyncMock(return_value=ArrayOfOptions([]))
|
|
mock.get_selected_program_options = AsyncMock(return_value=ArrayOfOptions([]))
|
|
mock.set_active_program_option = AsyncMock(
|
|
side_effect=set_program_option_side_effect
|
|
)
|
|
mock.set_selected_program_option = AsyncMock(
|
|
side_effect=set_program_option_side_effect
|
|
)
|
|
|
|
mock.side_effect = mock
|
|
return mock
|
|
|
|
|
|
@pytest.fixture(name="client_with_exception")
|
|
def mock_client_with_exception(request: pytest.FixtureRequest) -> MagicMock:
|
|
"""Fixture to mock Client from HomeConnect that raise exceptions."""
|
|
mock = MagicMock(
|
|
autospec=HomeConnectClient,
|
|
)
|
|
|
|
exception = HomeConnectError()
|
|
if hasattr(request, "param") and request.param:
|
|
exception = request.param
|
|
|
|
event_queue: asyncio.Queue[list[EventMessage]] = asyncio.Queue()
|
|
|
|
async def stream_all_events() -> AsyncGenerator[EventMessage]:
|
|
"""Mock stream_all_events."""
|
|
while True:
|
|
for event in await event_queue.get():
|
|
yield event
|
|
|
|
mock.get_home_appliances = AsyncMock(return_value=copy.deepcopy(MOCK_APPLIANCES))
|
|
mock.stream_all_events = stream_all_events
|
|
|
|
mock.start_program = AsyncMock(side_effect=exception)
|
|
mock.stop_program = AsyncMock(side_effect=exception)
|
|
mock.set_selected_program = AsyncMock(side_effect=exception)
|
|
mock.stop_program = AsyncMock(side_effect=exception)
|
|
mock.set_active_program_option = AsyncMock(side_effect=exception)
|
|
mock.set_active_program_options = AsyncMock(side_effect=exception)
|
|
mock.set_selected_program_option = AsyncMock(side_effect=exception)
|
|
mock.set_selected_program_options = AsyncMock(side_effect=exception)
|
|
mock.set_setting = AsyncMock(side_effect=exception)
|
|
mock.get_settings = AsyncMock(side_effect=exception)
|
|
mock.get_setting = AsyncMock(side_effect=exception)
|
|
mock.get_status = AsyncMock(side_effect=exception)
|
|
mock.get_all_programs = AsyncMock(side_effect=exception)
|
|
mock.get_available_commands = AsyncMock(side_effect=exception)
|
|
mock.put_command = AsyncMock(side_effect=exception)
|
|
mock.get_available_program = AsyncMock(side_effect=exception)
|
|
mock.get_active_program_options = AsyncMock(side_effect=exception)
|
|
mock.get_selected_program_options = AsyncMock(side_effect=exception)
|
|
mock.set_active_program_option = AsyncMock(side_effect=exception)
|
|
mock.set_selected_program_option = AsyncMock(side_effect=exception)
|
|
|
|
return mock
|
|
|
|
|
|
@pytest.fixture(name="appliance_ha_id")
|
|
def mock_appliance_ha_id(request: pytest.FixtureRequest) -> str:
|
|
"""Fixture to mock Appliance."""
|
|
app = "Washer"
|
|
if hasattr(request, "param") and request.param:
|
|
app = request.param
|
|
for appliance in MOCK_APPLIANCES.homeappliances:
|
|
if appliance.type == app:
|
|
return appliance.ha_id
|
|
raise ValueError(f"Appliance {app} not found")
|