diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index ba276969299..87d8044c044 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from collections.abc import Callable from contextlib import suppress +from copy import deepcopy import inspect from json import JSONEncoder import logging @@ -133,6 +134,10 @@ class Store: # If we didn't generate data yet, do it now. if "data_func" in data: data["data"] = data.pop("data_func")() + + # We make a copy because code might assume it's safe to mutate loaded data + # and we don't want that to mess with what we're trying to store. + data = deepcopy(data) else: data = await self.hass.async_add_executor_job( json_util.load_json, self.path diff --git a/tests/components/trace/fixtures/automation_saved_traces.json b/tests/components/trace/fixtures/automation_saved_traces.json index 45bcfffc157..7f6ed56a8bc 100644 --- a/tests/components/trace/fixtures/automation_saved_traces.json +++ b/tests/components/trace/fixtures/automation_saved_traces.json @@ -1,5 +1,6 @@ { "version": 1, + "minor_version": 1, "key": "trace.saved_traces", "data": { "automation.sun": [ diff --git a/tests/components/trace/fixtures/script_saved_traces.json b/tests/components/trace/fixtures/script_saved_traces.json index 91677b2a47e..ccd2902d726 100644 --- a/tests/components/trace/fixtures/script_saved_traces.json +++ b/tests/components/trace/fixtures/script_saved_traces.json @@ -1,5 +1,6 @@ { "version": 1, + "minor_version": 1, "key": "trace.saved_traces", "data": { "script.sun": [ diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 41864f5dc3e..0f3447b3dd9 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -49,7 +49,7 @@ import homeassistant.util.dt as dt_util from tests.common import MockConfigEntry, async_fire_time_changed -DEFAULT_CONFIG_ENTRY_ID = 1 +DEFAULT_CONFIG_ENTRY_ID = "1" DEFAULT_HOST = "1.2.3.4" DEFAULT_SITE = "site_id" diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 5ab61ce21a7..384db693f1c 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -1089,7 +1089,7 @@ async def test_restoring_client(hass, aioclient_mock): data=ENTRY_CONFIG, source="test", options={}, - entry_id=1, + entry_id="1", ) registry = er.async_get(hass) diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 1d1fcad9cbe..85733d6d686 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -14,7 +14,7 @@ from .test_controller import ( setup_unifi_integration, ) -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, flush_store async def test_setup_with_no_config(hass): @@ -110,6 +110,7 @@ async def test_wireless_clients(hass, hass_storage, aioclient_mock): config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=[client_1, client_2] ) + await flush_store(hass.data[unifi.UNIFI_WIRELESS_CLIENTS]._store) for mac in [ "00:00:00:00:00:00", diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 85850062583..796434c5cd9 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -22,6 +22,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from .test_controller import ( CONTROLLER_HOST, + DEFAULT_CONFIG_ENTRY_ID, DESCRIPTION, ENTRY_CONFIG, setup_unifi_integration, @@ -857,7 +858,7 @@ async def test_restore_client_succeed(hass, aioclient_mock): data=ENTRY_CONFIG, source="test", options={}, - entry_id=1, + entry_id=DEFAULT_CONFIG_ENTRY_ID, ) registry = er.async_get(hass) @@ -947,7 +948,7 @@ async def test_restore_client_no_old_state(hass, aioclient_mock): data=ENTRY_CONFIG, source="test", options={}, - entry_id=1, + entry_id=DEFAULT_CONFIG_ENTRY_ID, ) registry = er.async_get(hass) diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index d6a78340127..0478c17e299 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -28,13 +28,13 @@ MOCK_DATA2 = {"goodbye": "cruel world"} @pytest.fixture def store(hass): """Fixture of a store that prevents writing on Home Assistant stop.""" - yield storage.Store(hass, MOCK_VERSION, MOCK_KEY) + return storage.Store(hass, MOCK_VERSION, MOCK_KEY) @pytest.fixture def store_v_1_1(hass): """Fixture of a store that prevents writing on Home Assistant stop.""" - yield storage.Store( + return storage.Store( hass, MOCK_VERSION, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_1 ) @@ -42,7 +42,7 @@ def store_v_1_1(hass): @pytest.fixture def store_v_1_2(hass): """Fixture of a store that prevents writing on Home Assistant stop.""" - yield storage.Store( + return storage.Store( hass, MOCK_VERSION, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_2 ) @@ -50,7 +50,7 @@ def store_v_1_2(hass): @pytest.fixture def store_v_2_1(hass): """Fixture of a store that prevents writing on Home Assistant stop.""" - yield storage.Store( + return storage.Store( hass, MOCK_VERSION_2, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_1 ) @@ -91,8 +91,8 @@ async def test_loading_parallel(hass, store, hass_storage, caplog): results = await asyncio.gather(store.async_load(), store.async_load()) - assert results[0] is MOCK_DATA - assert results[1] is MOCK_DATA + assert results[0] == MOCK_DATA + assert results[0] is results[1] assert caplog.text.count(f"Loading data for {store.key}") @@ -436,3 +436,25 @@ async def test_legacy_migration(hass, hass_storage, store_v_1_2): "minor_version": 1, "data": MOCK_DATA, } + + +async def test_changing_delayed_written_data(hass, store, hass_storage): + """Test changing data that is written with delay.""" + data_to_store = {"hello": "world"} + store.async_delay_save(lambda: data_to_store, 1) + assert store.key not in hass_storage + + loaded_data = await store.async_load() + assert loaded_data == data_to_store + assert loaded_data is not data_to_store + + loaded_data["hello"] = "earth" + + async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=1)) + await hass.async_block_till_done() + assert hass_storage[store.key] == { + "version": MOCK_VERSION, + "minor_version": 1, + "key": MOCK_KEY, + "data": {"hello": "world"}, + }