Skip to content

Commit c2eb1e8

Browse files
committed
address allens comments
1 parent d375eb8 commit c2eb1e8

3 files changed

Lines changed: 81 additions & 78 deletions

File tree

roborock/devices/traits/v1/rooms.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import logging
44
from dataclasses import dataclass
5-
from functools import cached_property
65

76
from roborock.data import HomeData, HomeDataRoom, NamedRoomMapping, RoborockBase
87
from roborock.devices.traits.v1 import common
@@ -90,17 +89,16 @@ def __init__(self, home_data: HomeData, device_uid: str, web_api: UserWebApiClie
9089
super().__init__()
9190
self._home_data = home_data
9291
self._device_uid = device_uid
92+
self._shared_device_uid = next(
93+
(device.duid for device in home_data.received_devices if device.duid == device_uid), None
94+
)
9395
self._web_api = web_api
9496
self._discovered_iot_ids: set[str] = set()
95-
self._shared_room_names: dict[str, str] = {}
96-
97-
@cached_property
98-
def _is_shared(self) -> bool:
99-
return any(d.duid == self._device_uid for d in self._home_data.received_devices)
97+
self._room_names: dict[str, str] = dict(home_data.rooms_name_map)
10098

10199
@property
102100
def _room_name_map(self) -> dict[str, str]:
103-
return {**self._home_data.rooms_name_map, **self._shared_room_names}
101+
return self._room_names
104102

105103
async def refresh(self) -> None:
106104
"""Refresh room mappings and backfill unknown room names from the web API."""
@@ -120,9 +118,8 @@ async def refresh(self) -> None:
120118
_LOGGER.debug("Refreshing room list to discover new room names")
121119
if updated_rooms := await self._refresh_rooms():
122120
_LOGGER.debug("Updating rooms: %s", list(updated_rooms))
123-
if self._is_shared:
124-
self._shared_room_names = {room.iot_id: room.name for room in updated_rooms}
125-
else:
121+
self._room_names = {room.iot_id: room.name for room in updated_rooms}
122+
if self._shared_device_uid is None:
126123
self._home_data.rooms = updated_rooms
127124
self._discovered_iot_ids.update(new_iot_ids)
128125
try:
@@ -141,8 +138,11 @@ async def refresh(self) -> None:
141138
async def _refresh_rooms(self) -> list[HomeDataRoom]:
142139
"""Fetch the latest rooms from the web API."""
143140
try:
144-
if self._is_shared:
145-
return await self._web_api.get_shared_device_rooms(self._device_uid)
141+
if self._shared_device_uid is not None:
142+
rooms_by_id = {room.iot_id: room for room in self._home_data.rooms}
143+
shared_rooms = await self._web_api.get_shared_device_rooms(self._shared_device_uid)
144+
rooms_by_id.update({room.iot_id: room for room in shared_rooms})
145+
return list(rooms_by_id.values())
146146
return await self._web_api.get_rooms()
147147
except Exception:
148148
_LOGGER.debug("Failed to fetch rooms from web API", exc_info=True)

tests/devices/traits/v1/fixtures.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,28 @@ def web_api_client_fixture() -> AsyncMock:
4646
return AsyncMock()
4747

4848

49+
@pytest.fixture(name="home_data")
50+
def home_data_fixture(request: pytest.FixtureRequest) -> HomeData:
51+
"""Fixture to provide HomeData, optionally overridden via indirect parametrization."""
52+
return deepcopy(getattr(request, "param", HOME_DATA))
53+
54+
4955
@pytest.fixture(autouse=True, name="roborock_cache")
5056
def roborock_cache_fixture() -> Cache:
5157
"""Fixture to provide a NoCache instance for tests."""
5258
return InMemoryCache()
5359

5460

5561
@pytest.fixture(autouse=True, name="device_cache")
56-
def device_cache_fixture(roborock_cache: Cache) -> DeviceCache:
62+
def device_cache_fixture(roborock_cache: Cache, home_data: HomeData) -> DeviceCache:
5763
"""Fixture to provide a DeviceCache instance for tests."""
58-
return DeviceCache(HOME_DATA.devices[0].duid, roborock_cache)
64+
return DeviceCache(home_data.get_all_devices()[0].duid, roborock_cache)
5965

6066

6167
@pytest.fixture(name="device_info")
62-
def device_info_fixture() -> HomeDataDevice:
68+
def device_info_fixture(home_data: HomeData) -> HomeDataDevice:
6369
"""Fixture to provide a DeviceInfo instance for tests."""
64-
return HOME_DATA.devices[0]
70+
return home_data.get_all_devices()[0]
6571

6672

6773
@pytest.fixture(name="products")
@@ -79,6 +85,7 @@ def device_fixture(
7985
web_api_client: AsyncMock,
8086
device_cache: DeviceCache,
8187
device_info: HomeDataDevice,
88+
home_data: HomeData,
8289
products: list[HomeDataProduct],
8390
) -> RoborockDevice:
8491
"""Fixture to set up the device for tests."""
@@ -90,7 +97,7 @@ def device_fixture(
9097
trait=v1.create(
9198
device_info.duid,
9299
product,
93-
deepcopy(HOME_DATA),
100+
home_data,
94101
mock_rpc_channel,
95102
mock_mqtt_rpc_channel,
96103
mock_map_rpc_channel,

tests/devices/traits/v1/test_rooms.py

Lines changed: 57 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
"""Tests for the RoomMapping related functionality."""
22

3+
from copy import deepcopy
34
from typing import Any
45
from unittest.mock import AsyncMock
56

67
import pytest
78

8-
from roborock.data.containers import HomeDataRoom, NamedRoomMapping
9+
from roborock.data.containers import HomeData, HomeDataRoom, NamedRoomMapping
910
from roborock.devices.device import RoborockDevice
1011
from roborock.devices.traits.v1.rooms import RoomsTrait
1112
from roborock.devices.traits.v1.status import StatusTrait
1213
from roborock.roborock_typing import RoborockCommand
14+
from tests.devices.traits.v1.fixtures import HOME_DATA
1315

1416

1517
@pytest.fixture
@@ -79,29 +81,25 @@ async def test_refresh_unknown_room_names_overwrites_home_data(
7981
mock_rpc_channel: AsyncMock,
8082
) -> None:
8183
"""Test web rooms are used to refresh home data for missing iot ids."""
82-
original_rooms = list(rooms_trait._home_data.rooms or ())
83-
try:
84-
web_api_client.get_rooms.return_value = [
85-
HomeDataRoom(id=2362048, name="Living Room"),
86-
HomeDataRoom(id=2362044, name="Example room 2"),
87-
HomeDataRoom(id=2362041, name="Example room 3"),
88-
HomeDataRoom(id=9999999, name="Office"),
89-
]
90-
91-
room_mapping_data = [[16, "2362048"], [17, "9999999"]]
92-
mock_rpc_channel.send_command.side_effect = [room_mapping_data]
84+
web_api_client.get_rooms.return_value = [
85+
HomeDataRoom(id=2362048, name="Living Room"),
86+
HomeDataRoom(id=2362044, name="Example room 2"),
87+
HomeDataRoom(id=2362041, name="Example room 3"),
88+
HomeDataRoom(id=9999999, name="Office"),
89+
]
90+
91+
room_mapping_data = [[16, "2362048"], [17, "9999999"]]
92+
mock_rpc_channel.send_command.side_effect = [room_mapping_data]
9393

94-
await rooms_trait.refresh()
94+
await rooms_trait.refresh()
9595

96-
assert rooms_trait.rooms
97-
assert rooms_trait.rooms[0] == NamedRoomMapping(segment_id=16, iot_id="2362048", raw_name="Living Room")
98-
assert rooms_trait.rooms[1] == NamedRoomMapping(segment_id=17, iot_id="9999999", raw_name="Office")
96+
assert rooms_trait.rooms
97+
assert rooms_trait.rooms[0] == NamedRoomMapping(segment_id=16, iot_id="2362048", raw_name="Living Room")
98+
assert rooms_trait.rooms[1] == NamedRoomMapping(segment_id=17, iot_id="9999999", raw_name="Office")
9999

100-
home_data_rooms = {str(room.id): room.name for room in rooms_trait._home_data.rooms or ()}
101-
assert home_data_rooms["2362048"] == "Living Room"
102-
assert home_data_rooms["9999999"] == "Office"
103-
finally:
104-
rooms_trait._home_data.rooms = original_rooms
100+
home_data_rooms = {str(room.id): room.name for room in rooms_trait._home_data.rooms or ()}
101+
assert home_data_rooms["2362048"] == "Living Room"
102+
assert home_data_rooms["9999999"] == "Office"
105103

106104

107105
async def test_refresh_unknown_room_names_web_api_called_once(
@@ -110,25 +108,21 @@ async def test_refresh_unknown_room_names_web_api_called_once(
110108
mock_rpc_channel: AsyncMock,
111109
) -> None:
112110
"""Test unknown room IDs trigger one web lookup per iot_id."""
113-
original_rooms = list(rooms_trait._home_data.rooms or ())
114-
try:
115-
web_api_client.get_rooms.return_value = [
116-
HomeDataRoom(id=9999911, name="Living Room"),
117-
]
111+
web_api_client.get_rooms.return_value = [
112+
HomeDataRoom(id=9999911, name="Living Room"),
113+
]
118114

119-
room_mapping_data = [[16, "9999911"]]
120-
mock_rpc_channel.send_command.side_effect = [room_mapping_data, room_mapping_data]
115+
room_mapping_data = [[16, "9999911"]]
116+
mock_rpc_channel.send_command.side_effect = [room_mapping_data, room_mapping_data]
121117

122-
await rooms_trait.refresh()
123-
assert rooms_trait.rooms
124-
assert rooms_trait.rooms[0].name == "Living Room"
118+
await rooms_trait.refresh()
119+
assert rooms_trait.rooms
120+
assert rooms_trait.rooms[0].name == "Living Room"
125121

126-
await rooms_trait.refresh()
127-
assert rooms_trait.rooms
128-
assert rooms_trait.rooms[0].name == "Living Room"
129-
web_api_client.get_rooms.assert_called_once()
130-
finally:
131-
rooms_trait._home_data.rooms = original_rooms
122+
await rooms_trait.refresh()
123+
assert rooms_trait.rooms
124+
assert rooms_trait.rooms[0].name == "Living Room"
125+
web_api_client.get_rooms.assert_called_once()
132126

133127

134128
async def test_refresh_unknown_room_names_unresolved_uses_room_fallback(
@@ -209,32 +203,34 @@ async def test_refresh_unknown_room_names_failure_falls_back_to_room_segment_id(
209203
web_api_client.get_rooms.assert_called_once()
210204

211205

212-
async def test_refresh_shared_room_names_use_shared_device_rooms_without_mutating_home_data(
206+
def _build_shared_home_data() -> HomeData:
207+
home_data = deepcopy(HOME_DATA)
208+
home_data.received_devices = [home_data.devices.pop(0)]
209+
return home_data
210+
211+
212+
@pytest.mark.parametrize("home_data", [_build_shared_home_data()], indirect=True)
213+
async def test_refresh_shared_room_names_use_shared_device_rooms(
213214
rooms_trait: RoomsTrait,
215+
home_data: HomeData,
214216
web_api_client: AsyncMock,
215217
mock_rpc_channel: AsyncMock,
216218
) -> None:
217219
"""Test shared devices resolve room names via the shared-device room list."""
218-
original_rooms = list(rooms_trait._home_data.rooms or ())
219-
try:
220-
# Mark the device as shared by adding it to received_devices
221-
device = next(d for d in rooms_trait._home_data.devices if d.duid == rooms_trait._device_uid)
222-
rooms_trait._home_data.received_devices = [device]
223-
rooms_trait._home_data.devices = []
224-
225-
web_api_client.get_shared_device_rooms.return_value = [
226-
HomeDataRoom(id=9999999, name="Office"),
227-
]
228-
room_mapping_data = [[16, "2362048"], [17, "9999999"]]
229-
mock_rpc_channel.send_command.side_effect = [room_mapping_data]
230-
231-
await rooms_trait.refresh()
232-
233-
assert rooms_trait.rooms
234-
assert rooms_trait.rooms[0] == NamedRoomMapping(segment_id=16, iot_id="2362048", raw_name="Example room 1")
235-
assert rooms_trait.rooms[1] == NamedRoomMapping(segment_id=17, iot_id="9999999", raw_name="Office")
236-
assert rooms_trait._home_data.rooms == original_rooms
237-
web_api_client.get_shared_device_rooms.assert_called_once_with(rooms_trait._device_uid)
238-
web_api_client.get_rooms.assert_not_called()
239-
finally:
240-
rooms_trait._home_data.rooms = original_rooms
220+
assert home_data.received_devices
221+
assert not rooms_trait.rooms
222+
223+
web_api_client.get_shared_device_rooms.return_value = [
224+
HomeDataRoom(id=9999999, name="Office"),
225+
]
226+
room_mapping_data = [[16, "2362048"], [17, "9999999"]]
227+
mock_rpc_channel.send_command.side_effect = [room_mapping_data]
228+
229+
await rooms_trait.refresh()
230+
231+
assert rooms_trait.rooms == [
232+
NamedRoomMapping(segment_id=16, iot_id="2362048", raw_name="Example room 1"),
233+
NamedRoomMapping(segment_id=17, iot_id="9999999", raw_name="Office"),
234+
]
235+
web_api_client.get_shared_device_rooms.assert_called_once_with(rooms_trait._device_uid)
236+
web_api_client.get_rooms.assert_not_called()

0 commit comments

Comments
 (0)