Allen Porter 4463e4c42b
Move roborock unique id to be based on roborock userid instead of email (#141337)
* Move roborock unique id to be based on roborock userid instead of email

* Remove unnecessary data update

* Update tests

* Add tests coverage for removal of config entry

* Use config entry migration

* Remove unused fixtues

* Remove unnecessary logging
2025-03-30 16:04:28 +02:00

262 lines
8.6 KiB
Python

"""Global fixtures for Roborock integration."""
from collections.abc import Generator
from copy import deepcopy
import pathlib
import tempfile
from typing import Any
from unittest.mock import Mock, patch
import pytest
from roborock import RoborockCategory, RoomMapping
from roborock.code_mappings import DyadError, RoborockDyadStateCode, ZeoError, ZeoState
from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
from roborock.version_a01_apis import RoborockMqttClientA01
from homeassistant.components.roborock.const import (
CONF_BASE_URL,
CONF_USER_DATA,
DOMAIN,
)
from homeassistant.const import CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from .mock_data import (
BASE_URL,
HOME_DATA,
MAP_DATA,
MULTI_MAP_LIST,
NETWORK_INFO,
PROP,
ROBOROCK_RRUID,
SCENES,
USER_DATA,
USER_EMAIL,
)
from tests.common import MockConfigEntry
class A01Mock(RoborockMqttClientA01):
"""A class to mock the A01 client."""
def __init__(self, user_data, device_info, category) -> None:
"""Initialize the A01Mock."""
super().__init__(user_data, device_info, category)
if category == RoborockCategory.WET_DRY_VAC:
self.protocol_responses = {
RoborockDyadDataProtocol.STATUS: RoborockDyadStateCode.drying.name,
RoborockDyadDataProtocol.POWER: 100,
RoborockDyadDataProtocol.MESH_LEFT: 111,
RoborockDyadDataProtocol.BRUSH_LEFT: 222,
RoborockDyadDataProtocol.ERROR: DyadError.none.name,
RoborockDyadDataProtocol.TOTAL_RUN_TIME: 213,
}
elif category == RoborockCategory.WASHING_MACHINE:
self.protocol_responses: list[RoborockZeoProtocol] = {
RoborockZeoProtocol.STATE: ZeoState.drying.name,
RoborockZeoProtocol.COUNTDOWN: 0,
RoborockZeoProtocol.WASHING_LEFT: 253,
RoborockZeoProtocol.ERROR: ZeoError.none.name,
}
async def update_values(
self, dyad_data_protocols: list[RoborockDyadDataProtocol | RoborockZeoProtocol]
):
"""Update values with a predetermined response that can be overridden."""
return {prot: self.protocol_responses[prot] for prot in dyad_data_protocols}
@pytest.fixture(name="bypass_api_client_fixture")
def bypass_api_client_fixture() -> None:
"""Skip calls to the API client."""
with (
patch(
"homeassistant.components.roborock.RoborockApiClient.get_home_data_v2",
return_value=HOME_DATA,
),
patch(
"homeassistant.components.roborock.RoborockApiClient.get_scenes",
return_value=SCENES,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.load_multi_map"
),
):
yield
@pytest.fixture(name="bypass_api_fixture")
def bypass_api_fixture(bypass_api_client_fixture: Any) -> None:
"""Skip calls to the API."""
with (
patch("homeassistant.components.roborock.RoborockMqttClientV1.async_connect"),
patch("homeassistant.components.roborock.RoborockMqttClientV1._send_command"),
patch(
"homeassistant.components.roborock.coordinator.RoborockMqttClientV1._send_command"
),
patch(
"homeassistant.components.roborock.RoborockMqttClientV1.get_networking",
return_value=NETWORK_INFO,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.get_prop",
return_value=PROP,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockMqttClientV1.get_multi_maps_list",
return_value=MULTI_MAP_LIST,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.get_multi_maps_list",
return_value=MULTI_MAP_LIST,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
return_value=MAP_DATA,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.send_message"
),
patch("homeassistant.components.roborock.RoborockMqttClientV1._wait_response"),
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1._wait_response"
),
patch(
"roborock.version_1_apis.AttributeCache.async_value",
),
patch(
"roborock.version_1_apis.AttributeCache.value",
),
patch(
"homeassistant.components.roborock.coordinator.MAP_SLEEP",
0,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.get_room_mapping",
return_value=[
RoomMapping(16, "2362048"),
RoomMapping(17, "2362044"),
RoomMapping(18, "2362041"),
],
),
patch(
"homeassistant.components.roborock.coordinator.RoborockMqttClientV1.get_room_mapping",
return_value=[
RoomMapping(16, "2362048"),
RoomMapping(17, "2362044"),
RoomMapping(18, "2362041"),
],
),
patch(
"homeassistant.components.roborock.coordinator.RoborockMqttClientV1.get_map_v1",
return_value=b"123",
),
patch(
"homeassistant.components.roborock.coordinator.RoborockClientA01",
A01Mock,
),
patch("homeassistant.components.roborock.RoborockMqttClientA01", A01Mock),
):
yield
@pytest.fixture(name="send_message_side_effect")
def send_message_side_effect_fixture() -> Any:
"""Fixture to return a side effect for the send_message method."""
return None
@pytest.fixture(name="mock_send_message")
def mock_send_message_fixture(send_message_side_effect: Any) -> Mock:
"""Fixture to mock the send_message method."""
with patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1._send_command",
side_effect=send_message_side_effect,
) as mock_send_message:
yield mock_send_message
@pytest.fixture
def bypass_api_fixture_v1_only(bypass_api_fixture) -> None:
"""Bypass api for tests that require only having v1 devices."""
home_data_copy = deepcopy(HOME_DATA)
home_data_copy.received_devices = []
with patch(
"homeassistant.components.roborock.RoborockApiClient.get_home_data_v2",
return_value=home_data_copy,
):
yield
@pytest.fixture(name="config_entry_data")
def config_entry_data_fixture() -> dict[str, Any]:
"""Fixture that returns the unique id for the config entry."""
return {
CONF_USERNAME: USER_EMAIL,
CONF_USER_DATA: USER_DATA.as_dict(),
CONF_BASE_URL: BASE_URL,
}
@pytest.fixture
def mock_roborock_entry(
hass: HomeAssistant, config_entry_data: dict[str, Any]
) -> MockConfigEntry:
"""Create a Roborock Entry that has not been setup."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
title=USER_EMAIL,
data=config_entry_data,
unique_id=ROBOROCK_RRUID,
version=1,
minor_version=2,
)
mock_entry.add_to_hass(hass)
return mock_entry
@pytest.fixture(name="platforms")
def mock_platforms() -> list[Platform]:
"""Fixture to specify platforms to test."""
return []
@pytest.fixture(autouse=True)
async def mock_patforms_fixture(
hass: HomeAssistant,
platforms: list[Platform],
) -> Generator[None]:
"""Set up the Roborock platform."""
with patch("homeassistant.components.roborock.PLATFORMS", platforms):
yield
@pytest.fixture
async def setup_entry(
hass: HomeAssistant,
bypass_api_fixture,
mock_roborock_entry: MockConfigEntry,
) -> Generator[MockConfigEntry]:
"""Set up the Roborock platform."""
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
await hass.async_block_till_done()
return mock_roborock_entry
@pytest.fixture(autouse=True, name="storage_path")
async def storage_path_fixture(
hass: HomeAssistant,
) -> Generator[pathlib.Path]:
"""Test cleanup, remove any map storage persisted during the test."""
with tempfile.TemporaryDirectory() as tmp_path:
def get_storage_path(_: HomeAssistant, entry_id: str) -> pathlib.Path:
return pathlib.Path(tmp_path) / entry_id
with patch(
"homeassistant.components.roborock.roborock_storage._storage_path_prefix",
new=get_storage_path,
):
yield pathlib.Path(tmp_path)