From 85ba29012f922926073543365e5943716f5d3998 Mon Sep 17 00:00:00 2001
From: Franck Nijhof <git@frenck.dev>
Date: Mon, 8 Jun 2020 21:11:37 +0200
Subject: [PATCH] Fix mobile_app sensor re-registration handling (#36567)

---
 homeassistant/components/mobile_app/const.py  |  1 -
 .../components/mobile_app/webhook.py          | 24 ++++++------
 tests/components/mobile_app/test_entity.py    | 39 ++++++++++++++++---
 3 files changed, 45 insertions(+), 19 deletions(-)

diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py
index 0fc4a5ee407..6e83a08c508 100644
--- a/homeassistant/components/mobile_app/const.py
+++ b/homeassistant/components/mobile_app/const.py
@@ -56,7 +56,6 @@ ERR_ENCRYPTION_ALREADY_ENABLED = "encryption_already_enabled"
 ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available"
 ERR_ENCRYPTION_REQUIRED = "encryption_required"
 ERR_SENSOR_NOT_REGISTERED = "not_registered"
-ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id"
 ERR_INVALID_FORMAT = "invalid_format"
 
 
diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py
index c155e722976..d0ab79ab7e2 100644
--- a/homeassistant/components/mobile_app/webhook.py
+++ b/homeassistant/components/mobile_app/webhook.py
@@ -78,7 +78,6 @@ from .const import (
     ERR_ENCRYPTION_NOT_AVAILABLE,
     ERR_ENCRYPTION_REQUIRED,
     ERR_INVALID_FORMAT,
-    ERR_SENSOR_DUPLICATE_UNIQUE_ID,
     ERR_SENSOR_NOT_REGISTERED,
     SIGNAL_LOCATION_UPDATE,
     SIGNAL_SENSOR_UPDATE,
@@ -364,29 +363,30 @@ async def webhook_enable_encryption(hass, config_entry, data):
 async def webhook_register_sensor(hass, config_entry, data):
     """Handle a register sensor webhook."""
     entity_type = data[ATTR_SENSOR_TYPE]
-
     unique_id = data[ATTR_SENSOR_UNIQUE_ID]
 
     unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
-
-    if unique_store_key in hass.data[DOMAIN][entity_type]:
-        _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id)
-        return error_response(
-            ERR_SENSOR_DUPLICATE_UNIQUE_ID,
-            f"{entity_type} {unique_id} already exists!",
-            status=409,
-        )
+    existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type]
 
     data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID]
 
+    # If sensor already is registered, update current state instead
+    if existing_sensor:
+        _LOGGER.debug("Re-register existing sensor %s", 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
     )
 
-    register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
-    async_dispatcher_send(hass, register_signal, data)
+    if existing_sensor:
+        async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data)
+    else:
+        register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
+        async_dispatcher_send(hass, register_signal, data)
 
     return webhook_response(
         {"success": True}, registration=config_entry.data, status=HTTP_CREATED,
diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py
index 9f91e659fb0..d0d2a4f841a 100644
--- a/tests/components/mobile_app/test_entity.py
+++ b/tests/components/mobile_app/test_entity.py
@@ -95,8 +95,8 @@ async def test_sensor_must_register(hass, create_registrations, webhook_client):
     assert json["battery_state"]["error"]["code"] == "not_registered"
 
 
-async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client):
-    """Test that sensors must have a unique ID."""
+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}"
 
@@ -120,14 +120,41 @@ async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client):
 
     reg_json = await reg_resp.json()
     assert reg_json == {"success": True}
+    await hass.async_block_till_done()
 
+    assert "Re-register existing sensor" not in caplog.text
+
+    entity = hass.states.get("sensor.test_1_battery_state")
+    assert entity is not None
+
+    assert entity.attributes["device_class"] == "battery"
+    assert entity.attributes["icon"] == "mdi:battery"
+    assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE
+    assert entity.attributes["foo"] == "bar"
+    assert entity.domain == "sensor"
+    assert entity.name == "Test 1 Battery State"
+    assert entity.state == "100"
+
+    payload["data"]["state"] = 99
     dupe_resp = await webhook_client.post(webhook_url, json=payload)
 
-    assert dupe_resp.status == 409
+    assert dupe_resp.status == 201
+    dupe_reg_json = await dupe_resp.json()
+    assert dupe_reg_json == {"success": True}
+    await hass.async_block_till_done()
 
-    dupe_json = await dupe_resp.json()
-    assert dupe_json["success"] is False
-    assert dupe_json["error"]["code"] == "duplicate_unique_id"
+    assert "Re-register existing sensor" in caplog.text
+
+    entity = hass.states.get("sensor.test_1_battery_state")
+    assert entity is not None
+
+    assert entity.attributes["device_class"] == "battery"
+    assert entity.attributes["icon"] == "mdi:battery"
+    assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE
+    assert entity.attributes["foo"] == "bar"
+    assert entity.domain == "sensor"
+    assert entity.name == "Test 1 Battery State"
+    assert entity.state == "99"
 
 
 async def test_register_sensor_no_state(hass, create_registrations, webhook_client):