Migrate mobile_app to RestoreEntity (#46391)

This commit is contained in:
Erik Montnemery 2021-02-11 20:18:03 +01:00 committed by GitHub
parent 70e23402a9
commit 26e7916367
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 395 additions and 101 deletions

View File

@ -17,11 +17,9 @@ from .const import (
ATTR_MODEL,
ATTR_OS_VERSION,
CONF_CLOUDHOOK_URL,
DATA_BINARY_SENSOR,
DATA_CONFIG_ENTRIES,
DATA_DELETED_IDS,
DATA_DEVICES,
DATA_SENSOR,
DATA_STORE,
DOMAIN,
STORAGE_KEY,
@ -40,18 +38,14 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
app_config = await store.async_load()
if app_config is None:
app_config = {
DATA_BINARY_SENSOR: {},
DATA_CONFIG_ENTRIES: {},
DATA_DELETED_IDS: [],
DATA_SENSOR: {},
}
hass.data[DOMAIN] = {
DATA_BINARY_SENSOR: app_config.get(DATA_BINARY_SENSOR, {}),
DATA_CONFIG_ENTRIES: {},
DATA_DELETED_IDS: app_config.get(DATA_DELETED_IDS, []),
DATA_DEVICES: {},
DATA_SENSOR: app_config.get(DATA_SENSOR, {}),
DATA_STORE: store,
}

View File

@ -2,18 +2,25 @@
from functools import partial
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID, STATE_ON
from homeassistant.core import callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
ATTR_DEVICE_NAME,
ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME,
ATTR_SENSOR_STATE,
ATTR_SENSOR_TYPE,
ATTR_SENSOR_TYPE_BINARY_SENSOR as ENTITY_TYPE,
ATTR_SENSOR_UNIQUE_ID,
DATA_DEVICES,
DOMAIN,
)
from .entity import MobileAppEntity, sensor_id
from .entity import MobileAppEntity, unique_id
async def async_setup_entry(hass, config_entry, async_add_entities):
@ -22,13 +29,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
for config in hass.data[DOMAIN][ENTITY_TYPE].values():
if config[CONF_WEBHOOK_ID] != webhook_id:
entity_registry = await er.async_get_registry(hass)
entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
for entry in entries:
if entry.domain != ENTITY_TYPE or entry.disabled_by:
continue
device = hass.data[DOMAIN][DATA_DEVICES][webhook_id]
entities.append(MobileAppBinarySensor(config, device, config_entry))
config = {
ATTR_SENSOR_ATTRIBUTES: {},
ATTR_SENSOR_DEVICE_CLASS: entry.device_class,
ATTR_SENSOR_ICON: entry.original_icon,
ATTR_SENSOR_NAME: entry.original_name,
ATTR_SENSOR_STATE: None,
ATTR_SENSOR_TYPE: entry.domain,
ATTR_SENSOR_UNIQUE_ID: entry.unique_id,
}
entities.append(MobileAppBinarySensor(config, entry.device_id, config_entry))
async_add_entities(entities)
@ -37,14 +52,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if data[CONF_WEBHOOK_ID] != webhook_id:
return
unique_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id]
if "added" in entity:
return
entity["added"] = True
data[CONF_UNIQUE_ID] = unique_id(
data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]
)
data[
CONF_NAME
] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}"
device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]]
@ -64,3 +77,10 @@ class MobileAppBinarySensor(MobileAppEntity, BinarySensorEntity):
def is_on(self):
"""Return the state of the binary sensor."""
return self._config[ATTR_SENSOR_STATE]
@callback
def async_restore_last_state(self, last_state):
"""Restore previous state."""
super().async_restore_last_state(last_state)
self._config[ATTR_SENSOR_STATE] = last_state.state == STATE_ON

View File

@ -3,7 +3,7 @@ import uuid
from homeassistant import config_entries
from homeassistant.components import person
from homeassistant.helpers import entity_registry
from homeassistant.helpers import entity_registry as er
from .const import ATTR_APP_ID, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN
@ -36,8 +36,8 @@ class MobileAppFlowHandler(config_entries.ConfigFlow):
user_input[ATTR_DEVICE_ID] = str(uuid.uuid4()).replace("-", "")
# Register device tracker entity and add to person registering app
ent_reg = await entity_registry.async_get_registry(self.hass)
devt_entry = ent_reg.async_get_or_create(
entity_registry = await er.async_get_registry(self.hass)
devt_entry = entity_registry.async_get_or_create(
"device_tracker",
DOMAIN,
user_input[ATTR_DEVICE_ID],

View File

@ -9,11 +9,9 @@ CONF_REMOTE_UI_URL = "remote_ui_url"
CONF_SECRET = "secret"
CONF_USER_ID = "user_id"
DATA_BINARY_SENSOR = "binary_sensor"
DATA_CONFIG_ENTRIES = "config_entries"
DATA_DELETED_IDS = "deleted_ids"
DATA_DEVICES = "devices"
DATA_SENSOR = "sensor"
DATA_STORE = "store"
DATA_NOTIFY = "notify"

View File

@ -1,57 +1,70 @@
"""A entity class for mobile_app."""
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.const import ATTR_ICON, CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID
from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.restore_state import RestoreEntity
from .const import (
ATTR_DEVICE_NAME,
ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME,
ATTR_SENSOR_STATE,
ATTR_SENSOR_TYPE,
ATTR_SENSOR_UNIQUE_ID,
DOMAIN,
SIGNAL_SENSOR_UPDATE,
)
from .helpers import device_info
def sensor_id(webhook_id, unique_id):
def unique_id(webhook_id, sensor_unique_id):
"""Return a unique sensor ID."""
return f"{webhook_id}_{unique_id}"
return f"{webhook_id}_{sensor_unique_id}"
class MobileAppEntity(Entity):
class MobileAppEntity(RestoreEntity):
"""Representation of an mobile app entity."""
def __init__(self, config: dict, device: DeviceEntry, entry: ConfigEntry):
"""Initialize the sensor."""
"""Initialize the entity."""
self._config = config
self._device = device
self._entry = entry
self._registration = entry.data
self._sensor_id = sensor_id(
self._registration[CONF_WEBHOOK_ID], config[ATTR_SENSOR_UNIQUE_ID]
)
self._unique_id = config[CONF_UNIQUE_ID]
self._entity_type = config[ATTR_SENSOR_TYPE]
self.unsub_dispatcher = None
self._name = f"{entry.data[ATTR_DEVICE_NAME]} {config[ATTR_SENSOR_NAME]}"
self._name = config[CONF_NAME]
async def async_added_to_hass(self):
"""Register callbacks."""
self.unsub_dispatcher = async_dispatcher_connect(
self.hass, SIGNAL_SENSOR_UPDATE, self._handle_update
)
state = await self.async_get_last_state()
if state is None:
return
self.async_restore_last_state(state)
async def async_will_remove_from_hass(self):
"""Disconnect dispatcher listener when removed."""
if self.unsub_dispatcher is not None:
self.unsub_dispatcher()
@callback
def async_restore_last_state(self, last_state):
"""Restore previous state."""
self._config[ATTR_SENSOR_STATE] = last_state.state
self._config[ATTR_SENSOR_ATTRIBUTES] = {
**last_state.attributes,
**self._config[ATTR_SENSOR_ATTRIBUTES],
}
if ATTR_ICON in last_state.attributes:
self._config[ATTR_SENSOR_ICON] = last_state.attributes[ATTR_ICON]
@property
def should_poll(self) -> bool:
"""Declare that this entity pushes its state to HA."""
@ -80,27 +93,19 @@ class MobileAppEntity(Entity):
@property
def unique_id(self):
"""Return the unique ID of this sensor."""
return self._sensor_id
return self._unique_id
@property
def device_info(self):
"""Return device registry information for this entity."""
return device_info(self._registration)
async def async_update(self):
"""Get the latest state of the sensor."""
data = self.hass.data[DOMAIN]
try:
self._config = data[self._entity_type][self._sensor_id]
except KeyError:
return
@callback
def _handle_update(self, data):
"""Handle async event updates."""
incoming_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
if incoming_id != self._sensor_id:
incoming_id = unique_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
if incoming_id != self._unique_id:
return
self._config = data
self._config = {**self._config, **data}
self.async_write_ha_state()

View File

@ -25,9 +25,7 @@ from .const import (
ATTR_SUPPORTS_ENCRYPTION,
CONF_SECRET,
CONF_USER_ID,
DATA_BINARY_SENSOR,
DATA_DELETED_IDS,
DATA_SENSOR,
DOMAIN,
)
@ -138,9 +136,7 @@ def safe_registration(registration: Dict) -> Dict:
def savable_state(hass: HomeAssistantType) -> Dict:
"""Return a clean object containing things that should be saved."""
return {
DATA_BINARY_SENSOR: hass.data[DOMAIN][DATA_BINARY_SENSOR],
DATA_DELETED_IDS: hass.data[DOMAIN][DATA_DELETED_IDS],
DATA_SENSOR: hass.data[DOMAIN][DATA_SENSOR],
}

View File

@ -1,19 +1,26 @@
"""Sensor platform for mobile_app."""
from functools import partial
from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID
from homeassistant.core import callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
ATTR_DEVICE_NAME,
ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME,
ATTR_SENSOR_STATE,
ATTR_SENSOR_TYPE,
ATTR_SENSOR_TYPE_SENSOR as ENTITY_TYPE,
ATTR_SENSOR_UNIQUE_ID,
ATTR_SENSOR_UOM,
DATA_DEVICES,
DOMAIN,
)
from .entity import MobileAppEntity, sensor_id
from .entity import MobileAppEntity, unique_id
async def async_setup_entry(hass, config_entry, async_add_entities):
@ -22,13 +29,22 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
for config in hass.data[DOMAIN][ENTITY_TYPE].values():
if config[CONF_WEBHOOK_ID] != webhook_id:
entity_registry = await er.async_get_registry(hass)
entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
for entry in entries:
if entry.domain != ENTITY_TYPE or entry.disabled_by:
continue
device = hass.data[DOMAIN][DATA_DEVICES][webhook_id]
entities.append(MobileAppSensor(config, device, config_entry))
config = {
ATTR_SENSOR_ATTRIBUTES: {},
ATTR_SENSOR_DEVICE_CLASS: entry.device_class,
ATTR_SENSOR_ICON: entry.original_icon,
ATTR_SENSOR_NAME: entry.original_name,
ATTR_SENSOR_STATE: None,
ATTR_SENSOR_TYPE: entry.domain,
ATTR_SENSOR_UNIQUE_ID: entry.unique_id,
ATTR_SENSOR_UOM: entry.unit_of_measurement,
}
entities.append(MobileAppSensor(config, entry.device_id, config_entry))
async_add_entities(entities)
@ -37,14 +53,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if data[CONF_WEBHOOK_ID] != webhook_id:
return
unique_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id]
if "added" in entity:
return
entity["added"] = True
data[CONF_UNIQUE_ID] = unique_id(
data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]
)
data[
CONF_NAME
] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}"
device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]]

View File

@ -36,6 +36,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceNotFound
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
entity_registry as er,
template,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
@ -79,7 +80,6 @@ from .const import (
CONF_SECRET,
DATA_CONFIG_ENTRIES,
DATA_DELETED_IDS,
DATA_STORE,
DOMAIN,
ERR_ENCRYPTION_ALREADY_ENABLED,
ERR_ENCRYPTION_NOT_AVAILABLE,
@ -95,7 +95,6 @@ from .helpers import (
error_response,
registration_context,
safe_registration,
savable_state,
supports_encryption,
webhook_response,
)
@ -415,7 +414,10 @@ async def webhook_register_sensor(hass, config_entry, data):
device_name = config_entry.data[ATTR_DEVICE_NAME]
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type]
entity_registry = await er.async_get_registry(hass)
existing_sensor = entity_registry.async_get_entity_id(
entity_type, DOMAIN, unique_store_key
)
data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID]
@ -424,16 +426,7 @@ async def webhook_register_sensor(hass, config_entry, data):
_LOGGER.debug(
"Re-register for %s of existing sensor %s", device_name, unique_id
)
entry = hass.data[DOMAIN][entity_type][unique_store_key]
data = {**entry, **data}
hass.data[DOMAIN][entity_type][unique_store_key] = data
hass.data[DOMAIN][DATA_STORE].async_delay_save(
lambda: savable_state(hass), DELAY_SAVE
)
if existing_sensor:
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data)
else:
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
@ -485,7 +478,10 @@ async def webhook_update_sensor_states(hass, config_entry, data):
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
if unique_store_key not in hass.data[DOMAIN][entity_type]:
entity_registry = await er.async_get_registry(hass)
if not entity_registry.async_get_entity_id(
entity_type, DOMAIN, unique_store_key
):
_LOGGER.error(
"Refusing to update %s non-registered sensor: %s",
device_name,
@ -498,7 +494,7 @@ async def webhook_update_sensor_states(hass, config_entry, data):
}
continue
entry = hass.data[DOMAIN][entity_type][unique_store_key]
entry = {CONF_WEBHOOK_ID: config_entry.data[CONF_WEBHOOK_ID]}
try:
sensor = sensor_schema_full(sensor)
@ -518,16 +514,10 @@ async def webhook_update_sensor_states(hass, config_entry, data):
new_state = {**entry, **sensor}
hass.data[DOMAIN][entity_type][unique_store_key] = new_state
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
resp[unique_id] = {"success": True}
hass.data[DOMAIN][DATA_STORE].async_delay_save(
lambda: savable_state(hass), DELAY_SAVE
)
return webhook_response(resp, registration=config_entry.data)

View File

@ -7,14 +7,6 @@ from homeassistant.setup import async_setup_component
from .const import REGISTER, REGISTER_CLEARTEXT
from tests.common import mock_device_registry
@pytest.fixture
def registry(hass):
"""Return a configured device registry."""
return mock_device_registry(hass)
@pytest.fixture
async def create_registrations(hass, authed_api_client):

View File

@ -0,0 +1,271 @@
"""Entity tests for mobile_app."""
from homeassistant.const import STATE_OFF
from homeassistant.helpers import device_registry
async def test_sensor(hass, create_registrations, webhook_client):
"""Test that sensors can be registered and updated."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"attributes": {"foo": "bar"},
"device_class": "plug",
"icon": "mdi:power-plug",
"name": "Is Charging",
"state": True,
"type": "binary_sensor",
"unique_id": "is_charging",
},
},
)
assert reg_resp.status == 201
json = await reg_resp.json()
assert json == {"success": True}
await hass.async_block_till_done()
entity = hass.states.get("binary_sensor.test_1_is_charging")
assert entity is not None
assert entity.attributes["device_class"] == "plug"
assert entity.attributes["icon"] == "mdi:power-plug"
assert entity.attributes["foo"] == "bar"
assert entity.domain == "binary_sensor"
assert entity.name == "Test 1 Is Charging"
assert entity.state == "on"
update_resp = await webhook_client.post(
webhook_url,
json={
"type": "update_sensor_states",
"data": [
{
"icon": "mdi:battery-unknown",
"state": False,
"type": "binary_sensor",
"unique_id": "is_charging",
},
# This invalid data should not invalidate whole request
{
"type": "binary_sensor",
"unique_id": "invalid_state",
"invalid": "data",
},
],
},
)
assert update_resp.status == 200
json = await update_resp.json()
assert json["invalid_state"]["success"] is False
updated_entity = hass.states.get("binary_sensor.test_1_is_charging")
assert updated_entity.state == "off"
assert "foo" not in updated_entity.attributes
dev_reg = await device_registry.async_get_registry(hass)
assert len(dev_reg.devices) == len(create_registrations)
# Reload to verify state is restored
config_entry = hass.config_entries.async_entries("mobile_app")[1]
await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
unloaded_entity = hass.states.get("binary_sensor.test_1_is_charging")
assert unloaded_entity.state == "unavailable"
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
restored_entity = hass.states.get("binary_sensor.test_1_is_charging")
assert restored_entity.state == updated_entity.state
assert restored_entity.attributes == updated_entity.attributes
async def test_sensor_must_register(hass, create_registrations, webhook_client):
"""Test that sensors must be registered before updating."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
resp = await webhook_client.post(
webhook_url,
json={
"type": "update_sensor_states",
"data": [
{"state": True, "type": "binary_sensor", "unique_id": "battery_state"}
],
},
)
assert resp.status == 200
json = await resp.json()
assert json["battery_state"]["success"] is False
assert json["battery_state"]["error"]["code"] == "not_registered"
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog):
"""Test that a duplicate unique ID in registration updates the sensor."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
payload = {
"type": "register_sensor",
"data": {
"attributes": {"foo": "bar"},
"device_class": "plug",
"icon": "mdi:power-plug",
"name": "Is Charging",
"state": True,
"type": "binary_sensor",
"unique_id": "is_charging",
},
}
reg_resp = await webhook_client.post(webhook_url, json=payload)
assert reg_resp.status == 201
reg_json = await reg_resp.json()
assert reg_json == {"success": True}
await hass.async_block_till_done()
assert "Re-register" not in caplog.text
entity = hass.states.get("binary_sensor.test_1_is_charging")
assert entity is not None
assert entity.attributes["device_class"] == "plug"
assert entity.attributes["icon"] == "mdi:power-plug"
assert entity.attributes["foo"] == "bar"
assert entity.domain == "binary_sensor"
assert entity.name == "Test 1 Is Charging"
assert entity.state == "on"
payload["data"]["state"] = False
dupe_resp = await webhook_client.post(webhook_url, json=payload)
assert dupe_resp.status == 201
dupe_reg_json = await dupe_resp.json()
assert dupe_reg_json == {"success": True}
await hass.async_block_till_done()
assert "Re-register" in caplog.text
entity = hass.states.get("binary_sensor.test_1_is_charging")
assert entity is not None
assert entity.attributes["device_class"] == "plug"
assert entity.attributes["icon"] == "mdi:power-plug"
assert entity.attributes["foo"] == "bar"
assert entity.domain == "binary_sensor"
assert entity.name == "Test 1 Is Charging"
assert entity.state == "off"
async def test_register_sensor_no_state(hass, create_registrations, webhook_client):
"""Test that sensors can be registered, when there is no (unknown) state."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Is Charging",
"state": None,
"type": "binary_sensor",
"unique_id": "is_charging",
},
},
)
assert reg_resp.status == 201
json = await reg_resp.json()
assert json == {"success": True}
await hass.async_block_till_done()
entity = hass.states.get("binary_sensor.test_1_is_charging")
assert entity is not None
assert entity.domain == "binary_sensor"
assert entity.name == "Test 1 Is Charging"
assert entity.state == STATE_OFF # Binary sensor defaults to off
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Backup Is Charging",
"type": "binary_sensor",
"unique_id": "backup_is_charging",
},
},
)
assert reg_resp.status == 201
json = await reg_resp.json()
assert json == {"success": True}
await hass.async_block_till_done()
entity = hass.states.get("binary_sensor.test_1_backup_is_charging")
assert entity
assert entity.domain == "binary_sensor"
assert entity.name == "Test 1 Backup Is Charging"
assert entity.state == STATE_OFF # Binary sensor defaults to off
async def test_update_sensor_no_state(hass, create_registrations, webhook_client):
"""Test that sensors can be updated, when there is no (unknown) state."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Is Charging",
"state": True,
"type": "binary_sensor",
"unique_id": "is_charging",
},
},
)
assert reg_resp.status == 201
json = await reg_resp.json()
assert json == {"success": True}
await hass.async_block_till_done()
entity = hass.states.get("binary_sensor.test_1_is_charging")
assert entity is not None
assert entity.state == "on"
update_resp = await webhook_client.post(
webhook_url,
json={
"type": "update_sensor_states",
"data": [
{"state": None, "type": "binary_sensor", "unique_id": "is_charging"}
],
},
)
assert update_resp.status == 200
json = await update_resp.json()
assert json == {"is_charging": {"success": True}}
updated_entity = hass.states.get("binary_sensor.test_1_is_charging")
assert updated_entity.state == STATE_OFF # Binary sensor defaults to off

View File

@ -66,10 +66,24 @@ async def test_sensor(hass, create_registrations, webhook_client):
updated_entity = hass.states.get("sensor.test_1_battery_state")
assert updated_entity.state == "123"
assert "foo" not in updated_entity.attributes
dev_reg = await device_registry.async_get_registry(hass)
assert len(dev_reg.devices) == len(create_registrations)
# Reload to verify state is restored
config_entry = hass.config_entries.async_entries("mobile_app")[1]
await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
unloaded_entity = hass.states.get("sensor.test_1_battery_state")
assert unloaded_entity.state == "unavailable"
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
restored_entity = hass.states.get("sensor.test_1_battery_state")
assert restored_entity.state == updated_entity.state
assert restored_entity.attributes == updated_entity.attributes
async def test_sensor_must_register(hass, create_registrations, webhook_client):
"""Test that sensors must be registered before updating."""

View File

@ -109,7 +109,7 @@ async def test_webhook_handle_fire_event(hass, create_registrations, webhook_cli
@callback
def store_event(event):
"""Helepr to store events."""
"""Help store events."""
events.append(event)
hass.bus.async_listen("test_event", store_event)