diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 70c87392d0b..aa0113cd7ce 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -15,6 +15,7 @@ from collections.abc import ( ) from contextvars import ContextVar from copy import deepcopy +from datetime import datetime from enum import Enum, StrEnum import functools from functools import cached_property @@ -69,6 +70,7 @@ from .setup import ( from .util import ulid as ulid_util from .util.async_ import create_eager_task from .util.decorator import Registry +from .util.dt import utc_from_timestamp, utcnow from .util.enum import try_parse_enum if TYPE_CHECKING: @@ -118,7 +120,7 @@ HANDLERS: Registry[str, type[ConfigFlow]] = Registry() STORAGE_KEY = "core.config_entries" STORAGE_VERSION = 1 -STORAGE_VERSION_MINOR = 2 +STORAGE_VERSION_MINOR = 3 SAVE_DELAY = 1 @@ -303,15 +305,19 @@ class ConfigEntry(Generic[_DataT]): _background_tasks: set[asyncio.Future[Any]] _integration_for_domain: loader.Integration | None _tries: int + created_at: datetime + modified_at: datetime def __init__( self, *, + created_at: datetime | None = None, data: Mapping[str, Any], disabled_by: ConfigEntryDisabler | None = None, domain: str, entry_id: str | None = None, minor_version: int, + modified_at: datetime | None = None, options: Mapping[str, Any] | None, pref_disable_new_entities: bool | None = None, pref_disable_polling: bool | None = None, @@ -415,6 +421,8 @@ class ConfigEntry(Generic[_DataT]): _setter(self, "_integration_for_domain", None) _setter(self, "_tries", 0) + _setter(self, "created_at", created_at or utcnow()) + _setter(self, "modified_at", modified_at or utcnow()) def __repr__(self) -> str: """Representation of ConfigEntry.""" @@ -483,8 +491,10 @@ class ConfigEntry(Generic[_DataT]): def as_json_fragment(self) -> json_fragment: """Return JSON fragment of a config entry.""" json_repr = { + "created_at": self.created_at.timestamp(), "entry_id": self.entry_id, "domain": self.domain, + "modified_at": self.modified_at.timestamp(), "title": self.title, "source": self.source, "state": self.state.value, @@ -831,6 +841,10 @@ class ConfigEntry(Generic[_DataT]): async def async_remove(self, hass: HomeAssistant) -> None: """Invoke remove callback on component.""" + old_modified_at = self.modified_at + object.__setattr__(self, "modified_at", utcnow()) + self.clear_cache() + if self.source == SOURCE_IGNORE: return @@ -862,6 +876,8 @@ class ConfigEntry(Generic[_DataT]): self.title, integration.domain, ) + # Restore modified_at + object.__setattr__(self, "modified_at", old_modified_at) @callback def _async_set_state( @@ -950,11 +966,13 @@ class ConfigEntry(Generic[_DataT]): def as_dict(self) -> dict[str, Any]: """Return dictionary version of this entry.""" return { + "created_at": self.created_at.isoformat(), "data": dict(self.data), "disabled_by": self.disabled_by, "domain": self.domain, "entry_id": self.entry_id, "minor_version": self.minor_version, + "modified_at": self.modified_at.isoformat(), "options": dict(self.options), "pref_disable_new_entities": self.pref_disable_new_entities, "pref_disable_polling": self.pref_disable_polling, @@ -1599,25 +1617,34 @@ class ConfigEntryStore(storage.Store[dict[str, list[dict[str, Any]]]]): ) -> dict[str, Any]: """Migrate to the new version.""" data = old_data - if old_major_version == 1 and old_minor_version < 2: - # Version 1.2 implements migration and freezes the available keys - for entry in data["entries"]: - # Populate keys which were introduced before version 1.2 + if old_major_version == 1: + if old_minor_version < 2: + # Version 1.2 implements migration and freezes the available keys + for entry in data["entries"]: + # Populate keys which were introduced before version 1.2 - pref_disable_new_entities = entry.get("pref_disable_new_entities") - if pref_disable_new_entities is None and "system_options" in entry: - pref_disable_new_entities = entry.get("system_options", {}).get( - "disable_new_entities" + pref_disable_new_entities = entry.get("pref_disable_new_entities") + if pref_disable_new_entities is None and "system_options" in entry: + pref_disable_new_entities = entry.get("system_options", {}).get( + "disable_new_entities" + ) + + entry.setdefault("disabled_by", entry.get("disabled_by")) + entry.setdefault("minor_version", entry.get("minor_version", 1)) + entry.setdefault("options", entry.get("options", {})) + entry.setdefault( + "pref_disable_new_entities", pref_disable_new_entities ) + entry.setdefault( + "pref_disable_polling", entry.get("pref_disable_polling") + ) + entry.setdefault("unique_id", entry.get("unique_id")) - entry.setdefault("disabled_by", entry.get("disabled_by")) - entry.setdefault("minor_version", entry.get("minor_version", 1)) - entry.setdefault("options", entry.get("options", {})) - entry.setdefault("pref_disable_new_entities", pref_disable_new_entities) - entry.setdefault( - "pref_disable_polling", entry.get("pref_disable_polling") - ) - entry.setdefault("unique_id", entry.get("unique_id")) + if old_minor_version < 3: + # Version 1.3 adds the created_at and modified_at fields + created_at = utc_from_timestamp(0).isoformat() + for entry in data["entries"]: + entry["created_at"] = entry["modified_at"] = created_at if old_major_version > 1: raise NotImplementedError @@ -1793,11 +1820,13 @@ class ConfigEntries: entry_id = entry["entry_id"] config_entry = ConfigEntry( + created_at=datetime.fromisoformat(entry["created_at"]), data=entry["data"], disabled_by=try_parse_enum(ConfigEntryDisabler, entry["disabled_by"]), domain=entry["domain"], entry_id=entry_id, minor_version=entry["minor_version"], + modified_at=datetime.fromisoformat(entry["modified_at"]), options=entry["options"], pref_disable_new_entities=entry["pref_disable_new_entities"], pref_disable_polling=entry["pref_disable_polling"], @@ -2014,6 +2043,8 @@ class ConfigEntries: if not changed: return False + _setter(entry, "modified_at", utcnow()) + for listener in entry.update_listeners: self.hass.async_create_task( listener(self.hass, entry), diff --git a/tests/components/aemet/test_diagnostics.py b/tests/components/aemet/test_diagnostics.py index 0d94995a85b..6d007dd0465 100644 --- a/tests/components/aemet/test_diagnostics.py +++ b/tests/components/aemet/test_diagnostics.py @@ -4,6 +4,7 @@ from unittest.mock import patch import pytest from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.aemet.const import DOMAIN from homeassistant.core import HomeAssistant @@ -30,4 +31,4 @@ async def test_config_entry_diagnostics( return_value={}, ): result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/airly/test_diagnostics.py b/tests/components/airly/test_diagnostics.py index 7364824e594..9a61bf5abee 100644 --- a/tests/components/airly/test_diagnostics.py +++ b/tests/components/airly/test_diagnostics.py @@ -1,6 +1,7 @@ """Test Airly diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -22,4 +23,4 @@ async def test_entry_diagnostics( result = await get_diagnostics_for_config_entry(hass, hass_client, entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/airnow/test_diagnostics.py b/tests/components/airnow/test_diagnostics.py index 7329398e789..eb79dabe51a 100644 --- a/tests/components/airnow/test_diagnostics.py +++ b/tests/components/airnow/test_diagnostics.py @@ -4,6 +4,7 @@ from unittest.mock import patch import pytest from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -27,7 +28,6 @@ async def test_entry_diagnostics( return_value="PST", ): assert await hass.config_entries.async_setup(config_entry.entry_id) - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/airvisual/test_diagnostics.py b/tests/components/airvisual/test_diagnostics.py index 072e4559705..0253f102c59 100644 --- a/tests/components/airvisual/test_diagnostics.py +++ b/tests/components/airvisual/test_diagnostics.py @@ -1,6 +1,7 @@ """Test AirVisual diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -16,7 +17,6 @@ async def test_entry_diagnostics( snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/airvisual_pro/test_diagnostics.py b/tests/components/airvisual_pro/test_diagnostics.py index dd87d00be30..372b62eaf38 100644 --- a/tests/components/airvisual_pro/test_diagnostics.py +++ b/tests/components/airvisual_pro/test_diagnostics.py @@ -1,6 +1,7 @@ """Test AirVisual Pro diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -16,7 +17,6 @@ async def test_entry_diagnostics( snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/airzone/test_diagnostics.py b/tests/components/airzone/test_diagnostics.py index 6a03b9f1985..bca75bca778 100644 --- a/tests/components/airzone/test_diagnostics.py +++ b/tests/components/airzone/test_diagnostics.py @@ -4,6 +4,7 @@ from unittest.mock import patch from aioairzone.const import RAW_HVAC, RAW_VERSION, RAW_WEBSERVER from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.airzone.const import DOMAIN from homeassistant.core import HomeAssistant @@ -37,4 +38,4 @@ async def test_config_entry_diagnostics( }, ): result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/airzone_cloud/test_diagnostics.py b/tests/components/airzone_cloud/test_diagnostics.py index 254dba16b09..d3e23fc7f4b 100644 --- a/tests/components/airzone_cloud/test_diagnostics.py +++ b/tests/components/airzone_cloud/test_diagnostics.py @@ -15,6 +15,7 @@ from aioairzone_cloud.const import ( RAW_WEBSERVERS, ) from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.airzone_cloud.const import DOMAIN from homeassistant.const import CONF_ID @@ -111,4 +112,4 @@ async def test_config_entry_diagnostics( return_value=RAW_DATA_MOCK, ): result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/ambient_station/test_diagnostics.py b/tests/components/ambient_station/test_diagnostics.py index 05161ba32cd..82db72eb9ca 100644 --- a/tests/components/ambient_station/test_diagnostics.py +++ b/tests/components/ambient_station/test_diagnostics.py @@ -1,6 +1,7 @@ """Test Ambient PWS diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.ambient_station import AmbientStationConfigEntry from homeassistant.core import HomeAssistant @@ -20,7 +21,6 @@ async def test_entry_diagnostics( """Test config entry diagnostics.""" ambient = config_entry.runtime_data ambient.stations = data_station - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/axis/test_diagnostics.py b/tests/components/axis/test_diagnostics.py index 07caf5b39de..e96ba88c2cd 100644 --- a/tests/components/axis/test_diagnostics.py +++ b/tests/components/axis/test_diagnostics.py @@ -2,6 +2,7 @@ import pytest from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -20,7 +21,6 @@ async def test_entry_diagnostics( snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry_setup) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry_setup + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/blink/test_diagnostics.py b/tests/components/blink/test_diagnostics.py index 3b120d23038..d527633d4c9 100644 --- a/tests/components/blink/test_diagnostics.py +++ b/tests/components/blink/test_diagnostics.py @@ -31,4 +31,4 @@ async def test_entry_diagnostics( hass, hass_client, mock_config_entry ) - assert result == snapshot(exclude=props("entry_id")) + assert result == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/components/braviatv/test_diagnostics.py b/tests/components/braviatv/test_diagnostics.py index 13f6c92fb76..a7bd1631788 100644 --- a/tests/components/braviatv/test_diagnostics.py +++ b/tests/components/braviatv/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import patch from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.braviatv.const import CONF_USE_PSK, DOMAIN from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PIN @@ -71,4 +72,4 @@ async def test_entry_diagnostics( assert await async_setup_component(hass, DOMAIN, {}) result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/co2signal/test_diagnostics.py b/tests/components/co2signal/test_diagnostics.py index edc0007952b..3d5e1a0580b 100644 --- a/tests/components/co2signal/test_diagnostics.py +++ b/tests/components/co2signal/test_diagnostics.py @@ -2,6 +2,7 @@ import pytest from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -20,4 +21,4 @@ async def test_entry_diagnostics( """Test config entry diagnostics.""" result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/coinbase/test_diagnostics.py b/tests/components/coinbase/test_diagnostics.py index e30bdef30b8..0e06c172c37 100644 --- a/tests/components/coinbase/test_diagnostics.py +++ b/tests/components/coinbase/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import patch from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -40,4 +41,4 @@ async def test_entry_diagnostics( result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index b184fedf928..a4dc91d5355 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -6,6 +6,7 @@ from http import HTTPStatus from unittest.mock import ANY, AsyncMock, patch from aiohttp.test_utils import TestClient +from freezegun.api import FrozenDateTimeFactory import pytest import voluptuous as vol @@ -18,6 +19,7 @@ from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers import config_entry_flow, config_validation as cv from homeassistant.loader import IntegrationNotFound from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow from tests.common import ( MockConfigEntry, @@ -69,6 +71,7 @@ def mock_flow() -> Generator[None]: yield +@pytest.mark.usefixtures("freezer") @pytest.mark.usefixtures("clear_handlers", "mock_flow") async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None: """Test get entries.""" @@ -124,12 +127,15 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None: data = await resp.json() for entry in data: entry.pop("entry_id") + timestamp = utcnow().timestamp() assert data == [ { + "created_at": timestamp, "disabled_by": None, "domain": "comp1", "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -142,10 +148,12 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None: "title": "Test 1", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp2", "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": "Unsupported API", @@ -158,10 +166,12 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None: "title": "Test 2", }, { + "created_at": timestamp, "disabled_by": core_ce.ConfigEntryDisabler.USER, "domain": "comp3", "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -174,10 +184,12 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None: "title": "Test 3", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp4", "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -190,10 +202,12 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None: "title": "Test 4", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp5", "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -509,7 +523,7 @@ async def test_abort(hass: HomeAssistant, client: TestClient) -> None: } -@pytest.mark.usefixtures("enable_custom_integrations") +@pytest.mark.usefixtures("enable_custom_integrations", "freezer") async def test_create_account(hass: HomeAssistant, client: TestClient) -> None: """Test a flow that creates an account.""" mock_platform(hass, "test.config_flow", None) @@ -536,6 +550,7 @@ async def test_create_account(hass: HomeAssistant, client: TestClient) -> None: entries = hass.config_entries.async_entries("test") assert len(entries) == 1 + timestamp = utcnow().timestamp() data = await resp.json() data.pop("flow_id") assert data == { @@ -544,11 +559,13 @@ async def test_create_account(hass: HomeAssistant, client: TestClient) -> None: "type": "create_entry", "version": 1, "result": { + "created_at": timestamp, "disabled_by": None, "domain": "test", "entry_id": entries[0].entry_id, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -567,7 +584,7 @@ async def test_create_account(hass: HomeAssistant, client: TestClient) -> None: } -@pytest.mark.usefixtures("enable_custom_integrations") +@pytest.mark.usefixtures("enable_custom_integrations", "freezer") async def test_two_step_flow(hass: HomeAssistant, client: TestClient) -> None: """Test we can finish a two step flow.""" mock_integration( @@ -616,6 +633,7 @@ async def test_two_step_flow(hass: HomeAssistant, client: TestClient) -> None: entries = hass.config_entries.async_entries("test") assert len(entries) == 1 + timestamp = utcnow().timestamp() data = await resp.json() data.pop("flow_id") assert data == { @@ -624,11 +642,13 @@ async def test_two_step_flow(hass: HomeAssistant, client: TestClient) -> None: "title": "user-title", "version": 1, "result": { + "created_at": timestamp, "disabled_by": None, "domain": "test", "entry_id": entries[0].entry_id, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1059,6 +1079,7 @@ async def test_options_flow_with_invalid_data( assert data == {"errors": {"choices": "invalid is not a valid option"}} +@pytest.mark.usefixtures("freezer") async def test_get_single( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: @@ -1080,13 +1101,16 @@ async def test_get_single( ) response = await ws_client.receive_json() + timestamp = utcnow().timestamp() assert response["success"] assert response["result"]["config_entry"] == { + "created_at": timestamp, "disabled_by": None, "domain": "test", "entry_id": entry.entry_id, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1366,7 +1390,7 @@ async def test_ignore_flow_nonexisting( assert response["error"]["code"] == "not_found" -@pytest.mark.usefixtures("clear_handlers") +@pytest.mark.usefixtures("clear_handlers", "freezer") async def test_get_matching_entries_ws( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: @@ -1420,13 +1444,16 @@ async def test_get_matching_entries_ws( await ws_client.send_json_auto_id({"type": "config_entries/get"}) response = await ws_client.receive_json() + timestamp = utcnow().timestamp() assert response["result"] == [ { + "created_at": timestamp, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1439,11 +1466,13 @@ async def test_get_matching_entries_ws( "title": "Test 1", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp2", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": "Unsupported API", @@ -1456,11 +1485,13 @@ async def test_get_matching_entries_ws( "title": "Test 2", }, { + "created_at": timestamp, "disabled_by": "user", "domain": "comp3", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1473,11 +1504,13 @@ async def test_get_matching_entries_ws( "title": "Test 3", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp4", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1490,11 +1523,13 @@ async def test_get_matching_entries_ws( "title": "Test 4", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp5", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1518,11 +1553,13 @@ async def test_get_matching_entries_ws( response = await ws_client.receive_json() assert response["result"] == [ { + "created_at": timestamp, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1545,11 +1582,13 @@ async def test_get_matching_entries_ws( response = await ws_client.receive_json() assert response["result"] == [ { + "created_at": timestamp, "disabled_by": None, "domain": "comp4", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1562,11 +1601,13 @@ async def test_get_matching_entries_ws( "title": "Test 4", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp5", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1589,11 +1630,13 @@ async def test_get_matching_entries_ws( response = await ws_client.receive_json() assert response["result"] == [ { + "created_at": timestamp, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1606,11 +1649,13 @@ async def test_get_matching_entries_ws( "title": "Test 1", }, { + "created_at": timestamp, "disabled_by": "user", "domain": "comp3", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1639,11 +1684,13 @@ async def test_get_matching_entries_ws( assert response["result"] == [ { + "created_at": timestamp, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1656,11 +1703,13 @@ async def test_get_matching_entries_ws( "title": "Test 1", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp2", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": "Unsupported API", @@ -1673,11 +1722,13 @@ async def test_get_matching_entries_ws( "title": "Test 2", }, { + "created_at": timestamp, "disabled_by": "user", "domain": "comp3", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1690,11 +1741,13 @@ async def test_get_matching_entries_ws( "title": "Test 3", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp4", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1707,11 +1760,13 @@ async def test_get_matching_entries_ws( "title": "Test 4", }, { + "created_at": timestamp, "disabled_by": None, "domain": "comp5", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1759,7 +1814,9 @@ async def test_get_matching_entries_ws( @pytest.mark.usefixtures("clear_handlers") async def test_subscribe_entries_ws( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + freezer: FrozenDateTimeFactory, ) -> None: """Test subscribe entries with the websocket api.""" assert await async_setup_component(hass, "config", {}) @@ -1805,15 +1862,18 @@ async def test_subscribe_entries_ws( assert response["type"] == "result" response = await ws_client.receive_json() assert response["id"] == 5 + created = utcnow().timestamp() assert response["event"] == [ { "type": None, "entry": { + "created_at": created, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": created, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1829,11 +1889,13 @@ async def test_subscribe_entries_ws( { "type": None, "entry": { + "created_at": created, "disabled_by": None, "domain": "comp2", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": created, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": "Unsupported API", @@ -1849,11 +1911,13 @@ async def test_subscribe_entries_ws( { "type": None, "entry": { + "created_at": created, "disabled_by": "user", "domain": "comp3", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": created, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1867,17 +1931,21 @@ async def test_subscribe_entries_ws( }, }, ] + freezer.tick() + modified = utcnow().timestamp() assert hass.config_entries.async_update_entry(entry, title="changed") response = await ws_client.receive_json() assert response["id"] == 5 assert response["event"] == [ { "entry": { + "created_at": created, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": modified, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1892,17 +1960,21 @@ async def test_subscribe_entries_ws( "type": "updated", } ] + freezer.tick() + modified = utcnow().timestamp() await hass.config_entries.async_remove(entry.entry_id) response = await ws_client.receive_json() assert response["id"] == 5 assert response["event"] == [ { "entry": { + "created_at": created, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": modified, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1917,17 +1989,20 @@ async def test_subscribe_entries_ws( "type": "removed", } ] + freezer.tick() await hass.config_entries.async_add(entry) response = await ws_client.receive_json() assert response["id"] == 5 assert response["event"] == [ { "entry": { + "created_at": entry.created_at.timestamp(), "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": entry.modified_at.timestamp(), "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -1946,9 +2021,12 @@ async def test_subscribe_entries_ws( @pytest.mark.usefixtures("clear_handlers") async def test_subscribe_entries_ws_filtered( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + freezer: FrozenDateTimeFactory, ) -> None: """Test subscribe entries with the websocket api with a type filter.""" + created = utcnow().timestamp() assert await async_setup_component(hass, "config", {}) mock_integration(hass, MockModule("comp1")) mock_integration( @@ -2008,11 +2086,13 @@ async def test_subscribe_entries_ws_filtered( { "type": None, "entry": { + "created_at": created, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": created, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -2028,11 +2108,13 @@ async def test_subscribe_entries_ws_filtered( { "type": None, "entry": { + "created_at": created, "disabled_by": "user", "domain": "comp3", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": created, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -2046,6 +2128,8 @@ async def test_subscribe_entries_ws_filtered( }, }, ] + freezer.tick() + modified = utcnow().timestamp() assert hass.config_entries.async_update_entry(entry, title="changed") assert hass.config_entries.async_update_entry(entry3, title="changed too") assert hass.config_entries.async_update_entry(entry4, title="changed but ignored") @@ -2054,11 +2138,13 @@ async def test_subscribe_entries_ws_filtered( assert response["event"] == [ { "entry": { + "created_at": created, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": modified, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -2078,11 +2164,13 @@ async def test_subscribe_entries_ws_filtered( assert response["event"] == [ { "entry": { + "created_at": created, "disabled_by": "user", "domain": "comp3", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": modified, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -2097,6 +2185,8 @@ async def test_subscribe_entries_ws_filtered( "type": "updated", } ] + freezer.tick() + modified = utcnow().timestamp() await hass.config_entries.async_remove(entry.entry_id) await hass.config_entries.async_remove(entry2.entry_id) response = await ws_client.receive_json() @@ -2104,11 +2194,13 @@ async def test_subscribe_entries_ws_filtered( assert response["event"] == [ { "entry": { + "created_at": created, "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": modified, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -2123,17 +2215,20 @@ async def test_subscribe_entries_ws_filtered( "type": "removed", } ] + freezer.tick() await hass.config_entries.async_add(entry) response = await ws_client.receive_json() assert response["id"] == 5 assert response["event"] == [ { "entry": { + "created_at": entry.created_at.timestamp(), "disabled_by": None, "domain": "comp1", "entry_id": ANY, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": entry.modified_at.timestamp(), "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, @@ -2238,8 +2333,11 @@ async def test_flow_with_multiple_schema_errors_base( } -@pytest.mark.usefixtures("enable_custom_integrations") -async def test_supports_reconfigure(hass: HomeAssistant, client: TestClient) -> None: +@pytest.mark.usefixtures("enable_custom_integrations", "freezer") +async def test_supports_reconfigure( + hass: HomeAssistant, + client: TestClient, +) -> None: """Test a flow that support reconfigure step.""" mock_platform(hass, "test.config_flow", None) @@ -2297,6 +2395,7 @@ async def test_supports_reconfigure(hass: HomeAssistant, client: TestClient) -> assert len(entries) == 1 data = await resp.json() + timestamp = utcnow().timestamp() data.pop("flow_id") assert data == { "handler": "test", @@ -2304,11 +2403,13 @@ async def test_supports_reconfigure(hass: HomeAssistant, client: TestClient) -> "type": "create_entry", "version": 1, "result": { + "created_at": timestamp, "disabled_by": None, "domain": "test", "entry_id": entries[0].entry_id, "error_reason_translation_key": None, "error_reason_translation_placeholders": None, + "modified_at": timestamp, "pref_disable_new_entities": False, "pref_disable_polling": False, "reason": None, diff --git a/tests/components/deconz/test_diagnostics.py b/tests/components/deconz/test_diagnostics.py index a490c95d5e6..2abc6d83995 100644 --- a/tests/components/deconz/test_diagnostics.py +++ b/tests/components/deconz/test_diagnostics.py @@ -2,6 +2,7 @@ from pydeconz.websocket import State from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -23,7 +24,6 @@ async def test_entry_diagnostics( await mock_websocket_state(State.RUNNING) await hass.async_block_till_done() - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry_setup) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry_setup + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/devolo_home_control/test_diagnostics.py b/tests/components/devolo_home_control/test_diagnostics.py index f52a9d49017..dfadc4d1c4b 100644 --- a/tests/components/devolo_home_control/test_diagnostics.py +++ b/tests/components/devolo_home_control/test_diagnostics.py @@ -5,6 +5,7 @@ from __future__ import annotations from unittest.mock import patch from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant @@ -35,4 +36,4 @@ async def test_entry_diagnostics( assert entry.state is ConfigEntryState.LOADED result = await get_diagnostics_for_config_entry(hass, hass_client, entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/devolo_home_network/test_diagnostics.py b/tests/components/devolo_home_network/test_diagnostics.py index a3580cac954..05d3c594677 100644 --- a/tests/components/devolo_home_network/test_diagnostics.py +++ b/tests/components/devolo_home_network/test_diagnostics.py @@ -4,6 +4,7 @@ from __future__ import annotations import pytest from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant @@ -28,4 +29,4 @@ async def test_entry_diagnostics( assert entry.state is ConfigEntryState.LOADED result = await get_diagnostics_for_config_entry(hass, hass_client, entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/dsmr_reader/test_diagnostics.py b/tests/components/dsmr_reader/test_diagnostics.py index 553efd0b38b..793fe1362b0 100644 --- a/tests/components/dsmr_reader/test_diagnostics.py +++ b/tests/components/dsmr_reader/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import patch from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.dsmr_reader.const import DOMAIN from homeassistant.core import HomeAssistant @@ -36,4 +37,4 @@ async def test_get_config_entry_diagnostics( diagnostics = await get_diagnostics_for_config_entry( hass, hass_client, config_entry ) - assert diagnostics == snapshot + assert diagnostics == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/ecovacs/test_diagnostics.py b/tests/components/ecovacs/test_diagnostics.py index b025db43cc0..6e4dcd5f677 100644 --- a/tests/components/ecovacs/test_diagnostics.py +++ b/tests/components/ecovacs/test_diagnostics.py @@ -28,4 +28,4 @@ async def test_diagnostics( """Test diagnostics.""" assert await get_diagnostics_for_config_entry( hass, hass_client, init_integration - ) == snapshot(exclude=props("entry_id")) + ) == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/components/esphome/test_diagnostics.py b/tests/components/esphome/test_diagnostics.py index 03689a5699e..b66b6d72fce 100644 --- a/tests/components/esphome/test_diagnostics.py +++ b/tests/components/esphome/test_diagnostics.py @@ -5,6 +5,7 @@ from unittest.mock import ANY import pytest from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components import bluetooth from homeassistant.core import HomeAssistant @@ -27,7 +28,7 @@ async def test_diagnostics( """Test diagnostics for config entry.""" result = await get_diagnostics_for_config_entry(hass, hass_client, init_integration) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) async def test_diagnostics_with_bluetooth( @@ -61,6 +62,7 @@ async def test_diagnostics_with_bluetooth( }, }, "config": { + "created_at": ANY, "data": { "device_name": "test", "host": "test.local", @@ -71,6 +73,7 @@ async def test_diagnostics_with_bluetooth( "domain": "esphome", "entry_id": ANY, "minor_version": 1, + "modified_at": ANY, "options": {"allow_service_calls": False}, "pref_disable_new_entities": False, "pref_disable_polling": False, diff --git a/tests/components/fronius/test_diagnostics.py b/tests/components/fronius/test_diagnostics.py index 7b1f384e405..ddef5b4a18c 100644 --- a/tests/components/fronius/test_diagnostics.py +++ b/tests/components/fronius/test_diagnostics.py @@ -1,6 +1,7 @@ """Tests for the diagnostics data provided by the Fronius integration.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -21,11 +22,8 @@ async def test_diagnostics( mock_responses(aioclient_mock) entry = await setup_fronius_integration(hass) - assert ( - await get_diagnostics_for_config_entry( - hass, - hass_client, - entry, - ) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, + hass_client, + entry, + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/fyta/test_diagnostics.py b/tests/components/fyta/test_diagnostics.py index 3a95b533489..cfaa5484b82 100644 --- a/tests/components/fyta/test_diagnostics.py +++ b/tests/components/fyta/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import AsyncMock from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -28,4 +29,4 @@ async def test_entry_diagnostics( hass, hass_client, mock_config_entry ) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/gios/test_diagnostics.py b/tests/components/gios/test_diagnostics.py index 903de4872a2..a965e5550df 100644 --- a/tests/components/gios/test_diagnostics.py +++ b/tests/components/gios/test_diagnostics.py @@ -1,6 +1,7 @@ """Test GIOS diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -18,4 +19,6 @@ async def test_entry_diagnostics( """Test config entry diagnostics.""" entry = await init_integration(hass) - assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( + exclude=props("created_at", "modified_at") + ) diff --git a/tests/components/goodwe/test_diagnostics.py b/tests/components/goodwe/test_diagnostics.py index 21917265811..0a997edc594 100644 --- a/tests/components/goodwe/test_diagnostics.py +++ b/tests/components/goodwe/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.goodwe import CONF_MODEL_FAMILY, DOMAIN from homeassistant.const import CONF_HOST @@ -32,4 +33,4 @@ async def test_entry_diagnostics( assert await async_setup_component(hass, DOMAIN, {}) result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/google_assistant/test_diagnostics.py b/tests/components/google_assistant/test_diagnostics.py index 26d91ce7920..1d68079563c 100644 --- a/tests/components/google_assistant/test_diagnostics.py +++ b/tests/components/google_assistant/test_diagnostics.py @@ -50,4 +50,4 @@ async def test_diagnostics( config_entry = hass.config_entries.async_entries("google_assistant")[0] assert await get_diagnostics_for_config_entry( hass, hass_client, config_entry - ) == snapshot(exclude=props("entry_id")) + ) == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/components/guardian/test_diagnostics.py b/tests/components/guardian/test_diagnostics.py index 6ec7376f3ef..3b3ed21bc65 100644 --- a/tests/components/guardian/test_diagnostics.py +++ b/tests/components/guardian/test_diagnostics.py @@ -4,7 +4,7 @@ from homeassistant.components.diagnostics import REDACTED from homeassistant.components.guardian import DOMAIN, GuardianData from homeassistant.core import HomeAssistant -from tests.common import MockConfigEntry +from tests.common import ANY, MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -39,6 +39,8 @@ async def test_entry_diagnostics( "source": "user", "unique_id": REDACTED, "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, "data": { "valve_controller": { diff --git a/tests/components/husqvarna_automower/test_diagnostics.py b/tests/components/husqvarna_automower/test_diagnostics.py index eeb6b46e6c4..3166b09f1ee 100644 --- a/tests/components/husqvarna_automower/test_diagnostics.py +++ b/tests/components/husqvarna_automower/test_diagnostics.py @@ -5,6 +5,7 @@ from unittest.mock import AsyncMock import pytest from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.husqvarna_automower.const import DOMAIN from homeassistant.core import HomeAssistant @@ -36,7 +37,7 @@ async def test_entry_diagnostics( result = await get_diagnostics_for_config_entry( hass, hass_client, mock_config_entry ) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) @pytest.mark.freeze_time(datetime.datetime(2024, 2, 29, 11, tzinfo=datetime.UTC)) diff --git a/tests/components/imgw_pib/test_diagnostics.py b/tests/components/imgw_pib/test_diagnostics.py index 62dabc982c4..14d4e7a5224 100644 --- a/tests/components/imgw_pib/test_diagnostics.py +++ b/tests/components/imgw_pib/test_diagnostics.py @@ -28,4 +28,4 @@ async def test_entry_diagnostics( hass, hass_client, mock_config_entry ) - assert result == snapshot(exclude=props("entry_id")) + assert result == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/components/iqvia/test_diagnostics.py b/tests/components/iqvia/test_diagnostics.py index 21935a81e86..9d5639c311c 100644 --- a/tests/components/iqvia/test_diagnostics.py +++ b/tests/components/iqvia/test_diagnostics.py @@ -1,6 +1,7 @@ """Test IQVIA diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -18,7 +19,6 @@ async def test_entry_diagnostics( ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/kostal_plenticore/test_diagnostics.py b/tests/components/kostal_plenticore/test_diagnostics.py index 1c3a9efe2e5..0f358260be7 100644 --- a/tests/components/kostal_plenticore/test_diagnostics.py +++ b/tests/components/kostal_plenticore/test_diagnostics.py @@ -6,7 +6,7 @@ from homeassistant.components.diagnostics import REDACTED from homeassistant.components.kostal_plenticore.coordinator import Plenticore from homeassistant.core import HomeAssistant -from tests.common import MockConfigEntry +from tests.common import ANY, MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -54,6 +54,8 @@ async def test_entry_diagnostics( "source": "user", "unique_id": None, "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, "client": { "version": "api_version='0.2.0' hostname='scb' name='PUCK RESTful API' sw_version='01.16.05025'", diff --git a/tests/components/lacrosse_view/test_diagnostics.py b/tests/components/lacrosse_view/test_diagnostics.py index 08cef64a935..dc48f160113 100644 --- a/tests/components/lacrosse_view/test_diagnostics.py +++ b/tests/components/lacrosse_view/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import patch from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.lacrosse_view import DOMAIN from homeassistant.core import HomeAssistant @@ -32,7 +33,6 @@ async def test_entry_diagnostics( assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/linear_garage_door/test_diagnostics.py b/tests/components/linear_garage_door/test_diagnostics.py index 6bf7415bde5..a00feed43ff 100644 --- a/tests/components/linear_garage_door/test_diagnostics.py +++ b/tests/components/linear_garage_door/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import AsyncMock from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -25,4 +26,4 @@ async def test_entry_diagnostics( result = await get_diagnostics_for_config_entry( hass, hass_client, mock_config_entry ) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/melcloud/test_diagnostics.py b/tests/components/melcloud/test_diagnostics.py index cbb35eadfd4..32ec94a54d1 100644 --- a/tests/components/melcloud/test_diagnostics.py +++ b/tests/components/melcloud/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import patch from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.melcloud.const import DOMAIN from homeassistant.core import HomeAssistant @@ -36,4 +37,4 @@ async def test_get_config_entry_diagnostics( diagnostics = await get_diagnostics_for_config_entry( hass, hass_client, config_entry ) - assert diagnostics == snapshot + assert diagnostics == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/netatmo/test_diagnostics.py b/tests/components/netatmo/test_diagnostics.py index 48f021295e1..7a0bf11c652 100644 --- a/tests/components/netatmo/test_diagnostics.py +++ b/tests/components/netatmo/test_diagnostics.py @@ -42,4 +42,11 @@ async def test_entry_diagnostics( assert await get_diagnostics_for_config_entry( hass, hass_client, config_entry - ) == snapshot(exclude=paths("info.data.token.expires_at", "info.entry_id")) + ) == snapshot( + exclude=paths( + "info.data.token.expires_at", + "info.entry_id", + "info.created_at", + "info.modified_at", + ) + ) diff --git a/tests/components/nextdns/test_diagnostics.py b/tests/components/nextdns/test_diagnostics.py index 7652bc4f03e..3bb1fc3ee67 100644 --- a/tests/components/nextdns/test_diagnostics.py +++ b/tests/components/nextdns/test_diagnostics.py @@ -1,6 +1,7 @@ """Test NextDNS diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -18,4 +19,6 @@ async def test_entry_diagnostics( """Test config entry diagnostics.""" entry = await init_integration(hass) - assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( + exclude=props("created_at", "modified_at") + ) diff --git a/tests/components/notion/test_diagnostics.py b/tests/components/notion/test_diagnostics.py index 023b9369f03..4d87b6292e4 100644 --- a/tests/components/notion/test_diagnostics.py +++ b/tests/components/notion/test_diagnostics.py @@ -4,6 +4,7 @@ from homeassistant.components.diagnostics import REDACTED from homeassistant.components.notion import DOMAIN from homeassistant.core import HomeAssistant +from tests.common import ANY from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -33,6 +34,8 @@ async def test_entry_diagnostics( "source": "user", "unique_id": REDACTED, "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, "data": { "bridges": [ diff --git a/tests/components/onvif/test_diagnostics.py b/tests/components/onvif/test_diagnostics.py index d58c8008ea6..ce8febe2341 100644 --- a/tests/components/onvif/test_diagnostics.py +++ b/tests/components/onvif/test_diagnostics.py @@ -1,6 +1,7 @@ """Test ONVIF diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -19,4 +20,6 @@ async def test_diagnostics( entry, _, _ = await setup_onvif_integration(hass) - assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( + exclude=props("created_at", "modified_at") + ) diff --git a/tests/components/openuv/test_diagnostics.py b/tests/components/openuv/test_diagnostics.py index 4b5114bccd1..4fe851eea53 100644 --- a/tests/components/openuv/test_diagnostics.py +++ b/tests/components/openuv/test_diagnostics.py @@ -4,6 +4,7 @@ from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import ANY from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -35,6 +36,8 @@ async def test_entry_diagnostics( "source": "user", "unique_id": REDACTED, "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, "data": { "protection_window": { diff --git a/tests/components/philips_js/test_diagnostics.py b/tests/components/philips_js/test_diagnostics.py index cb3235b9780..d61546e52c3 100644 --- a/tests/components/philips_js/test_diagnostics.py +++ b/tests/components/philips_js/test_diagnostics.py @@ -63,4 +63,4 @@ async def test_entry_diagnostics( hass, hass_client, mock_config_entry ) - assert result == snapshot(exclude=props("entry_id")) + assert result == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/components/pi_hole/test_diagnostics.py b/tests/components/pi_hole/test_diagnostics.py index c9fc9a0a9b8..8d5a83e4622 100644 --- a/tests/components/pi_hole/test_diagnostics.py +++ b/tests/components/pi_hole/test_diagnostics.py @@ -1,6 +1,7 @@ """Test pi_hole component.""" from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.components import pi_hole from homeassistant.core import HomeAssistant @@ -28,4 +29,6 @@ async def test_diagnostics( await hass.async_block_till_done() - assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( + exclude=props("created_at", "modified_at") + ) diff --git a/tests/components/proximity/test_diagnostics.py b/tests/components/proximity/test_diagnostics.py index a60c592fcab..e4f22236808 100644 --- a/tests/components/proximity/test_diagnostics.py +++ b/tests/components/proximity/test_diagnostics.py @@ -72,5 +72,12 @@ async def test_entry_diagnostics( assert await get_diagnostics_for_config_entry( hass, hass_client, mock_entry ) == snapshot( - exclude=props("entry_id", "last_changed", "last_reported", "last_updated") + exclude=props( + "entry_id", + "last_changed", + "last_reported", + "last_updated", + "created_at", + "modified_at", + ) ) diff --git a/tests/components/purpleair/test_diagnostics.py b/tests/components/purpleair/test_diagnostics.py index 13dcd1338e0..599549bb723 100644 --- a/tests/components/purpleair/test_diagnostics.py +++ b/tests/components/purpleair/test_diagnostics.py @@ -3,6 +3,7 @@ from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant +from tests.common import ANY from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -34,6 +35,8 @@ async def test_entry_diagnostics( "source": "user", "unique_id": REDACTED, "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, "data": { "fields": [ diff --git a/tests/components/rainmachine/test_diagnostics.py b/tests/components/rainmachine/test_diagnostics.py index 1fc03ab357a..ad5743957dd 100644 --- a/tests/components/rainmachine/test_diagnostics.py +++ b/tests/components/rainmachine/test_diagnostics.py @@ -2,6 +2,7 @@ from regenmaschine.errors import RainMachineError from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -17,10 +18,9 @@ async def test_entry_diagnostics( snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) async def test_entry_diagnostics_failed_controller_diagnostics( @@ -33,7 +33,6 @@ async def test_entry_diagnostics_failed_controller_diagnostics( ) -> None: """Test config entry diagnostics when the controller diagnostics API call fails.""" controller.diagnostics.current.side_effect = RainMachineError - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/recollect_waste/test_diagnostics.py b/tests/components/recollect_waste/test_diagnostics.py index 6c8549786e8..2b92892b1d1 100644 --- a/tests/components/recollect_waste/test_diagnostics.py +++ b/tests/components/recollect_waste/test_diagnostics.py @@ -5,6 +5,7 @@ from homeassistant.core import HomeAssistant from .conftest import TEST_SERVICE_ID +from tests.common import ANY from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -30,6 +31,8 @@ async def test_entry_diagnostics( "source": "user", "unique_id": REDACTED, "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, "data": [ { diff --git a/tests/components/ridwell/test_diagnostics.py b/tests/components/ridwell/test_diagnostics.py index adfbb525283..45683bba903 100644 --- a/tests/components/ridwell/test_diagnostics.py +++ b/tests/components/ridwell/test_diagnostics.py @@ -1,6 +1,7 @@ """Test Ridwell diagnostics.""" from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -16,7 +17,6 @@ async def test_entry_diagnostics( snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/samsungtv/test_diagnostics.py b/tests/components/samsungtv/test_diagnostics.py index 7b20002ae5b..b1bdf034bc1 100644 --- a/tests/components/samsungtv/test_diagnostics.py +++ b/tests/components/samsungtv/test_diagnostics.py @@ -16,6 +16,7 @@ from .const import ( SAMPLE_DEVICE_INFO_WIFI, ) +from tests.common import ANY from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -29,6 +30,7 @@ async def test_entry_diagnostics( assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "entry": { + "created_at": ANY, "data": { "host": "fake_host", "ip_address": "test", @@ -43,6 +45,7 @@ async def test_entry_diagnostics( "domain": "samsungtv", "entry_id": "123456", "minor_version": 2, + "modified_at": ANY, "options": {}, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -65,6 +68,7 @@ async def test_entry_diagnostics_encrypted( assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "entry": { + "created_at": ANY, "data": { "host": "fake_host", "ip_address": "test", @@ -80,6 +84,7 @@ async def test_entry_diagnostics_encrypted( "domain": "samsungtv", "entry_id": "123456", "minor_version": 2, + "modified_at": ANY, "options": {}, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -102,6 +107,7 @@ async def test_entry_diagnostics_encrypte_offline( assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "entry": { + "created_at": ANY, "data": { "host": "fake_host", "ip_address": "test", @@ -116,6 +122,7 @@ async def test_entry_diagnostics_encrypte_offline( "domain": "samsungtv", "entry_id": "123456", "minor_version": 2, + "modified_at": ANY, "options": {}, "pref_disable_new_entities": False, "pref_disable_polling": False, diff --git a/tests/components/screenlogic/test_diagnostics.py b/tests/components/screenlogic/test_diagnostics.py index c6d6ea60e87..77e1ce58dad 100644 --- a/tests/components/screenlogic/test_diagnostics.py +++ b/tests/components/screenlogic/test_diagnostics.py @@ -4,6 +4,7 @@ from unittest.mock import DEFAULT, patch from screenlogicpy import ScreenLogicGateway from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr @@ -56,4 +57,4 @@ async def test_diagnostics( hass, hass_client, mock_config_entry ) - assert diag == snapshot + assert diag == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/sensibo/snapshots/test_diagnostics.ambr b/tests/components/sensibo/snapshots/test_diagnostics.ambr index c911a7629be..a33209f7c88 100644 --- a/tests/components/sensibo/snapshots/test_diagnostics.ambr +++ b/tests/components/sensibo/snapshots/test_diagnostics.ambr @@ -1,246 +1,5 @@ # serializer version: 1 # name: test_diagnostics - dict({ - 'modes': dict({ - 'auto': dict({ - 'fanLevels': list([ - 'quiet', - 'low', - 'medium', - ]), - 'horizontalSwing': list([ - 'stopped', - 'fixedLeft', - 'fixedCenterLeft', - ]), - 'light': list([ - 'on', - 'off', - ]), - 'swing': list([ - 'stopped', - 'fixedTop', - 'fixedMiddleTop', - ]), - 'temperatures': dict({ - 'C': dict({ - 'isNative': True, - 'values': list([ - 10, - 16, - 17, - 18, - 19, - 20, - ]), - }), - 'F': dict({ - 'isNative': False, - 'values': list([ - 64, - 66, - 68, - ]), - }), - }), - }), - 'cool': dict({ - 'fanLevels': list([ - 'quiet', - 'low', - 'medium', - ]), - 'horizontalSwing': list([ - 'stopped', - 'fixedLeft', - 'fixedCenterLeft', - ]), - 'light': list([ - 'on', - 'off', - ]), - 'swing': list([ - 'stopped', - 'fixedTop', - 'fixedMiddleTop', - ]), - 'temperatures': dict({ - 'C': dict({ - 'isNative': True, - 'values': list([ - 10, - 16, - 17, - 18, - 19, - 20, - ]), - }), - 'F': dict({ - 'isNative': False, - 'values': list([ - 64, - 66, - 68, - ]), - }), - }), - }), - 'dry': dict({ - 'horizontalSwing': list([ - 'stopped', - 'fixedLeft', - 'fixedCenterLeft', - ]), - 'light': list([ - 'on', - 'off', - ]), - 'swing': list([ - 'stopped', - 'fixedTop', - 'fixedMiddleTop', - ]), - 'temperatures': dict({ - 'C': dict({ - 'isNative': True, - 'values': list([ - 10, - 16, - 17, - 18, - 19, - 20, - ]), - }), - 'F': dict({ - 'isNative': False, - 'values': list([ - 64, - 66, - 68, - ]), - }), - }), - }), - 'fan': dict({ - 'fanLevels': list([ - 'quiet', - 'low', - 'medium', - ]), - 'horizontalSwing': list([ - 'stopped', - 'fixedLeft', - 'fixedCenterLeft', - ]), - 'light': list([ - 'on', - 'off', - ]), - 'swing': list([ - 'stopped', - 'fixedTop', - 'fixedMiddleTop', - ]), - 'temperatures': dict({ - }), - }), - 'heat': dict({ - 'fanLevels': list([ - 'quiet', - 'low', - 'medium', - ]), - 'horizontalSwing': list([ - 'stopped', - 'fixedLeft', - 'fixedCenterLeft', - ]), - 'light': list([ - 'on', - 'off', - ]), - 'swing': list([ - 'stopped', - 'fixedTop', - 'fixedMiddleTop', - ]), - 'temperatures': dict({ - 'C': dict({ - 'isNative': True, - 'values': list([ - 10, - 16, - 17, - 18, - 19, - 20, - ]), - }), - 'F': dict({ - 'isNative': False, - 'values': list([ - 63, - 64, - 66, - ]), - }), - }), - }), - }), - }) -# --- -# name: test_diagnostics.1 - dict({ - 'low': 'low', - 'medium': 'medium', - 'quiet': 'quiet', - }) -# --- -# name: test_diagnostics.2 - dict({ - 'fixedmiddletop': 'fixedMiddleTop', - 'fixedtop': 'fixedTop', - 'stopped': 'stopped', - }) -# --- -# name: test_diagnostics.3 - dict({ - 'fixedcenterleft': 'fixedCenterLeft', - 'fixedleft': 'fixedLeft', - 'stopped': 'stopped', - }) -# --- -# name: test_diagnostics.4 - dict({ - 'fanlevel': 'low', - 'horizontalswing': 'stopped', - 'light': 'on', - 'mode': 'heat', - 'on': True, - 'swing': 'stopped', - 'targettemperature': 21, - 'temperatureunit': 'c', - }) -# --- -# name: test_diagnostics.5 - dict({ - 'fanlevel': 'high', - 'horizontalswing': 'stopped', - 'light': 'on', - 'mode': 'cool', - 'on': True, - 'swing': 'stopped', - 'targettemperature': 21, - 'temperatureunit': 'c', - }) -# --- -# name: test_diagnostics.6 - dict({ - }) -# --- -# name: test_diagnostics[full_snapshot] dict({ 'AAZZAAZZ': dict({ 'ac_states': dict({ diff --git a/tests/components/sensibo/test_diagnostics.py b/tests/components/sensibo/test_diagnostics.py index 1fe72cca0f3..0dc1f2c25e9 100644 --- a/tests/components/sensibo/test_diagnostics.py +++ b/tests/components/sensibo/test_diagnostics.py @@ -3,6 +3,7 @@ from __future__ import annotations from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -10,8 +11,6 @@ from homeassistant.core import HomeAssistant from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator -EXCLUDE_ATTRIBUTES = {"full_features"} - async def test_diagnostics( hass: HomeAssistant, @@ -24,16 +23,6 @@ async def test_diagnostics( diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) - assert diag["ABC999111"]["full_capabilities"] == snapshot - assert diag["ABC999111"]["fan_modes_translated"] == snapshot - assert diag["ABC999111"]["swing_modes_translated"] == snapshot - assert diag["ABC999111"]["horizontal_swing_modes_translated"] == snapshot - assert diag["ABC999111"]["smart_low_state"] == snapshot - assert diag["ABC999111"]["smart_high_state"] == snapshot - assert diag["ABC999111"]["pure_conf"] == snapshot - - def limit_attrs(prop, path): - exclude_attrs = EXCLUDE_ATTRIBUTES - return prop in exclude_attrs - - assert diag == snapshot(name="full_snapshot", exclude=limit_attrs) + assert diag == snapshot( + exclude=props("full_features", "created_at", "modified_at"), + ) diff --git a/tests/components/simplisafe/test_diagnostics.py b/tests/components/simplisafe/test_diagnostics.py index 6948f98b159..31bd44c6146 100644 --- a/tests/components/simplisafe/test_diagnostics.py +++ b/tests/components/simplisafe/test_diagnostics.py @@ -3,6 +3,7 @@ from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant +from tests.common import ANY from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -28,6 +29,8 @@ async def test_entry_diagnostics( "source": "user", "unique_id": REDACTED, "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, "subscription_data": { "12345": { diff --git a/tests/components/switcher_kis/test_diagnostics.py b/tests/components/switcher_kis/test_diagnostics.py index 107a48a1062..c8df4dd0b83 100644 --- a/tests/components/switcher_kis/test_diagnostics.py +++ b/tests/components/switcher_kis/test_diagnostics.py @@ -8,6 +8,7 @@ from homeassistant.core import HomeAssistant from . import init_integration from .consts import DUMMY_WATER_HEATER_DEVICE +from tests.common import ANY from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -64,5 +65,7 @@ async def test_diagnostics( "source": "user", "unique_id": "switcher_kis", "disabled_by": None, + "created_at": ANY, + "modified_at": ANY, }, } diff --git a/tests/components/systemmonitor/test_diagnostics.py b/tests/components/systemmonitor/test_diagnostics.py index 78128aad5f4..b0f4fca3d0c 100644 --- a/tests/components/systemmonitor/test_diagnostics.py +++ b/tests/components/systemmonitor/test_diagnostics.py @@ -23,4 +23,4 @@ async def test_diagnostics( """Test diagnostics.""" assert await get_diagnostics_for_config_entry( hass, hass_client, mock_added_config_entry - ) == snapshot(exclude=props("last_update", "entry_id")) + ) == snapshot(exclude=props("last_update", "entry_id", "created_at", "modified_at")) diff --git a/tests/components/tankerkoenig/test_diagnostics.py b/tests/components/tankerkoenig/test_diagnostics.py index 441268659f3..e7b479a0c32 100644 --- a/tests/components/tankerkoenig/test_diagnostics.py +++ b/tests/components/tankerkoenig/test_diagnostics.py @@ -4,6 +4,7 @@ from __future__ import annotations import pytest from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -21,4 +22,4 @@ async def test_entry_diagnostics( ) -> None: """Test config entry diagnostics.""" result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/tractive/test_diagnostics.py b/tests/components/tractive/test_diagnostics.py index cc4fcdeba15..ce07b4d6e2a 100644 --- a/tests/components/tractive/test_diagnostics.py +++ b/tests/components/tractive/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import AsyncMock from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -27,4 +28,4 @@ async def test_entry_diagnostics( hass, hass_client, mock_config_entry ) - assert result == snapshot + assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/twinkly/test_diagnostics.py b/tests/components/twinkly/test_diagnostics.py index 5cb9fc1fe9e..f9cf0bc562c 100644 --- a/tests/components/twinkly/test_diagnostics.py +++ b/tests/components/twinkly/test_diagnostics.py @@ -3,6 +3,7 @@ from collections.abc import Awaitable, Callable from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -26,4 +27,6 @@ async def test_diagnostics( await setup_integration() entry = hass.config_entries.async_entries(DOMAIN)[0] - assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( + exclude=props("created_at", "modified_at") + ) diff --git a/tests/components/unifi/test_diagnostics.py b/tests/components/unifi/test_diagnostics.py index fcaba59cbad..3963de2deb3 100644 --- a/tests/components/unifi/test_diagnostics.py +++ b/tests/components/unifi/test_diagnostics.py @@ -2,6 +2,7 @@ import pytest from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.components.unifi.const import ( CONF_ALLOW_BANDWIDTH_SENSORS, @@ -125,7 +126,6 @@ async def test_entry_diagnostics( snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry_setup) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry_setup + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/utility_meter/test_diagnostics.py b/tests/components/utility_meter/test_diagnostics.py index cefd17fc7e4..9ecabe813b1 100644 --- a/tests/components/utility_meter/test_diagnostics.py +++ b/tests/components/utility_meter/test_diagnostics.py @@ -4,6 +4,7 @@ from aiohttp.test_utils import TestClient from freezegun import freeze_time import pytest from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.auth.models import Credentials from homeassistant.components.utility_meter.const import DOMAIN @@ -45,11 +46,6 @@ def _get_test_client_generator( return auth_client -def limit_diagnostic_attrs(prop, path) -> bool: - """Mark attributes to exclude from diagnostic snapshot.""" - return prop in {"entry_id"} - - @freeze_time("2024-04-06 00:00:00+00:00") @pytest.mark.usefixtures("socket_enabled") async def test_diagnostics( @@ -125,4 +121,4 @@ async def test_diagnostics( hass, _get_test_client_generator(hass, aiohttp_client, new_token), config_entry ) - assert diag == snapshot(exclude=limit_diagnostic_attrs) + assert diag == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/components/v2c/test_diagnostics.py b/tests/components/v2c/test_diagnostics.py index 770b00e988b..eafbd68e6fc 100644 --- a/tests/components/v2c/test_diagnostics.py +++ b/tests/components/v2c/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import AsyncMock from syrupy import SnapshotAssertion +from syrupy.filters import props from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -24,7 +25,6 @@ async def test_entry_diagnostics( await init_integration(hass, mock_config_entry) - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry) - == snapshot() - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/vicare/test_diagnostics.py b/tests/components/vicare/test_diagnostics.py index 815b39545a9..6adf4fe0edc 100644 --- a/tests/components/vicare/test_diagnostics.py +++ b/tests/components/vicare/test_diagnostics.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -21,4 +22,4 @@ async def test_diagnostics( hass, hass_client, mock_vicare_gas_boiler ) - assert diag == snapshot + assert diag == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/watttime/test_diagnostics.py b/tests/components/watttime/test_diagnostics.py index 0526a64aedc..f4465a44d26 100644 --- a/tests/components/watttime/test_diagnostics.py +++ b/tests/components/watttime/test_diagnostics.py @@ -19,4 +19,4 @@ async def test_entry_diagnostics( """Test config entry diagnostics.""" assert await get_diagnostics_for_config_entry( hass, hass_client, config_entry - ) == snapshot(exclude=props("entry_id")) + ) == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/components/webmin/test_diagnostics.py b/tests/components/webmin/test_diagnostics.py index 5f1df44f4a8..98d6544bc76 100644 --- a/tests/components/webmin/test_diagnostics.py +++ b/tests/components/webmin/test_diagnostics.py @@ -1,6 +1,7 @@ """Tests for the diagnostics data provided by the Webmin integration.""" from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -16,9 +17,6 @@ async def test_diagnostics( snapshot: SnapshotAssertion, ) -> None: """Test diagnostics.""" - assert ( - await get_diagnostics_for_config_entry( - hass, hass_client, await async_init_integration(hass) - ) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, await async_init_integration(hass) + ) == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/webostv/test_diagnostics.py b/tests/components/webostv/test_diagnostics.py index 934b59a7b83..e2fbc43e187 100644 --- a/tests/components/webostv/test_diagnostics.py +++ b/tests/components/webostv/test_diagnostics.py @@ -58,5 +58,7 @@ async def test_diagnostics( "source": "user", "unique_id": REDACTED, "disabled_by": None, + "created_at": entry.created_at.isoformat(), + "modified_at": entry.modified_at.isoformat(), }, } diff --git a/tests/components/whirlpool/test_diagnostics.py b/tests/components/whirlpool/test_diagnostics.py index 6cfc1b76e38..2a0b2e6fd18 100644 --- a/tests/components/whirlpool/test_diagnostics.py +++ b/tests/components/whirlpool/test_diagnostics.py @@ -29,4 +29,4 @@ async def test_entry_diagnostics( result = await get_diagnostics_for_config_entry(hass, hass_client, mock_entry) - assert result == snapshot(exclude=props("entry_id")) + assert result == snapshot(exclude=props("entry_id", "created_at", "modified_at")) diff --git a/tests/snapshots/test_config_entries.ambr b/tests/snapshots/test_config_entries.ambr index bfb583ba8db..136749dfb14 100644 --- a/tests/snapshots/test_config_entries.ambr +++ b/tests/snapshots/test_config_entries.ambr @@ -1,12 +1,14 @@ # serializer version: 1 # name: test_as_dict dict({ + 'created_at': '2024-02-14T12:00:00+00:00', 'data': dict({ }), 'disabled_by': None, 'domain': 'test', 'entry_id': 'mock-entry', 'minor_version': 1, + 'modified_at': '2024-02-14T12:00:00+00:00', 'options': dict({ }), 'pref_disable_new_entities': False, diff --git a/tests/syrupy.py b/tests/syrupy.py index 09e18428015..0bdbcf99e2b 100644 --- a/tests/syrupy.py +++ b/tests/syrupy.py @@ -137,7 +137,8 @@ class HomeAssistantSnapshotSerializer(AmberDataSerializer): @classmethod def _serializable_config_entry(cls, data: ConfigEntry) -> SerializableData: """Prepare a Home Assistant config entry for serialization.""" - return ConfigEntrySnapshot(data.as_dict() | {"entry_id": ANY}) + entry = ConfigEntrySnapshot(data.as_dict() | {"entry_id": ANY}) + return cls._remove_created_and_modified_at(entry) @classmethod def _serializable_device_registry_entry( diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 10cdaa8add9..2a5dff5c14a 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -10,6 +10,7 @@ import logging from typing import Any from unittest.mock import ANY, AsyncMock, Mock, patch +from freezegun import freeze_time from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion @@ -51,6 +52,7 @@ from .common import ( async_capture_events, async_fire_time_changed, async_get_persistent_notifications, + flush_store, mock_config_flow, mock_integration, mock_platform, @@ -912,6 +914,7 @@ async def test_saving_and_loading( assert orig.as_dict() == loaded.as_dict() +@freeze_time("2024-02-14 12:00:00") async def test_as_dict(snapshot: SnapshotAssertion) -> None: """Test ConfigEntry.as_dict.""" @@ -1251,8 +1254,11 @@ async def test_loading_default_config(hass: HomeAssistant) -> None: assert len(manager.async_entries()) == 0 -async def test_updating_entry_data(manager: config_entries.ConfigEntries) -> None: +async def test_updating_entry_data( + manager: config_entries.ConfigEntries, freezer: FrozenDateTimeFactory +) -> None: """Test that we can update an entry data.""" + created = dt_util.utcnow() entry = MockConfigEntry( domain="test", data={"first": True}, @@ -1260,17 +1266,32 @@ async def test_updating_entry_data(manager: config_entries.ConfigEntries) -> Non ) entry.add_to_manager(manager) + assert len(manager.async_entries()) == 1 + assert manager.async_entries()[0] == entry + assert entry.created_at == created + assert entry.modified_at == created + + freezer.tick() + assert manager.async_update_entry(entry) is False assert entry.data == {"first": True} + assert entry.modified_at == created + assert manager.async_entries()[0].modified_at == created + + freezer.tick() + modified = dt_util.utcnow() assert manager.async_update_entry(entry, data={"second": True}) is True assert entry.data == {"second": True} + assert entry.modified_at == modified + assert manager.async_entries()[0].modified_at == modified async def test_updating_entry_system_options( - manager: config_entries.ConfigEntries, + manager: config_entries.ConfigEntries, freezer: FrozenDateTimeFactory ) -> None: """Test that we can update an entry data.""" + created = dt_util.utcnow() entry = MockConfigEntry( domain="test", data={"first": True}, @@ -1281,6 +1302,11 @@ async def test_updating_entry_system_options( assert entry.pref_disable_new_entities is True assert entry.pref_disable_polling is False + assert entry.created_at == created + assert entry.modified_at == created + + freezer.tick() + modified = dt_util.utcnow() manager.async_update_entry( entry, pref_disable_new_entities=False, pref_disable_polling=True @@ -1288,6 +1314,8 @@ async def test_updating_entry_system_options( assert entry.pref_disable_new_entities is False assert entry.pref_disable_polling is True + assert entry.created_at == created + assert entry.modified_at == modified async def test_update_entry_options_and_trigger_listener( @@ -5908,3 +5936,67 @@ async def test_config_entry_late_platform_setup( "entry_id test2 cannot forward setup for light because it is " "not loaded in the ConfigEntryState.NOT_LOADED state" ) not in caplog.text + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_migration_from_1_2( + hass: HomeAssistant, hass_storage: dict[str, Any] +) -> None: + """Test migration from version 1.2.""" + hass_storage[config_entries.STORAGE_KEY] = { + "version": 1, + "minor_version": 2, + "data": { + "entries": [ + { + "data": {}, + "disabled_by": None, + "domain": "sun", + "entry_id": "0a8bd02d0d58c7debf5daf7941c9afe2", + "minor_version": 1, + "options": {}, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "source": "import", + "title": "Sun", + "unique_id": None, + "version": 1, + }, + ] + }, + } + + manager = config_entries.ConfigEntries(hass, {}) + await manager.async_initialize() + + # Test data was loaded + entries = manager.async_entries() + assert len(entries) == 1 + + # Check we store migrated data + await flush_store(manager._store) + assert hass_storage[config_entries.STORAGE_KEY] == { + "version": config_entries.STORAGE_VERSION, + "minor_version": config_entries.STORAGE_VERSION_MINOR, + "key": config_entries.STORAGE_KEY, + "data": { + "entries": [ + { + "created_at": "1970-01-01T00:00:00+00:00", + "data": {}, + "disabled_by": None, + "domain": "sun", + "entry_id": "0a8bd02d0d58c7debf5daf7941c9afe2", + "minor_version": 1, + "modified_at": "1970-01-01T00:00:00+00:00", + "options": {}, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "source": "import", + "title": "Sun", + "unique_id": None, + "version": 1, + }, + ] + }, + }