Fix a series of bugs due to Notion API changes (#93039)

* Fix a series of bugs due to Notion API changes

* Simplify

* Reduce blast radius

* Reduce blast radius

* Fix tests
This commit is contained in:
Aaron Bach 2023-05-14 10:07:15 -06:00 committed by GitHub
parent e593ceaaf2
commit 637941df4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 283 additions and 221 deletions

View file

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from dataclasses import dataclass, field, fields from dataclasses import dataclass, field
from datetime import timedelta from datetime import timedelta
import logging import logging
import traceback import traceback
@ -10,9 +10,16 @@ from typing import Any
from uuid import UUID from uuid import UUID
from aionotion import async_get_client from aionotion import async_get_client
from aionotion.bridge.models import Bridge from aionotion.bridge.models import Bridge, BridgeAllResponse
from aionotion.errors import InvalidCredentialsError, NotionError from aionotion.errors import InvalidCredentialsError, NotionError
from aionotion.sensor.models import Listener, ListenerKind, Sensor from aionotion.sensor.models import (
Listener,
ListenerAllResponse,
ListenerKind,
Sensor,
SensorAllResponse,
)
from aionotion.user.models import UserPreferences, UserPreferencesResponse
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
@ -51,6 +58,11 @@ PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
ATTR_SYSTEM_MODE = "system_mode" ATTR_SYSTEM_MODE = "system_mode"
ATTR_SYSTEM_NAME = "system_name" ATTR_SYSTEM_NAME = "system_name"
DATA_BRIDGES = "bridges"
DATA_LISTENERS = "listeners"
DATA_SENSORS = "sensors"
DATA_USER_PREFERENCES = "user_preferences"
DEFAULT_SCAN_INTERVAL = timedelta(minutes=1) DEFAULT_SCAN_INTERVAL = timedelta(minutes=1)
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
@ -84,6 +96,9 @@ def is_uuid(value: str) -> bool:
class NotionData: class NotionData:
"""Define a manager class for Notion data.""" """Define a manager class for Notion data."""
hass: HomeAssistant
entry: ConfigEntry
# Define a dict of bridges, indexed by bridge ID (an integer): # Define a dict of bridges, indexed by bridge ID (an integer):
bridges: dict[int, Bridge] = field(default_factory=dict) bridges: dict[int, Bridge] = field(default_factory=dict)
@ -93,12 +108,40 @@ class NotionData:
# Define a dict of sensors, indexed by sensor UUID (a string): # Define a dict of sensors, indexed by sensor UUID (a string):
sensors: dict[str, Sensor] = field(default_factory=dict) sensors: dict[str, Sensor] = field(default_factory=dict)
# Define a user preferences response object:
user_preferences: UserPreferences | None = field(default=None)
def update_data_from_response(
self,
response: BridgeAllResponse
| ListenerAllResponse
| SensorAllResponse
| UserPreferencesResponse,
) -> None:
"""Update data from an aionotion response."""
if isinstance(response, BridgeAllResponse):
for bridge in response.bridges:
# If a new bridge is discovered, register it:
if bridge.id not in self.bridges:
_async_register_new_bridge(self.hass, self.entry, bridge)
self.bridges[bridge.id] = bridge
elif isinstance(response, ListenerAllResponse):
self.listeners = {listener.id: listener for listener in response.listeners}
elif isinstance(response, SensorAllResponse):
self.sensors = {sensor.uuid: sensor for sensor in response.sensors}
elif isinstance(response, UserPreferencesResponse):
self.user_preferences = response.user_preferences
def asdict(self) -> dict[str, Any]: def asdict(self) -> dict[str, Any]:
"""Represent this dataclass (and its Pydantic contents) as a dict.""" """Represent this dataclass (and its Pydantic contents) as a dict."""
return { data: dict[str, Any] = {
field.name: [obj.dict() for obj in getattr(self, field.name).values()] DATA_BRIDGES: [bridge.dict() for bridge in self.bridges.values()],
for field in fields(self) DATA_LISTENERS: [listener.dict() for listener in self.listeners.values()],
DATA_SENSORS: [sensor.dict() for sensor in self.sensors.values()],
} }
if self.user_preferences:
data[DATA_USER_PREFERENCES] = self.user_preferences.dict()
return data
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -121,11 +164,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_update() -> NotionData: async def async_update() -> NotionData:
"""Get the latest data from the Notion API.""" """Get the latest data from the Notion API."""
data = NotionData() data = NotionData(hass=hass, entry=entry)
tasks = { tasks = {
"bridges": client.bridge.async_all(), DATA_BRIDGES: client.bridge.async_all(),
"listeners": client.sensor.async_listeners(), DATA_LISTENERS: client.sensor.async_listeners(),
"sensors": client.sensor.async_all(), DATA_SENSORS: client.sensor.async_all(),
DATA_USER_PREFERENCES: client.user.async_preferences(),
} }
results = await asyncio.gather(*tasks.values(), return_exceptions=True) results = await asyncio.gather(*tasks.values(), return_exceptions=True)
@ -145,16 +189,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
f"There was an unknown error while updating {attr}: {result}" f"There was an unknown error while updating {attr}: {result}"
) from result ) from result
for item in result: data.update_data_from_response(result)
if attr == "bridges":
# If a new bridge is discovered, register it:
if item.id not in data.bridges:
_async_register_new_bridge(hass, item, entry)
data.bridges[item.id] = item
elif attr == "listeners":
data.listeners[item.id] = item
else:
data.sensors[item.uuid] = item
return data return data
@ -216,7 +251,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@callback @callback
def _async_register_new_bridge( def _async_register_new_bridge(
hass: HomeAssistant, bridge: Bridge, entry: ConfigEntry hass: HomeAssistant, entry: ConfigEntry, bridge: Bridge
) -> None: ) -> None:
"""Register a new bridge.""" """Register a new bridge."""
if name := bridge.name: if name := bridge.name:
@ -279,6 +314,11 @@ class NotionEntity(CoordinatorEntity[DataUpdateCoordinator[NotionData]]):
and self._listener_id in self.coordinator.data.listeners and self._listener_id in self.coordinator.data.listeners
) )
@property
def listener(self) -> Listener:
"""Return the listener related to this entity."""
return self.coordinator.data.listeners[self._listener_id]
@callback @callback
def _async_update_bridge_id(self) -> None: def _async_update_bridge_id(self) -> None:
"""Update the entity's bridge ID if it has changed. """Update the entity's bridge ID if it has changed.
@ -310,21 +350,9 @@ class NotionEntity(CoordinatorEntity[DataUpdateCoordinator[NotionData]]):
this_device.id, via_device_id=bridge_device.id this_device.id, via_device_id=bridge_device.id
) )
@callback
def _async_update_from_latest_data(self) -> None:
"""Update the entity from the latest data."""
raise NotImplementedError
@callback @callback
def _handle_coordinator_update(self) -> None: def _handle_coordinator_update(self) -> None:
"""Respond to a DataUpdateCoordinator update.""" """Respond to a DataUpdateCoordinator update."""
if self._listener_id in self.coordinator.data.listeners: if self._listener_id in self.coordinator.data.listeners:
self._async_update_bridge_id() self._async_update_bridge_id()
self._async_update_from_latest_data() super()._handle_coordinator_update()
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
self._async_update_from_latest_data()

View file

@ -13,7 +13,7 @@ from homeassistant.components.binary_sensor import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import NotionEntity from . import NotionEntity
@ -37,7 +37,7 @@ from .model import NotionEntityDescriptionMixin
class NotionBinarySensorDescriptionMixin: class NotionBinarySensorDescriptionMixin:
"""Define an entity description mixin for binary and regular sensors.""" """Define an entity description mixin for binary and regular sensors."""
on_state: Literal["alarm", "critical", "leak", "not_missing", "open"] on_state: Literal["alarm", "leak", "low", "not_missing", "open"]
@dataclass @dataclass
@ -56,7 +56,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
device_class=BinarySensorDeviceClass.BATTERY, device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
listener_kind=ListenerKind.BATTERY, listener_kind=ListenerKind.BATTERY,
on_state="critical", on_state="low",
), ),
NotionBinarySensorDescription( NotionBinarySensorDescription(
key=SENSOR_DOOR, key=SENSOR_DOOR,
@ -146,17 +146,10 @@ class NotionBinarySensor(NotionEntity, BinarySensorEntity):
entity_description: NotionBinarySensorDescription entity_description: NotionBinarySensorDescription
@callback @property
def _async_update_from_latest_data(self) -> None: def is_on(self) -> bool | None:
"""Fetch new state data for the sensor.""" """Return true if the binary sensor is on."""
listener = self.coordinator.data.listeners[self._listener_id] if not self.listener.insights.primary.value:
LOGGER.warning("Unknown listener structure: %s", self.listener.dict())
if listener.status.trigger_value: return False
state = listener.status.trigger_value return self.listener.insights.primary.value == self.entity_description.on_state
elif listener.insights.primary.value:
state = listener.insights.primary.value
else:
LOGGER.warning("Unknown listener structure: %s", listener)
state = None
self._attr_is_on = self.entity_description.on_state == state

View file

@ -16,6 +16,7 @@ CONF_DEVICE_KEY = "device_key"
CONF_HARDWARE_ID = "hardware_id" CONF_HARDWARE_ID = "hardware_id"
CONF_LAST_BRIDGE_HARDWARE_ID = "last_bridge_hardware_id" CONF_LAST_BRIDGE_HARDWARE_ID = "last_bridge_hardware_id"
CONF_TITLE = "title" CONF_TITLE = "title"
CONF_USER_ID = "user_id"
TO_REDACT = { TO_REDACT = {
CONF_DEVICE_KEY, CONF_DEVICE_KEY,
@ -27,6 +28,7 @@ TO_REDACT = {
CONF_TITLE, CONF_TITLE,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
CONF_USERNAME, CONF_USERNAME,
CONF_USER_ID,
} }

View file

@ -7,5 +7,5 @@
"integration_type": "hub", "integration_type": "hub",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aionotion"], "loggers": ["aionotion"],
"requirements": ["aionotion==2023.05.1"] "requirements": ["aionotion==2023.05.4"]
} }

View file

@ -11,11 +11,11 @@ from homeassistant.components.sensor import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import NotionEntity from . import NotionEntity
from .const import DOMAIN, LOGGER, SENSOR_TEMPERATURE from .const import DOMAIN, SENSOR_TEMPERATURE
from .model import NotionEntityDescriptionMixin from .model import NotionEntityDescriptionMixin
@ -63,15 +63,24 @@ async def async_setup_entry(
class NotionSensor(NotionEntity, SensorEntity): class NotionSensor(NotionEntity, SensorEntity):
"""Define a Notion sensor.""" """Define a Notion sensor."""
@callback @property
def _async_update_from_latest_data(self) -> None: def native_unit_of_measurement(self) -> str | None:
"""Fetch new state data for the sensor.""" """Return the unit of measurement of the sensor."""
listener = self.coordinator.data.listeners[self._listener_id] if self.listener.listener_kind == ListenerKind.TEMPERATURE:
if not self.coordinator.data.user_preferences:
return None
if self.coordinator.data.user_preferences.celsius_enabled:
return UnitOfTemperature.CELSIUS
return UnitOfTemperature.FAHRENHEIT
return None
if listener.listener_kind == ListenerKind.TEMPERATURE: @property
self._attr_native_value = round(listener.status.temperature, 1) # type: ignore[attr-defined] def native_value(self) -> str | None:
else: """Return the value reported by the sensor.
LOGGER.error(
"Unknown listener type for sensor %s", The Notion API only returns a localized string for temperature (e.g. "70°"); we
self.coordinator.data.sensors[self._sensor_id], simply remove the degree symbol:
) """
if not self.listener.status_localized:
return None
return self.listener.status_localized.state[:-1]

View file

@ -223,7 +223,7 @@ aionanoleaf==0.2.1
aionotify==0.2.0 aionotify==0.2.0
# homeassistant.components.notion # homeassistant.components.notion
aionotion==2023.05.1 aionotion==2023.05.4
# homeassistant.components.oncue # homeassistant.components.oncue
aiooncue==0.3.4 aiooncue==0.3.4

View file

@ -204,7 +204,7 @@ aiomusiccast==0.14.8
aionanoleaf==0.2.1 aionanoleaf==0.2.1
# homeassistant.components.notion # homeassistant.components.notion
aionotion==2023.05.1 aionotion==2023.05.4
# homeassistant.components.oncue # homeassistant.components.oncue
aiooncue==0.3.4 aiooncue==0.3.4

View file

@ -3,8 +3,9 @@ from collections.abc import Generator
import json import json
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch
from aionotion.bridge.models import Bridge from aionotion.bridge.models import BridgeAllResponse
from aionotion.sensor.models import Listener, Sensor from aionotion.sensor.models import ListenerAllResponse, SensorAllResponse
from aionotion.user.models import UserPreferencesResponse
import pytest import pytest
from homeassistant.components.notion import DOMAIN from homeassistant.components.notion import DOMAIN
@ -27,24 +28,23 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
@pytest.fixture(name="client") @pytest.fixture(name="client")
def client_fixture(data_bridge, data_listener, data_sensor): def client_fixture(data_bridge, data_listener, data_sensor, data_user_preferences):
"""Define a fixture for an aionotion client.""" """Define a fixture for an aionotion client."""
return Mock( return Mock(
bridge=Mock( bridge=Mock(
async_all=AsyncMock( async_all=AsyncMock(return_value=BridgeAllResponse.parse_obj(data_bridge))
return_value=[Bridge.parse_obj(bridge) for bridge in data_bridge]
)
), ),
sensor=Mock( sensor=Mock(
async_all=AsyncMock( async_all=AsyncMock(return_value=SensorAllResponse.parse_obj(data_sensor)),
return_value=[Sensor.parse_obj(sensor) for sensor in data_sensor]
),
async_listeners=AsyncMock( async_listeners=AsyncMock(
return_value=[ return_value=ListenerAllResponse.parse_obj(data_listener)
Listener.parse_obj(listener) for listener in data_listener
]
), ),
), ),
user=Mock(
async_preferences=AsyncMock(
return_value=UserPreferencesResponse.parse_obj(data_user_preferences)
)
),
) )
@ -83,6 +83,12 @@ def data_sensor_fixture():
return json.loads(load_fixture("sensor_data.json", "notion")) return json.loads(load_fixture("sensor_data.json", "notion"))
@pytest.fixture(name="data_user_preferences", scope="package")
def data_user_preferences_fixture():
"""Define user preferences data."""
return json.loads(load_fixture("user_preferences_data.json", "notion"))
@pytest.fixture(name="get_client") @pytest.fixture(name="get_client")
def get_client_fixture(client): def get_client_fixture(client):
"""Define a fixture to mock the async_get_client method.""" """Define a fixture to mock the async_get_client method."""

View file

@ -1,50 +1,52 @@
[ {
{ "base_stations": [
"id": 12345, {
"name": "Bridge 1", "id": 12345,
"mode": "home", "name": "Bridge 1",
"hardware_id": "0x0000000000000000", "mode": "home",
"hardware_revision": 4, "hardware_id": "0x0000000000000000",
"firmware_version": { "hardware_revision": 4,
"silabs": "1.1.2", "firmware_version": {
"wifi": "0.121.0", "silabs": "1.1.2",
"wifi_app": "3.3.0" "wifi": "0.121.0",
"wifi_app": "3.3.0"
},
"missing_at": null,
"created_at": "2019-06-27T00:18:44.337Z",
"updated_at": "2023-03-19T03:20:16.061Z",
"system_id": 11111,
"firmware": {
"silabs": "1.1.2",
"wifi": "0.121.0",
"wifi_app": "3.3.0"
},
"links": {
"system": 11111
}
}, },
"missing_at": null, {
"created_at": "2019-06-27T00:18:44.337Z", "id": 67890,
"updated_at": "2023-03-19T03:20:16.061Z", "name": "Bridge 2",
"system_id": 11111, "mode": "home",
"firmware": { "hardware_id": "0x0000000000000000",
"silabs": "1.1.2", "hardware_revision": 4,
"wifi": "0.121.0", "firmware_version": {
"wifi_app": "3.3.0" "wifi": "0.121.0",
}, "wifi_app": "3.3.0",
"links": { "silabs": "1.1.2"
"system": 11111 },
"missing_at": null,
"created_at": "2019-04-30T01:43:50.497Z",
"updated_at": "2023-01-02T19:09:58.251Z",
"system_id": 11111,
"firmware": {
"wifi": "0.121.0",
"wifi_app": "3.3.0",
"silabs": "1.1.2"
},
"links": {
"system": 11111
}
} }
}, ]
{ }
"id": 67890,
"name": "Bridge 2",
"mode": "home",
"hardware_id": "0x0000000000000000",
"hardware_revision": 4,
"firmware_version": {
"wifi": "0.121.0",
"wifi_app": "3.3.0",
"silabs": "1.1.2"
},
"missing_at": null,
"created_at": "2019-04-30T01:43:50.497Z",
"updated_at": "2023-01-02T19:09:58.251Z",
"system_id": 11111,
"firmware": {
"wifi": "0.121.0",
"wifi_app": "3.3.0",
"silabs": "1.1.2"
},
"links": {
"system": 11111
}
}
]

View file

@ -1,55 +1,57 @@
[ {
{ "listeners": [
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", {
"definition_id": 4, "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"created_at": "2019-06-28T22:12:49.651Z", "definition_id": 4,
"type": "sensor", "created_at": "2019-06-28T22:12:49.651Z",
"model_version": "2.1", "type": "sensor",
"sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "model_version": "2.1",
"status": { "sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"trigger_value": "no_leak", "status": {
"data_received_at": "2022-03-20T08:00:29.763Z" "trigger_value": "no_leak",
},
"status_localized": {
"state": "No Leak",
"description": "Mar 20 at 2:00am"
},
"insights": {
"primary": {
"origin": {
"type": "Sensor",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"value": "no_leak",
"data_received_at": "2022-03-20T08:00:29.763Z" "data_received_at": "2022-03-20T08:00:29.763Z"
} },
"status_localized": {
"state": "No Leak",
"description": "Mar 20 at 2:00am"
},
"insights": {
"primary": {
"origin": {
"type": "Sensor",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"value": "no_leak",
"data_received_at": "2022-03-20T08:00:29.763Z"
}
},
"configuration": {},
"pro_monitoring_status": "eligible"
}, },
"configuration": {}, {
"pro_monitoring_status": "eligible" "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
}, "definition_id": 7,
{ "created_at": "2019-07-10T22:40:48.847Z",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "type": "sensor",
"definition_id": 7, "model_version": "3.1",
"created_at": "2019-07-10T22:40:48.847Z", "sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"type": "sensor", "status": {
"model_version": "3.1", "trigger_value": "no_alarm",
"sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": {
"trigger_value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516Z"
},
"status_localized": {
"state": "No Sound",
"description": "Jun 28 at 4:12pm"
},
"insights": {
"primary": {
"origin": {},
"value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516Z" "data_received_at": "2019-06-28T22:12:49.516Z"
} },
}, "status_localized": {
"configuration": {}, "state": "No Sound",
"pro_monitoring_status": "eligible" "description": "Jun 28 at 4:12pm"
} },
] "insights": {
"primary": {
"origin": {},
"value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516Z"
}
},
"configuration": {},
"pro_monitoring_status": "eligible"
}
]
}

View file

@ -1,34 +1,36 @@
[ {
{ "sensors": [
"id": 123456, {
"uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "id": 123456,
"user": { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"id": 12345, "user": {
"email": "user@email.com" "id": 12345,
}, "email": "user@email.com"
"bridge": { },
"id": 67890, "bridge": {
"hardware_id": "0x0000000000000000" "id": 67890,
}, "hardware_id": "0x0000000000000000"
"last_bridge_hardware_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", },
"name": "Sensor 1", "last_bridge_hardware_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"location_id": 123456, "name": "Sensor 1",
"system_id": 12345, "location_id": 123456,
"hardware_id": "0x0000000000000000", "system_id": 12345,
"hardware_revision": 5, "hardware_id": "0x0000000000000000",
"firmware_version": "1.1.2", "hardware_revision": 5,
"device_key": "0x0000000000000000", "firmware_version": "1.1.2",
"encryption_key": true, "device_key": "0x0000000000000000",
"installed_at": "2019-06-28T22:12:51.209Z", "encryption_key": true,
"calibrated_at": "2023-03-07T19:51:56.838Z", "installed_at": "2019-06-28T22:12:51.209Z",
"last_reported_at": "2023-04-19T18:09:40.479Z", "calibrated_at": "2023-03-07T19:51:56.838Z",
"missing_at": null, "last_reported_at": "2023-04-19T18:09:40.479Z",
"updated_at": "2023-03-28T13:33:33.801Z", "missing_at": null,
"created_at": "2019-06-28T22:12:20.256Z", "updated_at": "2023-03-28T13:33:33.801Z",
"signal_strength": 4, "created_at": "2019-06-28T22:12:20.256Z",
"firmware": { "signal_strength": 4,
"status": "valid" "firmware": {
}, "status": "valid"
"surface_type": null },
} "surface_type": null
] }
]
}

View file

@ -0,0 +1,10 @@
{
"user_preferences": {
"user_id": 12345,
"military_time_enabled": false,
"celsius_enabled": false,
"disconnect_alerts_enabled": true,
"home_away_alerts_enabled": false,
"battery_alerts_enabled": true
}
}

View file

@ -86,14 +86,6 @@ async def test_entry_diagnostics(
"device_type": "sensor", "device_type": "sensor",
"model_version": "3.1", "model_version": "3.1",
"sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": {
"trigger_value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516000+00:00",
},
"status_localized": {
"state": "No Sound",
"description": "Jun 28 at 4:12pm",
},
"insights": { "insights": {
"primary": { "primary": {
"origin": {"type": None, "id": None}, "origin": {"type": None, "id": None},
@ -103,6 +95,14 @@ async def test_entry_diagnostics(
}, },
"configuration": {}, "configuration": {},
"pro_monitoring_status": "eligible", "pro_monitoring_status": "eligible",
"status": {
"trigger_value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516000+00:00",
},
"status_localized": {
"state": "No Sound",
"description": "Jun 28 at 4:12pm",
},
} }
], ],
"sensors": [ "sensors": [
@ -131,5 +131,13 @@ async def test_entry_diagnostics(
"surface_type": None, "surface_type": None,
} }
], ],
"user_preferences": {
"user_id": REDACTED,
"military_time_enabled": False,
"celsius_enabled": False,
"disconnect_alerts_enabled": True,
"home_away_alerts_enabled": False,
"battery_alerts_enabled": True,
},
}, },
} }