mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 08:17:08 +00:00
Add uuid as unique_id to config entries for Cookidoo (#134831)
This commit is contained in:
parent
0d9ac25257
commit
3ce4c47cfc
@ -2,41 +2,29 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from cookidoo_api import Cookidoo, CookidooConfig, get_localization_options
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import (
|
from cookidoo_api import CookidooAuthException, CookidooRequestException
|
||||||
CONF_COUNTRY,
|
|
||||||
CONF_EMAIL,
|
from homeassistant.const import Platform
|
||||||
CONF_LANGUAGE,
|
|
||||||
CONF_PASSWORD,
|
|
||||||
Platform,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
from .coordinator import CookidooConfigEntry, CookidooDataUpdateCoordinator
|
from .coordinator import CookidooConfigEntry, CookidooDataUpdateCoordinator
|
||||||
|
from .helpers import cookidoo_from_config_entry
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.TODO]
|
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.TODO]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
|
||||||
"""Set up Cookidoo from a config entry."""
|
"""Set up Cookidoo from a config entry."""
|
||||||
|
|
||||||
localizations = await get_localization_options(
|
coordinator = CookidooDataUpdateCoordinator(
|
||||||
country=entry.data[CONF_COUNTRY].lower(),
|
hass, await cookidoo_from_config_entry(hass, entry), entry
|
||||||
language=entry.data[CONF_LANGUAGE],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cookidoo = Cookidoo(
|
|
||||||
async_get_clientsession(hass),
|
|
||||||
CookidooConfig(
|
|
||||||
email=entry.data[CONF_EMAIL],
|
|
||||||
password=entry.data[CONF_PASSWORD],
|
|
||||||
localization=localizations[0],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
coordinator = CookidooDataUpdateCoordinator(hass, cookidoo, entry)
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
entry.runtime_data = coordinator
|
entry.runtime_data = coordinator
|
||||||
@ -49,3 +37,56 @@ async def async_setup_entry(hass: HomeAssistant, entry: CookidooConfigEntry) ->
|
|||||||
async def async_unload_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entry(
|
||||||
|
hass: HomeAssistant, config_entry: CookidooConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Migrate config entry."""
|
||||||
|
_LOGGER.debug("Migrating from version %s", config_entry.version)
|
||||||
|
|
||||||
|
if config_entry.version == 1 and config_entry.minor_version == 1:
|
||||||
|
# Add the unique uuid
|
||||||
|
cookidoo = await cookidoo_from_config_entry(hass, config_entry)
|
||||||
|
|
||||||
|
try:
|
||||||
|
auth_data = await cookidoo.login()
|
||||||
|
except (CookidooRequestException, CookidooAuthException) as e:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Could not migrate config config_entry: %s",
|
||||||
|
str(e),
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
unique_id = auth_data.sub
|
||||||
|
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
device_entries = dr.async_entries_for_config_entry(
|
||||||
|
device_registry, config_entry_id=config_entry.entry_id
|
||||||
|
)
|
||||||
|
entity_entries = er.async_entries_for_config_entry(
|
||||||
|
entity_registry, config_entry_id=config_entry.entry_id
|
||||||
|
)
|
||||||
|
for dev in device_entries:
|
||||||
|
device_registry.async_update_device(
|
||||||
|
dev.id, new_identifiers={(DOMAIN, unique_id)}
|
||||||
|
)
|
||||||
|
for ent in entity_entries:
|
||||||
|
assert ent.config_entry_id
|
||||||
|
entity_registry.async_update_entity(
|
||||||
|
ent.entity_id,
|
||||||
|
new_unique_id=ent.unique_id.replace(ent.config_entry_id, unique_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
config_entry, unique_id=auth_data.sub, minor_version=2
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migration to version %s.%s successful",
|
||||||
|
config_entry.version,
|
||||||
|
config_entry.minor_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
@ -56,7 +56,8 @@ class CookidooButton(CookidooBaseEntity, ButtonEntity):
|
|||||||
"""Initialize cookidoo button."""
|
"""Initialize cookidoo button."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{description.key}"
|
assert coordinator.config_entry.unique_id
|
||||||
|
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{description.key}"
|
||||||
|
|
||||||
async def async_press(self) -> None:
|
async def async_press(self) -> None:
|
||||||
"""Press the button."""
|
"""Press the button."""
|
||||||
|
@ -7,9 +7,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from cookidoo_api import (
|
from cookidoo_api import (
|
||||||
Cookidoo,
|
|
||||||
CookidooAuthException,
|
CookidooAuthException,
|
||||||
CookidooConfig,
|
|
||||||
CookidooRequestException,
|
CookidooRequestException,
|
||||||
get_country_options,
|
get_country_options,
|
||||||
get_localization_options,
|
get_localization_options,
|
||||||
@ -23,7 +21,6 @@ from homeassistant.config_entries import (
|
|||||||
ConfigFlowResult,
|
ConfigFlowResult,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_COUNTRY, CONF_EMAIL, CONF_LANGUAGE, CONF_PASSWORD
|
from homeassistant.const import CONF_COUNTRY, CONF_EMAIL, CONF_LANGUAGE, CONF_PASSWORD
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
||||||
from homeassistant.helpers.selector import (
|
from homeassistant.helpers.selector import (
|
||||||
CountrySelector,
|
CountrySelector,
|
||||||
CountrySelectorConfig,
|
CountrySelectorConfig,
|
||||||
@ -35,6 +32,7 @@ from homeassistant.helpers.selector import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
from .helpers import cookidoo_from_config_data
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -57,10 +55,14 @@ AUTH_DATA_SCHEMA = {
|
|||||||
class CookidooConfigFlow(ConfigFlow, domain=DOMAIN):
|
class CookidooConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Cookidoo."""
|
"""Handle a config flow for Cookidoo."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
MINOR_VERSION = 2
|
||||||
|
|
||||||
COUNTRY_DATA_SCHEMA: dict
|
COUNTRY_DATA_SCHEMA: dict
|
||||||
LANGUAGE_DATA_SCHEMA: dict
|
LANGUAGE_DATA_SCHEMA: dict
|
||||||
|
|
||||||
user_input: dict[str, Any]
|
user_input: dict[str, Any]
|
||||||
|
user_uuid: str
|
||||||
|
|
||||||
async def async_step_reconfigure(
|
async def async_step_reconfigure(
|
||||||
self, user_input: dict[str, Any]
|
self, user_input: dict[str, Any]
|
||||||
@ -78,8 +80,11 @@ class CookidooConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None and not (
|
if user_input is not None and not (
|
||||||
errors := await self.validate_input(user_input)
|
errors := await self.validate_input(user_input)
|
||||||
):
|
):
|
||||||
|
await self.async_set_unique_id(self.user_uuid)
|
||||||
if self.source == SOURCE_USER:
|
if self.source == SOURCE_USER:
|
||||||
self._async_abort_entries_match({CONF_EMAIL: user_input[CONF_EMAIL]})
|
self._abort_if_unique_id_configured()
|
||||||
|
if self.source == SOURCE_RECONFIGURE:
|
||||||
|
self._abort_if_unique_id_mismatch()
|
||||||
self.user_input = user_input
|
self.user_input = user_input
|
||||||
return await self.async_step_language()
|
return await self.async_step_language()
|
||||||
await self.generate_country_schema()
|
await self.generate_country_schema()
|
||||||
@ -153,10 +158,8 @@ class CookidooConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
if not (
|
if not (
|
||||||
errors := await self.validate_input({**reauth_entry.data, **user_input})
|
errors := await self.validate_input({**reauth_entry.data, **user_input})
|
||||||
):
|
):
|
||||||
if user_input[CONF_EMAIL] != reauth_entry.data[CONF_EMAIL]:
|
await self.async_set_unique_id(self.user_uuid)
|
||||||
self._async_abort_entries_match(
|
self._abort_if_unique_id_mismatch()
|
||||||
{CONF_EMAIL: user_input[CONF_EMAIL]}
|
|
||||||
)
|
|
||||||
return self.async_update_reload_and_abort(
|
return self.async_update_reload_and_abort(
|
||||||
reauth_entry, data_updates=user_input
|
reauth_entry, data_updates=user_input
|
||||||
)
|
)
|
||||||
@ -220,21 +223,10 @@ class CookidooConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
await get_localization_options(country=data_input[CONF_COUNTRY].lower())
|
await get_localization_options(country=data_input[CONF_COUNTRY].lower())
|
||||||
)[0].language # Pick any language to test login
|
)[0].language # Pick any language to test login
|
||||||
|
|
||||||
localizations = await get_localization_options(
|
cookidoo = await cookidoo_from_config_data(self.hass, data_input)
|
||||||
country=data_input[CONF_COUNTRY].lower(),
|
|
||||||
language=data_input[CONF_LANGUAGE],
|
|
||||||
)
|
|
||||||
|
|
||||||
cookidoo = Cookidoo(
|
|
||||||
async_get_clientsession(self.hass),
|
|
||||||
CookidooConfig(
|
|
||||||
email=data_input[CONF_EMAIL],
|
|
||||||
password=data_input[CONF_PASSWORD],
|
|
||||||
localization=localizations[0],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
await cookidoo.login()
|
auth_data = await cookidoo.login()
|
||||||
|
self.user_uuid = auth_data.sub
|
||||||
if language_input:
|
if language_input:
|
||||||
await cookidoo.get_additional_items()
|
await cookidoo.get_additional_items()
|
||||||
except CookidooRequestException:
|
except CookidooRequestException:
|
||||||
|
@ -21,10 +21,12 @@ class CookidooBaseEntity(CoordinatorEntity[CookidooDataUpdateCoordinator]):
|
|||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
assert coordinator.config_entry.unique_id
|
||||||
|
|
||||||
self.device_info = DeviceInfo(
|
self.device_info = DeviceInfo(
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
name="Cookidoo",
|
name="Cookidoo",
|
||||||
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
|
identifiers={(DOMAIN, coordinator.config_entry.unique_id)},
|
||||||
manufacturer="Vorwerk International & Co. KmG",
|
manufacturer="Vorwerk International & Co. KmG",
|
||||||
model="Cookidoo - Thermomix® recipe portal",
|
model="Cookidoo - Thermomix® recipe portal",
|
||||||
)
|
)
|
||||||
|
37
homeassistant/components/cookidoo/helpers.py
Normal file
37
homeassistant/components/cookidoo/helpers.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
"""Helpers for cookidoo."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from cookidoo_api import Cookidoo, CookidooConfig, get_localization_options
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_COUNTRY, CONF_EMAIL, CONF_LANGUAGE, CONF_PASSWORD
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .coordinator import CookidooConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def cookidoo_from_config_data(
|
||||||
|
hass: HomeAssistant, data: dict[str, Any]
|
||||||
|
) -> Cookidoo:
|
||||||
|
"""Build cookidoo from config data."""
|
||||||
|
localizations = await get_localization_options(
|
||||||
|
country=data[CONF_COUNTRY].lower(),
|
||||||
|
language=data[CONF_LANGUAGE],
|
||||||
|
)
|
||||||
|
|
||||||
|
return Cookidoo(
|
||||||
|
async_get_clientsession(hass),
|
||||||
|
CookidooConfig(
|
||||||
|
email=data[CONF_EMAIL],
|
||||||
|
password=data[CONF_PASSWORD],
|
||||||
|
localization=localizations[0],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def cookidoo_from_config_entry(
|
||||||
|
hass: HomeAssistant, entry: CookidooConfigEntry
|
||||||
|
) -> Cookidoo:
|
||||||
|
"""Build cookidoo from config entry."""
|
||||||
|
return await cookidoo_from_config_data(hass, dict(entry.data))
|
@ -44,7 +44,8 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||||
|
"unique_id_mismatch": "The user identifier does not match the previous identifier"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
|
@ -52,7 +52,8 @@ class CookidooIngredientsTodoListEntity(CookidooBaseEntity, TodoListEntity):
|
|||||||
def __init__(self, coordinator: CookidooDataUpdateCoordinator) -> None:
|
def __init__(self, coordinator: CookidooDataUpdateCoordinator) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_ingredients"
|
assert coordinator.config_entry.unique_id
|
||||||
|
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_ingredients"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def todo_items(self) -> list[TodoItem]:
|
def todo_items(self) -> list[TodoItem]:
|
||||||
@ -112,7 +113,8 @@ class CookidooAdditionalItemTodoListEntity(CookidooBaseEntity, TodoListEntity):
|
|||||||
def __init__(self, coordinator: CookidooDataUpdateCoordinator) -> None:
|
def __init__(self, coordinator: CookidooDataUpdateCoordinator) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_additional_items"
|
assert coordinator.config_entry.unique_id
|
||||||
|
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_additional_items"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def todo_items(self) -> list[TodoItem]:
|
def todo_items(self) -> list[TodoItem]:
|
||||||
|
@ -21,6 +21,8 @@ PASSWORD = "test-password"
|
|||||||
COUNTRY = "CH"
|
COUNTRY = "CH"
|
||||||
LANGUAGE = "de-CH"
|
LANGUAGE = "de-CH"
|
||||||
|
|
||||||
|
TEST_UUID = "sub_uuid"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||||
@ -34,16 +36,10 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_cookidoo_client() -> Generator[AsyncMock]:
|
def mock_cookidoo_client() -> Generator[AsyncMock]:
|
||||||
"""Mock a Cookidoo client."""
|
"""Mock a Cookidoo client."""
|
||||||
with (
|
with patch(
|
||||||
patch(
|
"homeassistant.components.cookidoo.helpers.Cookidoo",
|
||||||
"homeassistant.components.cookidoo.Cookidoo",
|
|
||||||
autospec=True,
|
autospec=True,
|
||||||
) as mock_client,
|
) as mock_client:
|
||||||
patch(
|
|
||||||
"homeassistant.components.cookidoo.config_flow.Cookidoo",
|
|
||||||
new=mock_client,
|
|
||||||
),
|
|
||||||
):
|
|
||||||
client = mock_client.return_value
|
client = mock_client.return_value
|
||||||
client.login.return_value = cast(CookidooAuthResponse, {"name": "Cookidoo"})
|
client.login.return_value = cast(CookidooAuthResponse, {"name": "Cookidoo"})
|
||||||
client.get_ingredient_items.return_value = [
|
client.get_ingredient_items.return_value = [
|
||||||
@ -58,7 +54,9 @@ def mock_cookidoo_client() -> Generator[AsyncMock]:
|
|||||||
"data"
|
"data"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
client.login.return_value = None
|
client.login.return_value = CookidooAuthResponse(
|
||||||
|
**load_json_object_fixture("login.json", DOMAIN)
|
||||||
|
)
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
@ -67,6 +65,8 @@ def mock_cookidoo_config_entry() -> MockConfigEntry:
|
|||||||
"""Mock cookidoo configuration entry."""
|
"""Mock cookidoo configuration entry."""
|
||||||
return MockConfigEntry(
|
return MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
|
version=1,
|
||||||
|
minor_version=2,
|
||||||
data={
|
data={
|
||||||
CONF_EMAIL: EMAIL,
|
CONF_EMAIL: EMAIL,
|
||||||
CONF_PASSWORD: PASSWORD,
|
CONF_PASSWORD: PASSWORD,
|
||||||
@ -74,4 +74,5 @@ def mock_cookidoo_config_entry() -> MockConfigEntry:
|
|||||||
CONF_LANGUAGE: LANGUAGE,
|
CONF_LANGUAGE: LANGUAGE,
|
||||||
},
|
},
|
||||||
entry_id="01JBVVVJ87F6G5V0QJX6HBC94T",
|
entry_id="01JBVVVJ87F6G5V0QJX6HBC94T",
|
||||||
|
unique_id=TEST_UUID,
|
||||||
)
|
)
|
||||||
|
7
tests/components/cookidoo/fixtures/login.json
Normal file
7
tests/components/cookidoo/fixtures/login.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"access_token": "eyJhbGci<redacted>",
|
||||||
|
"expires_in": 43199,
|
||||||
|
"refresh_token": "eyJhbGciOiJSUzI1NiI<redacted>",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"sub": "sub_uuid"
|
||||||
|
}
|
@ -28,7 +28,7 @@
|
|||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'todo_clear',
|
'translation_key': 'todo_clear',
|
||||||
'unique_id': '01JBVVVJ87F6G5V0QJX6HBC94T_todo_clear',
|
'unique_id': 'sub_uuid_todo_clear',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <TodoListEntityFeature: 7>,
|
'supported_features': <TodoListEntityFeature: 7>,
|
||||||
'translation_key': 'additional_item_list',
|
'translation_key': 'additional_item_list',
|
||||||
'unique_id': '01JBVVVJ87F6G5V0QJX6HBC94T_additional_items',
|
'unique_id': 'sub_uuid_additional_items',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
@ -75,7 +75,7 @@
|
|||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <TodoListEntityFeature: 4>,
|
'supported_features': <TodoListEntityFeature: 4>,
|
||||||
'translation_key': 'ingredient_list',
|
'translation_key': 'ingredient_list',
|
||||||
'unique_id': '01JBVVVJ87F6G5V0QJX6HBC94T_ingredients',
|
'unique_id': 'sub_uuid_ingredients',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -200,7 +200,12 @@ async def test_flow_reconfigure_success(
|
|||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={**MOCK_DATA_USER_STEP, CONF_COUNTRY: "DE"},
|
user_input={
|
||||||
|
**MOCK_DATA_USER_STEP,
|
||||||
|
CONF_EMAIL: "new-email",
|
||||||
|
CONF_PASSWORD: "new-password",
|
||||||
|
CONF_COUNTRY: "DE",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
@ -215,6 +220,8 @@ async def test_flow_reconfigure_success(
|
|||||||
assert result["reason"] == "reconfigure_successful"
|
assert result["reason"] == "reconfigure_successful"
|
||||||
assert cookidoo_config_entry.data == {
|
assert cookidoo_config_entry.data == {
|
||||||
**MOCK_DATA_USER_STEP,
|
**MOCK_DATA_USER_STEP,
|
||||||
|
CONF_EMAIL: "new-email",
|
||||||
|
CONF_PASSWORD: "new-password",
|
||||||
CONF_COUNTRY: "DE",
|
CONF_COUNTRY: "DE",
|
||||||
CONF_LANGUAGE: "de-DE",
|
CONF_LANGUAGE: "de-DE",
|
||||||
}
|
}
|
||||||
@ -340,6 +347,35 @@ async def test_flow_reconfigure_init_data_unknown_error_and_recover_on_step_2(
|
|||||||
assert len(hass.config_entries.async_entries()) == 1
|
assert len(hass.config_entries.async_entries()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_reconfigure_id_mismatch(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_cookidoo_client: AsyncMock,
|
||||||
|
cookidoo_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test we abort when the new config is not for the same user."""
|
||||||
|
|
||||||
|
cookidoo_config_entry.add_to_hass(hass)
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
cookidoo_config_entry, unique_id="some_other_uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await cookidoo_config_entry.start_reconfigure_flow(hass)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
**MOCK_DATA_USER_STEP,
|
||||||
|
CONF_EMAIL: "new-email",
|
||||||
|
CONF_PASSWORD: "new-password",
|
||||||
|
CONF_COUNTRY: "DE",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "unique_id_mismatch"
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_reauth(
|
async def test_flow_reauth(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_cookidoo_client: AsyncMock,
|
mock_cookidoo_client: AsyncMock,
|
||||||
@ -419,46 +455,26 @@ async def test_flow_reauth_error_and_recover(
|
|||||||
assert len(hass.config_entries.async_entries()) == 1
|
assert len(hass.config_entries.async_entries()) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
async def test_flow_reauth_id_mismatch(
|
||||||
("new_email", "saved_email", "result_reason"),
|
|
||||||
[
|
|
||||||
(EMAIL, EMAIL, "reauth_successful"),
|
|
||||||
("another-email", EMAIL, "already_configured"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_flow_reauth_init_data_already_configured(
|
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_cookidoo_client: AsyncMock,
|
mock_cookidoo_client: AsyncMock,
|
||||||
cookidoo_config_entry: MockConfigEntry,
|
cookidoo_config_entry: MockConfigEntry,
|
||||||
new_email: str,
|
|
||||||
saved_email: str,
|
|
||||||
result_reason: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we abort user data set when entry is already configured."""
|
"""Test we abort when the new auth is not for the same user."""
|
||||||
|
|
||||||
cookidoo_config_entry.add_to_hass(hass)
|
cookidoo_config_entry.add_to_hass(hass)
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
another_cookidoo_config_entry = MockConfigEntry(
|
cookidoo_config_entry, unique_id="some_other_uuid"
|
||||||
domain=DOMAIN,
|
|
||||||
data={
|
|
||||||
CONF_EMAIL: "another-email",
|
|
||||||
CONF_PASSWORD: PASSWORD,
|
|
||||||
CONF_COUNTRY: COUNTRY,
|
|
||||||
CONF_LANGUAGE: LANGUAGE,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
another_cookidoo_config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
result = await cookidoo_config_entry.start_reauth_flow(hass)
|
result = await cookidoo_config_entry.start_reauth_flow(hass)
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_EMAIL: new_email, CONF_PASSWORD: PASSWORD},
|
{CONF_EMAIL: "new-email", CONF_PASSWORD: PASSWORD},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.ABORT
|
assert result["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == result_reason
|
assert result["reason"] == "unique_id_mismatch"
|
||||||
assert cookidoo_config_entry.data[CONF_EMAIL] == saved_email
|
|
||||||
|
@ -7,9 +7,18 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components.cookidoo.const import DOMAIN
|
from homeassistant.components.cookidoo.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_COUNTRY,
|
||||||
|
CONF_EMAIL,
|
||||||
|
CONF_LANGUAGE,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from . import setup_integration
|
from . import setup_integration
|
||||||
|
from .conftest import COUNTRY, EMAIL, LANGUAGE, PASSWORD, TEST_UUID
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -100,3 +109,229 @@ async def test_config_entry_not_ready_auth_error(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert cookidoo_config_entry.state is status
|
assert cookidoo_config_entry.state is status
|
||||||
|
|
||||||
|
|
||||||
|
MOCK_CONFIG_ENTRY_MIGRATION = {
|
||||||
|
CONF_EMAIL: EMAIL,
|
||||||
|
CONF_PASSWORD: PASSWORD,
|
||||||
|
CONF_COUNTRY: COUNTRY,
|
||||||
|
CONF_LANGUAGE: LANGUAGE,
|
||||||
|
}
|
||||||
|
|
||||||
|
OLD_ENTRY_ID = "OLD_OLD_ENTRY_ID"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"from_version",
|
||||||
|
"from_minor_version",
|
||||||
|
"config_data",
|
||||||
|
"unique_id",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
MOCK_CONFIG_ENTRY_MIGRATION,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(1, 2, MOCK_CONFIG_ENTRY_MIGRATION, TEST_UUID),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_migration_from(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
from_version,
|
||||||
|
from_minor_version,
|
||||||
|
config_data,
|
||||||
|
unique_id,
|
||||||
|
mock_cookidoo_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test different expected migration paths."""
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=config_data,
|
||||||
|
title=f"MIGRATION_TEST from {from_version}.{from_minor_version}",
|
||||||
|
version=from_version,
|
||||||
|
minor_version=from_minor_version,
|
||||||
|
unique_id=unique_id,
|
||||||
|
entry_id=OLD_ENTRY_ID,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
device = device_registry.async_get_or_create(
|
||||||
|
config_entry_id=config_entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, OLD_ENTRY_ID)},
|
||||||
|
entry_type=dr.DeviceEntryType.SERVICE,
|
||||||
|
)
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="todo",
|
||||||
|
unique_id=f"{OLD_ENTRY_ID}_ingredients",
|
||||||
|
device_id=device.id,
|
||||||
|
)
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="todo",
|
||||||
|
unique_id=f"{OLD_ENTRY_ID}_additional_items",
|
||||||
|
device_id=device.id,
|
||||||
|
)
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="button",
|
||||||
|
unique_id=f"{OLD_ENTRY_ID}_todo_clear",
|
||||||
|
device_id=device.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
# Check change in config entry and verify most recent version
|
||||||
|
assert config_entry.version == 1
|
||||||
|
assert config_entry.minor_version == 2
|
||||||
|
assert config_entry.unique_id == TEST_UUID
|
||||||
|
|
||||||
|
assert entity_registry.async_is_registered(
|
||||||
|
entity_registry.entities.get_entity_id(
|
||||||
|
(
|
||||||
|
Platform.TODO,
|
||||||
|
DOMAIN,
|
||||||
|
f"{TEST_UUID}_ingredients",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert entity_registry.async_is_registered(
|
||||||
|
entity_registry.entities.get_entity_id(
|
||||||
|
(
|
||||||
|
Platform.TODO,
|
||||||
|
DOMAIN,
|
||||||
|
f"{TEST_UUID}_additional_items",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert entity_registry.async_is_registered(
|
||||||
|
entity_registry.entities.get_entity_id(
|
||||||
|
(
|
||||||
|
Platform.BUTTON,
|
||||||
|
DOMAIN,
|
||||||
|
f"{TEST_UUID}_todo_clear",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"from_version",
|
||||||
|
"from_minor_version",
|
||||||
|
"config_data",
|
||||||
|
"unique_id",
|
||||||
|
"login_exception",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
MOCK_CONFIG_ENTRY_MIGRATION,
|
||||||
|
None,
|
||||||
|
CookidooRequestException,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
MOCK_CONFIG_ENTRY_MIGRATION,
|
||||||
|
None,
|
||||||
|
CookidooAuthException,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_migration_from_with_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
from_version,
|
||||||
|
from_minor_version,
|
||||||
|
config_data,
|
||||||
|
unique_id,
|
||||||
|
login_exception: Exception,
|
||||||
|
mock_cookidoo_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test different expected migration paths but with connection issues."""
|
||||||
|
# Migration can fail due to connection issues as we have to fetch the uuid
|
||||||
|
mock_cookidoo_client.login.side_effect = login_exception
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=config_data,
|
||||||
|
title=f"MIGRATION_TEST from {from_version}.{from_minor_version} with login exception '{login_exception}'",
|
||||||
|
version=from_version,
|
||||||
|
minor_version=from_minor_version,
|
||||||
|
unique_id=unique_id,
|
||||||
|
entry_id=OLD_ENTRY_ID,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
device = device_registry.async_get_or_create(
|
||||||
|
config_entry_id=config_entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, OLD_ENTRY_ID)},
|
||||||
|
entry_type=dr.DeviceEntryType.SERVICE,
|
||||||
|
)
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="todo",
|
||||||
|
unique_id=f"{OLD_ENTRY_ID}_ingredients",
|
||||||
|
device_id=device.id,
|
||||||
|
)
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="todo",
|
||||||
|
unique_id=f"{OLD_ENTRY_ID}_additional_items",
|
||||||
|
device_id=device.id,
|
||||||
|
)
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="button",
|
||||||
|
unique_id=f"{OLD_ENTRY_ID}_todo_clear",
|
||||||
|
device_id=device.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.MIGRATION_ERROR
|
||||||
|
|
||||||
|
assert entity_registry.async_is_registered(
|
||||||
|
entity_registry.entities.get_entity_id(
|
||||||
|
(
|
||||||
|
Platform.TODO,
|
||||||
|
DOMAIN,
|
||||||
|
f"{OLD_ENTRY_ID}_ingredients",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert entity_registry.async_is_registered(
|
||||||
|
entity_registry.entities.get_entity_id(
|
||||||
|
(
|
||||||
|
Platform.TODO,
|
||||||
|
DOMAIN,
|
||||||
|
f"{OLD_ENTRY_ID}_additional_items",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert entity_registry.async_is_registered(
|
||||||
|
entity_registry.entities.get_entity_id(
|
||||||
|
(
|
||||||
|
Platform.BUTTON,
|
||||||
|
DOMAIN,
|
||||||
|
f"{OLD_ENTRY_ID}_todo_clear",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user