Migrate Mastodon unique id (#123877)

* Migrate unique id

* Fix unique id check

* Switch to minor version and other fixes
This commit is contained in:
Andrew Jackson 2024-08-14 11:55:59 +01:00 committed by GitHub
parent d50bac3b3e
commit ac223e64f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 139 additions and 13 deletions

View File

@ -17,10 +17,11 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import discovery
from homeassistant.util import slugify
from .const import CONF_BASE_URL, DOMAIN
from .const import CONF_BASE_URL, DOMAIN, LOGGER
from .coordinator import MastodonCoordinator
from .utils import create_mastodon_client
from .utils import construct_mastodon_username, create_mastodon_client
PLATFORMS: list[Platform] = [Platform.NOTIFY, Platform.SENSOR]
@ -80,6 +81,39 @@ async def async_unload_entry(hass: HomeAssistant, entry: MastodonConfigEntry) ->
)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old config."""
if entry.version == 1 and entry.minor_version == 1:
# Version 1.1 had the unique_id as client_id, this isn't necessarily unique
LOGGER.debug("Migrating config entry from version %s", entry.version)
try:
_, instance, account = await hass.async_add_executor_job(
setup_mastodon,
entry,
)
except MastodonError as ex:
LOGGER.error("Migration failed with error %s", ex)
return False
entry.minor_version = 2
hass.config_entries.async_update_entry(
entry,
unique_id=slugify(construct_mastodon_username(instance, account)),
)
LOGGER.info(
"Entry %s successfully migrated to version %s.%s",
entry.entry_id,
entry.version,
entry.minor_version,
)
return True
def setup_mastodon(entry: ConfigEntry) -> tuple[Mastodon, dict, dict]:
"""Get mastodon details."""
client = create_mastodon_client(

View File

@ -20,6 +20,7 @@ from homeassistant.helpers.selector import (
TextSelectorType,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from .const import CONF_BASE_URL, DEFAULT_URL, DOMAIN, LOGGER
from .utils import construct_mastodon_username, create_mastodon_client
@ -47,6 +48,7 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""
VERSION = 1
MINOR_VERSION = 2
config_entry: ConfigEntry
def check_connection(
@ -105,10 +107,6 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by the user."""
errors: dict[str, str] | None = None
if user_input:
self._async_abort_entries_match(
{CONF_CLIENT_ID: user_input[CONF_CLIENT_ID]}
)
instance, account, errors = await self.hass.async_add_executor_job(
self.check_connection,
user_input[CONF_BASE_URL],
@ -119,7 +117,8 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
if not errors:
name = construct_mastodon_username(instance, account)
await self.async_set_unique_id(user_input[CONF_CLIENT_ID])
await self.async_set_unique_id(slugify(name))
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=name,
data=user_input,
@ -148,7 +147,8 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
)
if not errors:
await self.async_set_unique_id(client_id)
name = construct_mastodon_username(instance, account)
await self.async_set_unique_id(slugify(name))
self._abort_if_unique_id_configured()
if not name:

View File

@ -53,5 +53,7 @@ def mock_config_entry() -> MockConfigEntry:
CONF_ACCESS_TOKEN: "access_token",
},
entry_id="01J35M4AH9HYRC2V0G6RNVNWJH",
unique_id="client_id",
unique_id="trwnh_mastodon_social",
version=1,
minor_version=2,
)

View File

@ -0,0 +1,33 @@
# serializer version: 1
# name: test_device_info
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'mastodon',
'trwnh_mastodon_social',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'Mastodon gGmbH',
'model': '@trwnh@mastodon.social',
'model_id': None,
'name': 'Mastodon @trwnh@mastodon.social',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'suggested_area': None,
'sw_version': '4.0.0rc1',
'via_device_id': None,
})
# ---

View File

@ -30,7 +30,7 @@
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'followers',
'unique_id': 'client_id_followers',
'unique_id': 'trwnh_mastodon_social_followers',
'unit_of_measurement': 'accounts',
})
# ---
@ -80,7 +80,7 @@
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'following',
'unique_id': 'client_id_following',
'unique_id': 'trwnh_mastodon_social_following',
'unit_of_measurement': 'accounts',
})
# ---
@ -130,7 +130,7 @@
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'posts',
'unique_id': 'client_id_posts',
'unique_id': 'trwnh_mastodon_social_posts',
'unit_of_measurement': 'posts',
})
# ---

View File

@ -44,7 +44,7 @@ async def test_full_flow(
CONF_CLIENT_SECRET: "client_secret",
CONF_ACCESS_TOKEN: "access_token",
}
assert result["result"].unique_id == "client_id"
assert result["result"].unique_id == "trwnh_mastodon_social"
@pytest.mark.parametrize(

View File

@ -3,15 +3,36 @@
from unittest.mock import AsyncMock
from mastodon.Mastodon import MastodonError
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.mastodon.config_flow import MastodonConfigFlow
from homeassistant.components.mastodon.const import CONF_BASE_URL, DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from . import setup_integration
from tests.common import MockConfigEntry
async def test_device_info(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_mastodon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test device registry integration."""
await setup_integration(hass, mock_config_entry)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_config_entry.unique_id)}
)
assert device_entry is not None
assert device_entry == snapshot
async def test_initialization_failure(
hass: HomeAssistant,
mock_mastodon_client: AsyncMock,
@ -23,3 +44,39 @@ async def test_initialization_failure(
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
async def test_migrate(
hass: HomeAssistant,
mock_mastodon_client: AsyncMock,
) -> None:
"""Test migration."""
# Setup the config entry
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_BASE_URL: "https://mastodon.social",
CONF_CLIENT_ID: "client_id",
CONF_CLIENT_SECRET: "client_secret",
CONF_ACCESS_TOKEN: "access_token",
},
title="@trwnh@mastodon.social",
unique_id="client_id",
version=1,
minor_version=1,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Check migration was successful
assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.data == {
CONF_BASE_URL: "https://mastodon.social",
CONF_CLIENT_ID: "client_id",
CONF_CLIENT_SECRET: "client_secret",
CONF_ACCESS_TOKEN: "access_token",
}
assert config_entry.version == MastodonConfigFlow.VERSION
assert config_entry.minor_version == MastodonConfigFlow.MINOR_VERSION
assert config_entry.unique_id == "trwnh_mastodon_social"