Skip to content

feat: fixing device migration#57

Open
mikart143 wants to merge 2 commits intomasterfrom
feat/config-migration
Open

feat: fixing device migration#57
mikart143 wants to merge 2 commits intomasterfrom
feat/config-migration

Conversation

@mikart143
Copy link
Collaborator

This pull request introduces a migration for legacy device identifiers in the inpost_air Home Assistant integration, ensuring that old 3-tuple devices are properly cleaned up and their user settings are preserved. The migration logic is thoroughly tested with new integration tests to guarantee correct behavior and idempotency.

Migration logic for legacy devices:

  • The async_migrate_entry function in custom_components/inpost_air/__init__.py now removes legacy devices with 3-tuple identifiers left by versions ≤ 1.4.x. If a corresponding new device exists, user settings (area, name, disabled status) are transferred. The migration marks the config entry as upgraded to version 2.
  • Imports for entity_registry and DOMAIN are added in __init__.py to support the migration process.

Integration tests for migration:

  • New integration tests in tests/test_migration_integration.py verify that legacy devices are removed, settings are transferred to new devices, orphaned devices are cleaned up, and the migration is idempotent (safe to run multiple times).

@mikart143 mikart143 requested review from ceski23 and Copilot August 18, 2025 20:10
@github-actions
Copy link

github-actions bot commented Aug 18, 2025

Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py661577%41–42, 44, 51, 53–54, 56–57, 59, 61–62, 71, 73, 78, 86
api.py552358%47–49, 54, 56, 58–62, 68–69, 71, 74, 83, 87, 90, 92, 96, 100, 105, 111, 117
config_flow.py491863%41–42, 46–47, 49–52, 54, 69–70, 72–77, 79
const.py150100% 
coordinator.py512747%38–39, 42–43, 46–59, 69, 75–76, 84–86, 90, 93–94
models.py28292%9–10
sensor.py17170%3–4, 10–14, 17–18, 22, 25, 124, 130–131, 133, 135, 141
utils.py240100% 
TOTAL30510266% 

@github-actions
Copy link

github-actions bot commented Aug 18, 2025

tests: Run #53

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
20 20 0 0 0 0 0 2ms

🎉 All tests passed!

Github Test Reporter by CTRF 💚

🔄 This comment has been updated

@mikart143 mikart143 linked an issue Aug 18, 2025 that may be closed by this pull request

This comment was marked as outdated.

@mikart143 mikart143 requested a review from Copilot August 18, 2025 20:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request introduces a device migration system for the InPost Air integration to clean up legacy device identifiers left from versions ≤ 1.4.x. The migration logic removes old 3-tuple device identifiers while preserving user settings by transferring them to corresponding new devices with 2-tuple identifiers.

  • Added migration logic to identify and remove legacy devices with 3-tuple identifiers
  • Implemented settings transfer from old devices to new devices when they exist
  • Created comprehensive test coverage for migration scenarios including edge cases

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
custom_components/inpost_air/init.py Added device migration logic and fixed type annotation typo
tests/test_migration.py Unit tests for migration functionality with mocked dependencies
tests/test_migration_integration.py Integration tests for migration using real Home Assistant device registry

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


new_disabled_by = new_dev.disabled_by
if isinstance(new_disabled_by, str):
new_disabled_by = dr.DeviceEntryDisabler(new_disabled_by)
Copy link

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string-to-enum conversion logic for disabled_by is duplicated for both old_disabled_by and new_disabled_by. Consider extracting this into a helper function to reduce code duplication.

Suggested change
new_disabled_by = dr.DeviceEntryDisabler(new_disabled_by)
def _normalize_disabled_by(value):
return dr.DeviceEntryDisabler(value) if isinstance(value, str) else value
old_disabled_by = _normalize_disabled_by(device.disabled_by)
new_disabled_by = _normalize_disabled_by(new_dev.disabled_by)

Copilot uses AI. Check for mistakes.
new_dev.id,
area_id=device.area_id or new_dev.area_id,
name_by_user=device.name_by_user or new_dev.name_by_user,
disabled_by=old_disabled_by or new_disabled_by,
Copy link

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'or' operator with DeviceEntryDisabler enums may not work as expected. If old_disabled_by is DeviceEntryDisabler.USER (which is truthy), it will be used correctly. However, if it's None, new_disabled_by will be used. But if old_disabled_by is a falsy enum value like DeviceEntryDisabler.INTEGRATION (if it exists), the logic might not behave as intended. Consider using explicit None checks instead.

Suggested change
disabled_by=old_disabled_by or new_disabled_by,
disabled_by=old_disabled_by if old_disabled_by is not None else new_disabled_by,

Copilot uses AI. Check for mistakes.
Comment on lines -81 to -83
_LOGGER.debug(
"Migrating %s from version %s", config_entry.title, config_entry.version
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep that for debugging purposes

"Migrating %s to version %s completed", config_entry.title, config_entry.version
)

if entry.version == 1: # 1.5.0 → 1.5.1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version 2 was introduced in 1.5.0

Suggested change
if entry.version == 1: # 1.5.0 → 1.5.1
if entry.version == 1: # < 1.5.0

Comment on lines -90 to -94
hass.config_entries.async_update_entry(
config_entry,
data={"parcel_locker": from_dict(InPostAirPoint, config_entry.data)},
version=2,
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happened to the original migration logic? VERSION 2 introduced storage of runtime data in config entry. I don't see logic that updates config_entry to handle that

dev_reg = dr.async_get(hass)
ent_reg = er.async_get(hass)

for device in list(dev_reg.devices.values()):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't be easier to just find device with 3-tuple id in registry instead of iterating over all devices? (especially since async_migrate_entry will be invoked for each configured parcel locker).

If there is such device then update it's ID to new format, else delete it

"Migrating %s from version %s", config_entry.title, config_entry.version
)
async def async_migrate_entry(hass: HomeAssistant, entry: InPostAirConfigEntry) -> bool:
"""Remove legacy 3-tuple devices left by ≤ 1.4.x."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method will be invoked each time we will bump config entry version, not just for 3-tuple ID change. we should revert old comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate devices on HA 2025.7

3 participants