From 609a573b555fd5c6b943182cfd785703e5a0521f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 20 Jun 2023 11:16:51 +0200 Subject: [PATCH] Regenerate instance ID on error (#94898) --- homeassistant/helpers/instance_id.py | 24 ++++++++++++--- tests/helpers/test_instance_id.py | 46 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/instance_id.py b/homeassistant/helpers/instance_id.py index 8561d10794c..5bb8be5a9fe 100644 --- a/homeassistant/helpers/instance_id.py +++ b/homeassistant/helpers/instance_id.py @@ -1,6 +1,7 @@ """Helper to create a unique instance ID.""" from __future__ import annotations +import logging import uuid from homeassistant.core import HomeAssistant @@ -12,17 +13,30 @@ DATA_VERSION = 1 LEGACY_UUID_FILE = ".uuid" +_LOGGER = logging.getLogger(__name__) + @singleton.singleton(DATA_KEY) async def async_get(hass: HomeAssistant) -> str: """Get unique ID for the hass instance.""" store = storage.Store[dict[str, str]](hass, DATA_VERSION, DATA_KEY, True) - data: dict[str, str] | None = await storage.async_migrator( - hass, - hass.config.path(LEGACY_UUID_FILE), - store, - ) + data: dict[str, str] | None = None + try: + data = await storage.async_migrator( + hass, + hass.config.path(LEGACY_UUID_FILE), + store, + ) + except Exception: # pylint: disable=broad-exception-caught + _LOGGER.exception( + ( + "Could not read hass instance ID from '%s' or '%s', a new instance ID " + "will be generated" + ), + DATA_KEY, + LEGACY_UUID_FILE, + ) if data is not None: return data["uuid"] diff --git a/tests/helpers/test_instance_id.py b/tests/helpers/test_instance_id.py index 453d18b1d2a..8fb6bfd8d7e 100644 --- a/tests/helpers/test_instance_id.py +++ b/tests/helpers/test_instance_id.py @@ -1,7 +1,10 @@ """Tests for instance ID helper.""" +from json import JSONDecodeError from typing import Any from unittest.mock import patch +import pytest + from homeassistant.core import HomeAssistant from homeassistant.helpers import instance_id @@ -14,6 +17,25 @@ async def test_get_id_empty(hass: HomeAssistant, hass_storage: dict[str, Any]) - assert hass_storage["core.uuid"]["data"]["uuid"] == uuid +async def test_get_id_load_fail( + hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture +) -> None: + """Migrate existing file with error.""" + hass_storage["core.uuid"] = None # Invalid, will make store.async_load raise + + uuid = await instance_id.async_get(hass) + + assert uuid is not None + + # Assert it's stored + assert hass_storage["core.uuid"]["data"]["uuid"] == uuid + + assert ( + "Could not read hass instance ID from 'core.uuid' or '.uuid', a " + "new instance ID will be generated" in caplog.text + ) + + async def test_get_id_migrate( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: @@ -30,3 +52,27 @@ async def test_get_id_migrate( # assert old deleted assert len(mock_remove.mock_calls) == 1 + + +async def test_get_id_migrate_fail( + hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture +) -> None: + """Migrate existing file with error.""" + with patch( + "homeassistant.util.json.load_json", + side_effect=JSONDecodeError("test_error", "test", 1), + ), patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove: + uuid = await instance_id.async_get(hass) + + assert uuid is not None + + # Assert it's stored + assert hass_storage["core.uuid"]["data"]["uuid"] == uuid + + # assert old not deleted + assert len(mock_remove.mock_calls) == 0 + + assert ( + "Could not read hass instance ID from 'core.uuid' or '.uuid', a " + "new instance ID will be generated" in caplog.text + )