Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions simplipy/device/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ def name(self) -> str:
Returns:
The device name.
"""
return cast(str, self._system.sensor_data[self._serial]["name"])
return cast(
str, self._system.sensor_data[self._serial].get("name", self._serial)
)

@property
def serial(self) -> str:
Expand All @@ -93,7 +95,9 @@ def serial(self) -> str:
Returns:
The device serial number.
"""
return cast(str, self._system.sensor_data[self._serial]["serial"])
return cast(
str, self._system.sensor_data[self._serial].get("serial", self._serial)
)

@property
def type(self) -> DeviceTypes:
Expand Down Expand Up @@ -146,7 +150,9 @@ def error(self) -> bool:
"""
return cast(
bool,
self._system.sensor_data[self._serial]["status"].get("malfunction", False),
self._system.sensor_data[self._serial]
.get("status", {})
.get("malfunction", False),
)

@property
Expand All @@ -156,7 +162,12 @@ def low_battery(self) -> bool:
Returns:
The device's low battery status.
"""
return cast(bool, self._system.sensor_data[self._serial]["flags"]["lowBattery"])
return cast(
bool,
self._system.sensor_data[self._serial]
.get("flags", {})
.get("lowBattery", False),
)

@property
def offline(self) -> bool:
Expand All @@ -165,7 +176,12 @@ def offline(self) -> bool:
Returns:
The device's offline status.
"""
return cast(bool, self._system.sensor_data[self._serial]["flags"]["offline"])
return cast(
bool,
self._system.sensor_data[self._serial]
.get("flags", {})
.get("offline", False),
)

@property
def settings(self) -> dict[str, Any]:
Expand All @@ -176,7 +192,9 @@ def settings(self) -> dict[str, Any]:
Returns:
A settings dictionary.
"""
return cast(dict[str, Any], self._system.sensor_data[self._serial]["setting"])
return cast(
dict[str, Any], self._system.sensor_data[self._serial].get("setting", {})
)

def as_dict(self) -> dict[str, Any]:
"""Return dictionary version of this device.
Expand Down
22 changes: 22 additions & 0 deletions tests/sensor/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,25 @@ async def test_properties_base(
assert sensor.type == DeviceTypes.KEYPAD

aresponses.assert_plan_strictly_followed()


@pytest.mark.asyncio
async def test_properties_base_missing_name_falls_back_to_serial(
aresponses: ResponsesMockServer,
authenticated_simplisafe_server_v3: ResponsesMockServer,
) -> None:
"""Test that name falls back to serial when the API omits the 'name' field.

Regression test for home-assistant/core#172599.
"""
async with authenticated_simplisafe_server_v3, aiohttp.ClientSession() as session:
simplisafe = await API.async_from_auth(
TEST_AUTHORIZATION_CODE, TEST_CODE_VERIFIER, session=session
)
systems = await simplisafe.async_get_systems()
system = systems[TEST_SYSTEM_ID]
sensor = system.sensors["825"]
del system.sensor_data["825"]["name"]
assert sensor.name == "825"

aresponses.assert_plan_strictly_followed()
30 changes: 30 additions & 0 deletions tests/sensor/test_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,33 @@ async def test_properties_v3(
assert siren.temperature == 42

aresponses.assert_plan_strictly_followed()


@pytest.mark.asyncio
async def test_properties_v3_missing_fields_return_defaults(
aresponses: ResponsesMockServer,
authenticated_simplisafe_server_v3: ResponsesMockServer,
) -> None:
"""Test that V3 properties return safe defaults when API fields are absent.

Regression test for home-assistant/core#172599.
"""
async with authenticated_simplisafe_server_v3, aiohttp.ClientSession() as session:
simplisafe = await API.async_from_auth(
TEST_AUTHORIZATION_CODE, TEST_CODE_VERIFIER, session=session
)
systems = await simplisafe.async_get_systems()
system = systems[TEST_SYSTEM_ID]
sensor: SensorV3 = cast(SensorV3, system.sensors["825"])

# Remove optional nested fields to simulate a sparse API response
system.sensor_data["825"].pop("status", None)
system.sensor_data["825"].pop("flags", None)
system.sensor_data["825"].pop("setting", None)

assert not sensor.error
assert not sensor.low_battery
assert not sensor.offline
assert sensor.settings == {}

aresponses.assert_plan_strictly_followed()
Loading