diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 64fa3342c08..8a388058b19 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -21,7 +21,10 @@ from homeassistant.helpers.device import ( async_remove_stale_devices_links_keep_entity_device, ) from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.helper_integration import async_handle_source_entity_changes +from homeassistant.helpers.helper_integration import ( + async_handle_source_entity_changes, + async_remove_helper_config_entry_from_source_device, +) from homeassistant.helpers.typing import ConfigType from .const import ( @@ -199,6 +202,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Utility Meter from a config entry.""" + # This can be removed in HA Core 2026.2 async_remove_stale_devices_links_keep_entity_device( hass, entry.entry_id, entry.options[CONF_SOURCE_SENSOR] ) @@ -225,20 +229,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: options={**entry.options, CONF_SOURCE_SENSOR: source_entity_id}, ) - async def source_entity_removed() -> None: - # The source entity has been removed, we need to clean the device links. - async_remove_stale_devices_links_keep_entity_device(hass, entry.entry_id, None) - entry.async_on_unload( async_handle_source_entity_changes( hass, + add_helper_config_entry_to_device=False, helper_config_entry_id=entry.entry_id, set_source_entity_id_or_uuid=set_source_entity_id_or_uuid, source_device_id=async_entity_id_to_device_id( hass, entry.options[CONF_SOURCE_SENSOR] ), source_entity_id_or_uuid=entry.options[CONF_SOURCE_SENSOR], - source_entity_removed=source_entity_removed, ) ) @@ -286,13 +286,39 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry.""" - _LOGGER.debug("Migrating from version %s", config_entry.version) + _LOGGER.debug( + "Migrating from version %s.%s", config_entry.version, config_entry.minor_version + ) + + if config_entry.version > 2: + # This means the user has downgraded from a future version + return False if config_entry.version == 1: new = {**config_entry.options} new[CONF_METER_PERIODICALLY_RESETTING] = True hass.config_entries.async_update_entry(config_entry, options=new, version=2) - _LOGGER.info("Migration to version %s successful", config_entry.version) + if config_entry.version == 2: + options = {**config_entry.options} + if config_entry.minor_version < 2: + # Remove the utility_meter config entry from the source device + if source_device_id := async_entity_id_to_device_id( + hass, options[CONF_SOURCE_SENSOR] + ): + async_remove_helper_config_entry_from_source_device( + hass, + helper_config_entry_id=config_entry.entry_id, + source_device_id=source_device_id, + ) + hass.config_entries.async_update_entry( + config_entry, options=options, minor_version=2 + ) + + _LOGGER.debug( + "Migration to version %s.%s successful", + config_entry.version, + config_entry.minor_version, + ) return True diff --git a/homeassistant/components/utility_meter/config_flow.py b/homeassistant/components/utility_meter/config_flow.py index db7cea6ecf2..933a04accba 100644 --- a/homeassistant/components/utility_meter/config_flow.py +++ b/homeassistant/components/utility_meter/config_flow.py @@ -130,6 +130,7 @@ class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN): """Handle a config or options flow for Utility Meter.""" VERSION = 2 + MINOR_VERSION = 2 config_flow = CONFIG_FLOW options_flow = OPTIONS_FLOW diff --git a/homeassistant/components/utility_meter/select.py b/homeassistant/components/utility_meter/select.py index 0c818525c8d..280a1fd7b1a 100644 --- a/homeassistant/components/utility_meter/select.py +++ b/homeassistant/components/utility_meter/select.py @@ -8,8 +8,8 @@ from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID from homeassistant.core import HomeAssistant -from homeassistant.helpers.device import async_device_info_to_link_from_entity -from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.device import async_entity_id_to_device +from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity_platform import ( AddConfigEntryEntitiesCallback, AddEntitiesCallback, @@ -33,7 +33,7 @@ async def async_setup_entry( unique_id = config_entry.entry_id - device_info = async_device_info_to_link_from_entity( + device = async_entity_id_to_device( hass, config_entry.options[CONF_SOURCE_SENSOR], ) @@ -42,7 +42,7 @@ async def async_setup_entry( name=name, tariffs=tariffs, unique_id=unique_id, - device_info=device_info, + device=device, ) async_add_entities([tariff_select]) @@ -91,14 +91,14 @@ class TariffSelect(SelectEntity, RestoreEntity): *, yaml_slug: str | None = None, unique_id: str | None = None, - device_info: DeviceInfo | None = None, + device: DeviceEntry | None = None, ) -> None: """Initialize a tariff selector.""" self._attr_name = name if yaml_slug: # Backwards compatibility with YAML configuration entries self.entity_id = f"select.{yaml_slug}" self._attr_unique_id = unique_id - self._attr_device_info = device_info + self.device_entry = device self._current_tariff: str | None = None self._tariffs = tariffs self._attr_should_poll = False diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index d424692ac95..457b02c2b50 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -39,7 +39,7 @@ from homeassistant.core import ( callback, ) from homeassistant.helpers import entity_platform, entity_registry as er -from homeassistant.helpers.device import async_device_info_to_link_from_entity +from homeassistant.helpers.device import async_entity_id_to_device from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import ( AddConfigEntryEntitiesCallback, @@ -129,11 +129,6 @@ async def async_setup_entry( registry, config_entry.options[CONF_SOURCE_SENSOR] ) - device_info = async_device_info_to_link_from_entity( - hass, - source_entity_id, - ) - cron_pattern = None delta_values = config_entry.options[CONF_METER_DELTA_VALUES] meter_offset = timedelta(days=config_entry.options[CONF_METER_OFFSET]) @@ -154,6 +149,7 @@ async def async_setup_entry( if not tariffs: # Add single sensor, not gated by a tariff selector meter_sensor = UtilityMeterSensor( + hass, cron_pattern=cron_pattern, delta_values=delta_values, meter_offset=meter_offset, @@ -166,7 +162,6 @@ async def async_setup_entry( tariff_entity=tariff_entity, tariff=None, unique_id=entry_id, - device_info=device_info, sensor_always_available=sensor_always_available, ) meters.append(meter_sensor) @@ -175,6 +170,7 @@ async def async_setup_entry( # Add sensors for each tariff for tariff in tariffs: meter_sensor = UtilityMeterSensor( + hass, cron_pattern=cron_pattern, delta_values=delta_values, meter_offset=meter_offset, @@ -187,7 +183,6 @@ async def async_setup_entry( tariff_entity=tariff_entity, tariff=tariff, unique_id=f"{entry_id}_{tariff}", - device_info=device_info, sensor_always_available=sensor_always_available, ) meters.append(meter_sensor) @@ -259,6 +254,7 @@ async def async_setup_platform( CONF_SENSOR_ALWAYS_AVAILABLE ] meter_sensor = UtilityMeterSensor( + hass, cron_pattern=conf_cron_pattern, delta_values=conf_meter_delta_values, meter_offset=conf_meter_offset, @@ -359,6 +355,7 @@ class UtilityMeterSensor(RestoreSensor): def __init__( self, + hass, *, cron_pattern, delta_values, @@ -374,11 +371,13 @@ class UtilityMeterSensor(RestoreSensor): unique_id, sensor_always_available, suggested_entity_id=None, - device_info=None, ): """Initialize the Utility Meter sensor.""" self._attr_unique_id = unique_id - self._attr_device_info = device_info + self.device_entry = async_entity_id_to_device( + hass, + source_entity, + ) self.entity_id = suggested_entity_id self._parent_meter = parent_meter self._sensor_source_id = source_entity diff --git a/tests/components/utility_meter/snapshots/test_diagnostics.ambr b/tests/components/utility_meter/snapshots/test_diagnostics.ambr index ef235bba99d..024fd1aaa7b 100644 --- a/tests/components/utility_meter/snapshots/test_diagnostics.ambr +++ b/tests/components/utility_meter/snapshots/test_diagnostics.ambr @@ -8,7 +8,7 @@ 'discovery_keys': dict({ }), 'domain': 'utility_meter', - 'minor_version': 1, + 'minor_version': 2, 'options': dict({ 'cycle': 'monthly', 'delta_values': False, diff --git a/tests/components/utility_meter/test_config_flow.py b/tests/components/utility_meter/test_config_flow.py index 01fd80acc0e..0aa73d6d123 100644 --- a/tests/components/utility_meter/test_config_flow.py +++ b/tests/components/utility_meter/test_config_flow.py @@ -403,11 +403,19 @@ async def test_change_device_source( assert await hass.config_entries.async_setup(utility_meter_config_entry.entry_id) await hass.async_block_till_done() - # Confirm that the configuration entry has been added to the source entity 1 (current) device registry + # Confirm that the configuration entry has not been added to the source entity 1 (current) device registry current_device = device_registry.async_get( device_id=current_entity_source.device_id ) - assert utility_meter_config_entry.entry_id in current_device.config_entries + assert utility_meter_config_entry.entry_id not in current_device.config_entries + + # Check that the entities are linked to the expected device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id == source_entity_1.device_id # Change configuration options to use source entity 2 (with a linked device) and reload the integration previous_entity_source = source_entity_1 @@ -427,17 +435,25 @@ async def test_change_device_source( assert result["type"] is FlowResultType.CREATE_ENTRY await hass.async_block_till_done() - # Confirm that the configuration entry has been removed from the source entity 1 (previous) device registry + # Confirm that the configuration entry is not in the source entity 1 (previous) device registry previous_device = device_registry.async_get( device_id=previous_entity_source.device_id ) assert utility_meter_config_entry.entry_id not in previous_device.config_entries - # Confirm that the configuration entry has been added to the source entity 2 (current) device registry + # Confirm that the configuration entry is not in to the source entity 2 (current) device registry current_device = device_registry.async_get( device_id=current_entity_source.device_id ) - assert utility_meter_config_entry.entry_id in current_device.config_entries + assert utility_meter_config_entry.entry_id not in current_device.config_entries + + # Check that the entities are linked to the expected device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id == source_entity_2.device_id # Change configuration options to use source entity 3 (without a device) and reload the integration previous_entity_source = source_entity_2 @@ -457,12 +473,20 @@ async def test_change_device_source( assert result["type"] is FlowResultType.CREATE_ENTRY await hass.async_block_till_done() - # Confirm that the configuration entry has been removed from the source entity 2 (previous) device registry + # Confirm that the configuration entry has is not in the source entity 2 (previous) device registry previous_device = device_registry.async_get( device_id=previous_entity_source.device_id ) assert utility_meter_config_entry.entry_id not in previous_device.config_entries + # Check that the entities are no longer linked to a device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id is None + # Confirm that there is no device with the helper configuration entry assert ( dr.async_entries_for_config_entry( @@ -489,8 +513,16 @@ async def test_change_device_source( assert result["type"] is FlowResultType.CREATE_ENTRY await hass.async_block_till_done() - # Confirm that the configuration entry has been added to the source entity 2 (current) device registry + # Confirm that the configuration entry is not in the source entity 2 (current) device registry current_device = device_registry.async_get( device_id=current_entity_source.device_id ) - assert utility_meter_config_entry.entry_id in current_device.config_entries + assert utility_meter_config_entry.entry_id not in current_device.config_entries + + # Check that the entities are linked to the expected device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id == source_entity_2.device_id diff --git a/tests/components/utility_meter/test_init.py b/tests/components/utility_meter/test_init.py index ea4af741e19..ec7fdd1db87 100644 --- a/tests/components/utility_meter/test_init.py +++ b/tests/components/utility_meter/test_init.py @@ -20,7 +20,7 @@ from homeassistant.components.utility_meter import ( ) from homeassistant.components.utility_meter.config_flow import ConfigFlowHandler from homeassistant.components.utility_meter.const import DOMAIN, SERVICE_RESET -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, @@ -29,7 +29,7 @@ from homeassistant.const import ( Platform, UnitOfEnergy, ) -from homeassistant.core import Event, HomeAssistant, State +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.event import async_track_entity_registry_updated_event from homeassistant.setup import async_setup_component @@ -108,6 +108,7 @@ def track_entity_registry_actions(hass: HomeAssistant, entity_id: str) -> list[s """Track entity registry actions for an entity.""" events = [] + @callback def add_event(event: Event[er.EventEntityRegistryUpdatedData]) -> None: """Add entity registry updated event to the list.""" events.append(event.data["action"]) @@ -601,7 +602,7 @@ async def test_device_cleaning( devices_before_reload = device_registry.devices.get_devices_for_config_entry_id( utility_meter_config_entry.entry_id ) - assert len(devices_before_reload) == 3 + assert len(devices_before_reload) == 2 # Config entry reload await hass.config_entries.async_reload(utility_meter_config_entry.entry_id) @@ -616,7 +617,7 @@ async def test_device_cleaning( devices_after_reload = device_registry.devices.get_devices_for_config_entry_id( utility_meter_config_entry.entry_id ) - assert len(devices_after_reload) == 1 + assert len(devices_after_reload) == 0 @pytest.mark.parametrize( @@ -642,6 +643,81 @@ async def test_async_handle_source_entity_changes_source_entity_removed( sensor_device: dr.DeviceEntry, sensor_entity_entry: er.RegistryEntry, expected_entities: set[str], +) -> None: + """Test the utility_meter config entry is removed when the source entity is removed.""" + assert await hass.config_entries.async_setup(utility_meter_config_entry.entry_id) + await hass.async_block_till_done() + + events = {} + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id == sensor_entity_entry.device_id + events[utility_meter_entity.entity_id] = track_entity_registry_actions( + hass, utility_meter_entity.entity_id + ) + assert set(events) == expected_entities + + sensor_device = device_registry.async_get(sensor_device.id) + assert utility_meter_config_entry.entry_id not in sensor_device.config_entries + + # Remove the source sensor's config entry from the device, this removes the + # source sensor + with patch( + "homeassistant.components.utility_meter.async_unload_entry", + wraps=utility_meter.async_unload_entry, + ) as mock_unload_entry: + device_registry.async_update_device( + sensor_device.id, remove_config_entry_id=sensor_config_entry.entry_id + ) + await hass.async_block_till_done() + await hass.async_block_till_done() + mock_unload_entry.assert_not_called() + + # Check that the entities are no longer linked to the source device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id is None + + # Check that the device is removed + assert not device_registry.async_get(sensor_device.id) + + # Check that the utility_meter config entry is not removed + assert utility_meter_config_entry.entry_id in hass.config_entries.async_entry_ids() + + # Check we got the expected events + for entity_events in events.values(): + assert entity_events == ["update"] + + +@pytest.mark.parametrize( + ("tariffs", "expected_entities"), + [ + ([], {"sensor.my_utility_meter"}), + ( + ["peak", "offpeak"], + { + "select.my_utility_meter", + "sensor.my_utility_meter_offpeak", + "sensor.my_utility_meter_peak", + }, + ), + ], +) +async def test_async_handle_source_entity_changes_source_entity_removed_shared_device( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + utility_meter_config_entry: MockConfigEntry, + sensor_config_entry: ConfigEntry, + sensor_device: dr.DeviceEntry, + sensor_entity_entry: er.RegistryEntry, + expected_entities: set[str], ) -> None: """Test the utility_meter config entry is removed when the source entity is removed.""" # Add another config entry to the sensor device @@ -667,7 +743,7 @@ async def test_async_handle_source_entity_changes_source_entity_removed( assert set(events) == expected_entities sensor_device = device_registry.async_get(sensor_device.id) - assert utility_meter_config_entry.entry_id in sensor_device.config_entries + assert utility_meter_config_entry.entry_id not in sensor_device.config_entries # Remove the source sensor's config entry from the device, this removes the # source sensor @@ -682,7 +758,15 @@ async def test_async_handle_source_entity_changes_source_entity_removed( await hass.async_block_till_done() mock_unload_entry.assert_not_called() - # Check that the utility_meter config entry is removed from the device + # Check that the entities are no longer linked to the source device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id is None + + # Check that the utility_meter config entry is not in the device sensor_device = device_registry.async_get(sensor_device.id) assert utility_meter_config_entry.entry_id not in sensor_device.config_entries @@ -734,7 +818,7 @@ async def test_async_handle_source_entity_changes_source_entity_removed_from_dev assert set(events) == expected_entities sensor_device = device_registry.async_get(sensor_device.id) - assert utility_meter_config_entry.entry_id in sensor_device.config_entries + assert utility_meter_config_entry.entry_id not in sensor_device.config_entries # Remove the source sensor from the device with patch( @@ -747,7 +831,15 @@ async def test_async_handle_source_entity_changes_source_entity_removed_from_dev await hass.async_block_till_done() mock_unload_entry.assert_called_once() - # Check that the utility_meter config entry is removed from the device + # Check that the entities are no longer linked to the source device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id is None + + # Check that the utility_meter config entry is not in the device sensor_device = device_registry.async_get(sensor_device.id) assert utility_meter_config_entry.entry_id not in sensor_device.config_entries @@ -805,7 +897,7 @@ async def test_async_handle_source_entity_changes_source_entity_moved_other_devi assert set(events) == expected_entities sensor_device = device_registry.async_get(sensor_device.id) - assert utility_meter_config_entry.entry_id in sensor_device.config_entries + assert utility_meter_config_entry.entry_id not in sensor_device.config_entries sensor_device_2 = device_registry.async_get(sensor_device_2.id) assert utility_meter_config_entry.entry_id not in sensor_device_2.config_entries @@ -820,11 +912,19 @@ async def test_async_handle_source_entity_changes_source_entity_moved_other_devi await hass.async_block_till_done() mock_unload_entry.assert_called_once() - # Check that the utility_meter config entry is moved to the other device + # Check that the entities are linked to the other device + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + assert utility_meter_entity.device_id == sensor_device_2.id + + # Check that the derivative config entry is not in any of the devices sensor_device = device_registry.async_get(sensor_device.id) assert utility_meter_config_entry.entry_id not in sensor_device.config_entries sensor_device_2 = device_registry.async_get(sensor_device_2.id) - assert utility_meter_config_entry.entry_id in sensor_device_2.config_entries + assert utility_meter_config_entry.entry_id not in sensor_device_2.config_entries # Check that the utility_meter config entry is not removed assert utility_meter_config_entry.entry_id in hass.config_entries.async_entry_ids() @@ -874,7 +974,7 @@ async def test_async_handle_source_entity_new_entity_id( assert set(events) == expected_entities sensor_device = device_registry.async_get(sensor_device.id) - assert utility_meter_config_entry.entry_id in sensor_device.config_entries + assert utility_meter_config_entry.entry_id not in sensor_device.config_entries # Change the source entity's entity ID with patch( @@ -890,9 +990,9 @@ async def test_async_handle_source_entity_new_entity_id( # Check that the utility_meter config entry is updated with the new entity ID assert utility_meter_config_entry.options["source"] == "sensor.new_entity_id" - # Check that the helper config is still in the device + # Check that the helper config is not in the device sensor_device = device_registry.async_get(sensor_device.id) - assert utility_meter_config_entry.entry_id in sensor_device.config_entries + assert utility_meter_config_entry.entry_id not in sensor_device.config_entries # Check that the utility_meter config entry is not removed assert utility_meter_config_entry.entry_id in hass.config_entries.async_entry_ids() @@ -900,3 +1000,108 @@ async def test_async_handle_source_entity_new_entity_id( # Check we got the expected events for entity_events in events.values(): assert entity_events == [] + + +@pytest.mark.parametrize( + ("tariffs", "expected_entities"), + [ + ([], {"sensor.my_utility_meter"}), + ( + ["peak", "offpeak"], + { + "select.my_utility_meter", + "sensor.my_utility_meter_offpeak", + "sensor.my_utility_meter_peak", + }, + ), + ], +) +async def test_migration_2_1( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + sensor_entity_entry: er.RegistryEntry, + sensor_device: dr.DeviceEntry, + tariffs: list[str], + expected_entities: set[str], +) -> None: + """Test migration from v2.1 removes utility_meter config entry from device.""" + + utility_meter_config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={ + "cycle": "monthly", + "delta_values": False, + "name": "My utility meter", + "net_consumption": False, + "offset": 0, + "periodically_resetting": True, + "source": sensor_entity_entry.entity_id, + "tariffs": tariffs, + }, + title="My utility meter", + version=2, + minor_version=1, + ) + utility_meter_config_entry.add_to_hass(hass) + + # Add the helper config entry to the device + device_registry.async_update_device( + sensor_device.id, add_config_entry_id=utility_meter_config_entry.entry_id + ) + + # Check preconditions + sensor_device = device_registry.async_get(sensor_device.id) + assert utility_meter_config_entry.entry_id in sensor_device.config_entries + + await hass.config_entries.async_setup(utility_meter_config_entry.entry_id) + await hass.async_block_till_done() + + assert utility_meter_config_entry.state is ConfigEntryState.LOADED + + # Check that the helper config entry is removed from the device and the helper + # entities are linked to the source device + sensor_device = device_registry.async_get(sensor_device.id) + assert utility_meter_config_entry.entry_id not in sensor_device.config_entries + # Check that the entities are linked to the other device + entities = set() + for ( + utility_meter_entity + ) in entity_registry.entities.get_entries_for_config_entry_id( + utility_meter_config_entry.entry_id + ): + entities.add(utility_meter_entity.entity_id) + assert utility_meter_entity.device_id == sensor_entity_entry.device_id + assert entities == expected_entities + + assert utility_meter_config_entry.version == 2 + assert utility_meter_config_entry.minor_version == 2 + + +async def test_migration_from_future_version( + hass: HomeAssistant, +) -> None: + """Test migration from future version.""" + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={ + "cycle": "monthly", + "delta_values": False, + "name": "My utility meter", + "net_consumption": False, + "offset": 0, + "periodically_resetting": True, + "source": "sensor.test", + "tariffs": [], + }, + title="My utility meter", + version=3, + minor_version=1, + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.MIGRATION_ERROR diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 2de2ee553b3..f684cdb16a0 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -1888,10 +1888,12 @@ async def test_bad_offset(hass: HomeAssistant) -> None: def test_calculate_adjustment_invalid_new_state( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture, ) -> None: """Test that calculate_adjustment method returns None if the new state is invalid.""" mock_sensor = UtilityMeterSensor( + hass, cron_pattern=None, delta_values=False, meter_offset=DEFAULT_OFFSET,